diff --git a/Build.PL b/Build.PL index 75da056..01c1c0a 100644 --- a/Build.PL +++ b/Build.PL @@ -24,6 +24,7 @@ my $build = Module::Build->new( 'Email::Valid' => 0, 'Crypt::Bcrypt' => 0, 'Email::Sender' => 0, + 'HTML::Entities' => 0, }, install_path => { 'templates' => "$HOME/.local/share/peace/template", diff --git a/db_tests/00002-developer-dao.t b/db_tests/00002-developer-dao.t index a9395f0..980a207 100644 --- a/db_tests/00002-developer-dao.t +++ b/db_tests/00002-developer-dao.t @@ -7,6 +7,7 @@ use warnings; use Test::Most tests => 2; +use Crypt::URandom q/urandom/; use DateTime; use Peace; @@ -32,6 +33,7 @@ use Peace::DAO::Developer; verified => 0, secret_bcrypt => $secret_bcrypt ); + $developer->email($developer->email =~ s/\@/unpack('H*', urandom(100)).'@'/er); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); ## WHEN diff --git a/db_tests/00003-application-dao.t b/db_tests/00003-application-dao.t index 9fb532e..89be3f5 100644 --- a/db_tests/00003-application-dao.t +++ b/db_tests/00003-application-dao.t @@ -10,6 +10,7 @@ use Data::Dumper; use Test::Most tests => 4; use DateTime; +use Crypt::URandom q/urandom/; use Peace; use Peace::DB; @@ -30,6 +31,7 @@ use Peace::Test::Mock::Model::Application; my $dbh = Peace::DB->dbh( config => $config ); my $developer = Peace::Test::Mock::Model::Developer->new; + $developer->email($developer->email =~ s/\@/unpack('H*', urandom(100)).'@'/er); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); $developer_dao->create( developer => $developer ); @@ -60,6 +62,7 @@ use Peace::Test::Mock::Model::Application; my $secret_bcrypt = 'hola'; my $developer = Peace::Test::Mock::Model::Developer->new; + $developer->email($developer->email =~ s/\@/unpack('H*', urandom(100)).'@'/er); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); $developer_dao->create( developer => $developer ); diff --git a/db_tests/00004-release-dao.t b/db_tests/00004-release-dao.t index e90234d..e5c52c5 100644 --- a/db_tests/00004-release-dao.t +++ b/db_tests/00004-release-dao.t @@ -10,6 +10,7 @@ use Data::Dumper; use Test::Most tests => 2; use DateTime; +use Crypt::URandom q/urandom/; use Peace; use Peace::DB; @@ -32,6 +33,7 @@ use Peace::Test::Mock::Model::Release; my $dbh = Peace::DB->dbh( config => $config ); my $developer = Peace::Test::Mock::Model::Developer->new; + $developer->email($developer->email =~ s/\@/unpack('H*', urandom(100)).'@'/er); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); $developer_dao->create( developer => $developer ); diff --git a/db_tests/00005-build-dao.t b/db_tests/00005-build-dao.t index 721cd0d..2b5f771 100644 --- a/db_tests/00005-build-dao.t +++ b/db_tests/00005-build-dao.t @@ -8,6 +8,7 @@ use warnings; use Test::Most tests => 2; use DateTime; +use Crypt::URandom q/urandom/; use Peace; use Peace::DB; @@ -31,6 +32,8 @@ use Peace::Test::Mock::Model::Build; my $release_dao = Peace::DAO::Release->new( dbh => $dbh ); my $application_dao = Peace::DAO::Application->new( dbh => $dbh ); my $build_dao = Peace::DAO::Build->new( dbh => $dbh ); + my $developer = $build->release->application->developer; + $developer->email($developer->email =~ s/\@/unpack('H*', urandom(100)).'@'/er); $developer_dao->create( developer => $build->release->application->developer ); $build->release->application->app_id( diff --git a/doc/index.html b/doc/index.html index 9d9a655..930460e 100644 --- a/doc/index.html +++ b/doc/index.html @@ -8,6 +8,9 @@
  • Peace
  • +
  • + Peace::Controller::Application +
  • Peace::Controller::Developer
  • @@ -32,6 +35,9 @@
  • Peace::DB
  • +
  • + Peace::Email +
  • Peace::Model::Application
  • diff --git a/doc/lib/Peace/Controller/Application.pm.html b/doc/lib/Peace/Controller/Application.pm.html new file mode 100644 index 0000000..021ac24 --- /dev/null +++ b/doc/lib/Peace/Controller/Application.pm.html @@ -0,0 +1,57 @@ + + + + +Peace::Controller::Application - Application's http endpoint. + + + + + + + + + + +

    NAME

    + +

    Peace::Controller::Application - Application's http endpoint.

    + +

    SYNOPSIS

    + +
    # This object is used by mojolicious.
    + +

    DESCRIPTION

    + +

    Peace::Controller::Application allows to interact using a json http api with the Peace::Model::Application objects in Peace.

    + +

    METHODS

    + +

    Peace::Controller::Developer implements the following methods:

    + +

    developer_application_post

    + +
    # To be used by mojolicious.
    + +

    Creates a application in db with the data given by the user.

    + +

    SEE ALSO

    + +

    Peace::Model::Application, Peace::DAO::Application

    + + + + + + + diff --git a/doc/lib/Peace/DAO/Developer.pm.html b/doc/lib/Peace/DAO/Developer.pm.html index 4053995..49b8cda 100644 --- a/doc/lib/Peace/DAO/Developer.pm.html +++ b/doc/lib/Peace/DAO/Developer.pm.html @@ -24,6 +24,7 @@
  • SEE ALSO
  • @@ -73,6 +74,12 @@ my $developer = $developer_dao->recover_by_uuid( uuid => $uuid );Recovers the Peace::Model::Developer associated from an uuid from database.

    +

    recover_by_identifier

    + +
    my $developer = $developer_dao->recover_by_identifier( identifier => $identifier );
    + +

    Recovers the Peace::Model::Developer associated from an identifier from database.

    +

    SEE ALSO

    Peace::DB, Peace::Model::Developer

    diff --git a/doc/lib/Peace/Email.pm.html b/doc/lib/Peace/Email.pm.html new file mode 100644 index 0000000..17c3527 --- /dev/null +++ b/doc/lib/Peace/Email.pm.html @@ -0,0 +1,84 @@ + + + + +Peace::Email - The mail sender module for Peace. + + + + + + + + + + +

    NAME

    + +

    Peace::Email - The mail sender module for Peace.

    + +

    SYNOPSIS

    + +
    my $mailer = Peace::Email->new;
    +
    +$mailer->sendmail(
    +  to      => 'larry@perl,org',
    +  text    => 'hola',
    +  html    => '<b>hola</b>',
    +  subject => 'Patch',
    +);
    + +

    DESCRIPTION

    + +

    Peace::Email reads the Peace config to determine the credentials to send mail and does an abstraction around those.

    + +

    INSTANCE METHODS

    + +

    Peace::Email implements the following instance methods:

    + +

    new

    + +
    my $mailer = Peace::Email->new;
    + +

    Instances a new mailer.

    + +

    METHODS

    + +

    Peace::Email implements the following methods:

    + +

    sendmail

    + +
    $mailer->sendmail(
    +   to      => 'larry@perl,org',
    +   text    => 'hola',
    +   html    => '<b>hola</b>',
    +   subject => 'Patch',
    +);
    + +

    Sends a mail to the given mail address.

    + +

    SEE ALSO

    + +

    Peace

    + + + + + + + diff --git a/doc/lib/Peace/Model/Application.pm.html b/doc/lib/Peace/Model/Application.pm.html index 39227e9..4db87ec 100644 --- a/doc/lib/Peace/Model/Application.pm.html +++ b/doc/lib/Peace/Model/Application.pm.html @@ -22,6 +22,7 @@
  • METHODS
  • @@ -78,10 +79,16 @@

    developer_post

    -
    my $developer_post = $swagger->developer_post
    +
    my $developer_post = $swagger->developer_post;

    Returns the schema of the post request to the /developer enpoint.

    +

    developer_application_post

    + +
    my $developer_application_post = $swagger->developer_application_post;
    + +

    Returns the schema of the post request to the /developer/:identifier/application endpoint.

    + diff --git a/lib/Peace/Controller/Developer.pm b/lib/Peace/Controller/Developer.pm index 2b319b9..3d1ab08 100644 --- a/lib/Peace/Controller/Developer.pm +++ b/lib/Peace/Controller/Developer.pm @@ -11,12 +11,14 @@ use Data::Dumper; use Const::Fast; -use Peace::Swagger; use Crypt::Bcrypt qw/bcrypt/; use Crypt::URandom qw/urandom/; +use HTML::Entities; use Peace; use Peace::DB; +use Peace::Email; +use Peace::Swagger; use Peace::Model::Developer; use Peace::DAO::Developer; @@ -28,6 +30,8 @@ sub post { my $json = $self->req->json; my $peace = Peace->new; my $config = $peace->peace_config; + my $mailer = Peace::Email->new; + my $url = $config->{url}; my $dbh = Peace::DB->dbh( config => $config ); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); @@ -53,6 +57,13 @@ sub post { $self->render( status => 400, text => $@ ); return; } + my $verification_url = "$url/web/account-verification/@{[$developer->verification_secret]}"; + my $verification_url_html = HTML::Entities::encode_entities($verification_url); + $mailer->sendmail( to => $developer->email, + text => "Verify your account at $verification_url", + html => "Verify your account at $verification_url_html.", + subject => 'Verify your Peace account.', + ); $self->render( json => $developer->to_json() ); } 1; diff --git a/lib/Peace/DAO/Developer.pm b/lib/Peace/DAO/Developer.pm index 6b9c676..d0b1fa0 100644 --- a/lib/Peace/DAO/Developer.pm +++ b/lib/Peace/DAO/Developer.pm @@ -47,13 +47,14 @@ EOF my $result; eval { $result = $dbh->selectrow_hashref( - $insert, undef, $developer->secret_bcrypt, - $developer->name, $developer->surname, $developer->email, - $developer->country, $developer->verified + $insert, undef, + $developer->secret_bcrypt, $developer->name, + $developer->surname, $developer->email, + $developer->country, $developer->verified ); }; if ($@) { - if ($@ =~ /duplicate key value/) { + if ( $@ =~ /duplicate key value/ ) { die "Email already registered."; } die $@; @@ -63,6 +64,7 @@ EOF my $new_developer = $self->recover_by_uuid( uuid => $uuid ); $developer->date_creation( $new_developer->date_creation ) if defined $new_developer->date_creation; + $developer->verification_secret( $new_developer->verification_secret ); return $developer; } } @@ -78,16 +80,16 @@ EOF my $self = shift; my %params = $validator->(@_); my $uuid = $params{uuid}; - my $dbh = $self->_dbh; + my $dbh = $self->_dbh; my $result = $dbh->selectrow_hashref( <<'EOF', undef, $uuid ); -SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified +SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified, verification_secret FROM developers WHERE uuid = ?; EOF - if (!defined $result) { + if ( !defined $result ) { die "No such developer $uuid."; } - for my $key (keys %$result) { + for my $key ( keys %$result ) { delete $result->{$key} unless defined $result->{$key}; } @@ -110,19 +112,19 @@ EOF ); sub recover_by_identifier { - my $self = shift; - my %params = $validator->(@_); - my $identifier =$params{identifier}; - my $dbh = $self->_dbh; - my $result = $dbh->selectrow_hashref( <<'EOF', undef, $identifier ); -SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified + my $self = shift; + my %params = $validator->(@_); + my $identifier = $params{identifier}; + my $dbh = $self->_dbh; + my $result = $dbh->selectrow_hashref( <<'EOF', undef, $identifier ); +SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified, verification_secret FROM developers WHERE email = $1 or uuid::text = $1; EOF - if (!defined $result) { + if ( !defined $result ) { die "No such developer $identifier."; } - for my $key (keys %$result) { + for my $key ( keys %$result ) { delete $result->{$key} unless defined $result->{$key}; } @@ -136,11 +138,13 @@ EOF return $developer; } } + sub _dbh { my $self = shift; return $self->{dbh}; } 1; + =encoding utf8 =head1 NAME diff --git a/lib/Peace/DB.pm b/lib/Peace/DB.pm index 65d9974..8000633 100644 --- a/lib/Peace/DB.pm +++ b/lib/Peace/DB.pm @@ -34,6 +34,12 @@ my @migrations = ( stripe_id TEXT, country TEXT NOT NULL, verified BOOL DEFAULT false, + verification_secret TEXT NOT NULL + DEFAULT encode( + sha512( + gen_random_uuid()::text::bytea + )::bytea, \'hex\' + ) UNIQUE, PRIMARY KEY (uuid) );', 'CREATE TABLE applications ( diff --git a/lib/Peace/Email.pm b/lib/Peace/Email.pm index c6b6309..a895c98 100644 --- a/lib/Peace/Email.pm +++ b/lib/Peace/Email.pm @@ -5,6 +5,8 @@ use v5.30.0; use strict; use warnings; +use Encode qw/decode/; + use Peace; use Params::ValidationCompiler qw/validation_for/; @@ -22,60 +24,63 @@ sub new { { my $validator = validation_for( params => { - text => { type => Str }, - html => { type => Str }, - to => { type => Str }, + text => { type => Str }, + html => { type => Str }, + to => { type => Str }, + subject => { type => Str }, } ); sub sendmail { my $self = shift; my %params = $validator->(@_); - my ( $text, $html, $to ) = @params{qw/text html to/}; + my ( $text, $html, $to, $subject ) = @params{qw/text html to subject/}; my @parts = ( Email::MIME->create( attributes => { content_type => 'multipart/alternative', + encoding => 'base64', }, parts => [ Email::MIME->create( attributes => { charset => 'UTF-8', content_type => 'text/plain', - encoding => "quoted-printable", - disposition => 'inline', + encoding => 'base64', + disposition => 'inline', }, - body_str => $text, + body_str => decode('utf-8', $text), ), Email::MIME->create( attributes => { charset => 'UTF-8', content_type => 'text/html', - encoding => "quoted-printable", - disposition => 'inline', + encoding => 'base64', + disposition => 'inline', }, - body_str => $html, + body_str => decode('utf-8', $html), ) ] ) ); my $email = Email::MIME->create( header_str => [ - From => Peace->new->peace_config->{smtp}{sasl_username}, - To => $to, + From => Peace->new->peace_config->{smtp}{sasl_username}, + To => $to, + Subject => $subject, ], attributes => { - encoding => 'base64', + encoding => 'base64', content_type => 'multipart/mixed' }, parts => [@parts], ); Email::Sender::Simple::send( 'Email::Sender::Simple', $email, - { transport => $self->generate_transport } ); + { transport => $self->_generate_transport } ); } } -sub generate_transport { +sub _generate_transport { my $peace_config = Peace->new->peace_config; my $transport = Email::Sender::Transport::SMTP->new( hosts => [ $peace_config->{smtp}{smtp_host} ], @@ -87,3 +92,55 @@ sub generate_transport { return $transport; } 1; +=encoding utf8 + +=head1 NAME + +Peace::Email - The mail sender module for Peace. + +=head1 SYNOPSIS + + my $mailer = Peace::Email->new; + + $mailer->sendmail( + to => 'larry@perl,org', + text => 'hola', + html => 'hola', + subject => 'Patch', + ); + +=head1 DESCRIPTION + +Peace::Email reads the Peace config to determine the credentials +to send mail and does an abstraction around those. + +=head1 INSTANCE METHODS + +Peace::Email implements the following instance methods: + +=head2 new + + my $mailer = Peace::Email->new; + +Instances a new mailer. + +=head1 METHODS + +Peace::Email implements the following methods: + +=head2 sendmail + + $mailer->sendmail( + to => 'larry@perl,org', + text => 'hola', + html => 'hola', + subject => 'Patch', + ); + +Sends a mail to the given mail address. + +=head1 SEE ALSO + +L + +=cut diff --git a/lib/Peace/Model/Developer.pm b/lib/Peace/Model/Developer.pm index 6d8ce75..4645385 100644 --- a/lib/Peace/Model/Developer.pm +++ b/lib/Peace/Model/Developer.pm @@ -17,13 +17,14 @@ use Peace::DAO::Application; params => { uuid => { type => Str, optional => 1 }, date_creation => { type => InstanceOf ['DateTime'], optional => 1 }, - secret_bcrypt => { type => Str }, - name => { type => Str }, - surname => { type => Str }, - email => { type => Str }, - stripe_id => { type => Str, optional => 1 }, - country => { type => Str }, - verified => { type => Bool }, + secret_bcrypt => { type => Str }, + name => { type => Str }, + surname => { type => Str }, + email => { type => Str }, + stripe_id => { type => Str, optional => 1 }, + country => { type => Str }, + verified => { type => Bool }, + verification_secret => { type => Str, optional => 1 }, dbh => { type => HasMethods ['selectall_arrayref'], optional => 1 }, } ); @@ -55,12 +56,13 @@ sub to_json { secret => { type => Str }, } ); + sub login { - my $self = shift; - my %params = $validator->(@_); - my $secret = $params{secret}; + my $self = shift; + my %params = $validator->(@_); + my $secret = $params{secret}; my $secret_bcrypt = $self->secret_bcrypt; - return bcrypt_check($secret, $secret_bcrypt); + return bcrypt_check( $secret, $secret_bcrypt ); } } @@ -207,6 +209,20 @@ sub applications { } } +{ + my $validator = + validation_for( params => [ { type => Str, optional => 1 } ] ); + + sub verification_secret { + my $self = shift; + if (@_) { + my ($new_verification_secret) = $validator->(@_); + $self->{verification_secret} = $new_verification_secret; + } + return $self->{verification_secret}; + } +} + sub _dbh { my $self = shift; return $self->{dbh}; @@ -342,12 +358,20 @@ Allows to retrieve and set the developer country. =head2 verified - my $verified = $developer->verified + my $verified = $developer->verified; $developer->verified($verified); Allows to retrieve and set the developer verified. +=head2 verification_secret + + my $verification_secret = $developer->verification_secret; + + $developer->verification_secret($verification_secret); + +Allows to retrieve and set the developer verification_secret. + =head1 SEE ALSO L, L diff --git a/t/00006-developer-dao.t b/t/00006-developer-dao.t index c43ddca..33822d8 100644 --- a/t/00006-developer-dao.t +++ b/t/00006-developer-dao.t @@ -46,7 +46,7 @@ EOF results => [ ['uuid'], [$uuid], ] }; $sql = <<'EOF'; -SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified +SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified, verification_secret FROM developers WHERE uuid = ?; EOF @@ -60,11 +60,12 @@ EOF 'surname', 'email', 'country', 'verified', 'secret_bcrypt', 'date_creation', - 'stripe_id' + 'stripe_id', 'verification_secret' ], [ - $uuid, $name, $surname, $email, $country, $verified, - $secret_bcrypt, $formatter->format_datetime($datetime), undef + $uuid, $name, $surname, $email, $country, $verified, + $secret_bcrypt, $formatter->format_datetime($datetime), + undef, 'aaa' ] ], }; @@ -81,7 +82,7 @@ EOF my $dbh = DBI->connect( 'DBI:Mock:', '', '' ); my $developer_dao = Peace::DAO::Developer->new( dbh => $dbh ); my $sql = <<'EOF'; -SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified +SELECT uuid, date_creation, secret_bcrypt, name, surname, email, stripe_id, country, verified, verification_secret FROM developers WHERE uuid = ?; EOF @@ -105,11 +106,12 @@ EOF 'surname', 'email', 'country', 'verified', 'secret_bcrypt', 'date_creation', - 'stripe_id' + 'stripe_id', 'verification_secret' ], [ - $uuid, $name, $surname, $email, $country, $verified, - $secret_bcrypt, $formatter->format_datetime($datetime), undef + $uuid, $name, $surname, $email, $country, $verified, + $secret_bcrypt, $formatter->format_datetime($datetime), + undef, 'aaa' ] ], };