diff --git a/Web/lib/MJB/Web/Controller/Auth.pm b/Web/lib/MJB/Web/Controller/Auth.pm index 35ad0e9..5c4d88a 100644 --- a/Web/lib/MJB/Web/Controller/Auth.pm +++ b/Web/lib/MJB/Web/Controller/Auth.pm @@ -313,9 +313,10 @@ sub do_forgot ( $c ) { return $c->redirect_error( 'show_forgot' ) if $c->stash->{errors}; - # Make a token & send the email TODO - my $token = $person->create_auth_token( 'forgot' ); + # Make a token for password resetting. + my $token = $c->stash->{token} = $person->create_auth_token( 'forgot' ); + # Send the user an email with the link for resetting. $c->send_email( 'forgot_password', { send_to => $email, link => 'https://' . $c->config->{domain_for_links} . "/reset/$token" @@ -359,10 +360,12 @@ sub do_reset ( $c ) { my $lower_time = DateTime->now; $lower_time->subtract( minutes => 60 ); + my $dtf = $c->db->storage->datetime_parser; + my $record = $c->db->auth_tokens->search( { token => $token, scope => 'forgot', - 'me.created_at' => { '>=', $lower_time }, + 'me.created_at' => { '>=', $dtf->format_datetime($lower_time) }, }, { prefetch => 'person' })->first; push @{$c->stash->{errors}}, "This token is not valid." diff --git a/Web/lib/MJB/Web/Plugin/Email.pm b/Web/lib/MJB/Web/Plugin/Email.pm index 281259b..f125f32 100644 --- a/Web/lib/MJB/Web/Plugin/Email.pm +++ b/Web/lib/MJB/Web/Plugin/Email.pm @@ -4,9 +4,35 @@ use Email::Sender::Simple qw( sendmail ); use Email::Sender::Transport::SMTP; use Email::MIME::Kit; +#== +# This plugin supports transactional email. The email templates are +# stored in mkits/ at the root of the repo. +# +# When this code is under test conditons, email will not be sent. +#== + sub register ( $self, $app, $config ) { $app->helper( send_email => sub ($c, $template, $options ) { + + # Do not send email under test conditions. + if ( $ENV{MJB_TESTMODE} and $ENV{MJB_TESTMODE} == 1 ) { + # Allow debugging of the emails being send + # -- show the template and options: + # + # MJB_DEBUG=1 prove -lrv t/01_endpoints/02_auth/10_do_forgot.t + if ( $ENV{MJB_DEBUG} and $ENV{MJB_DEBUG} == 1 ) { + require Data::Dumper; + warn "\n>>> Email Not Sending During Test\n"; + warn "Template: $template\n"; + warn "Options:\n"; + warn Data::Dumper::Dumper $options; + warn "\n>>> Finished Email\n"; + } + return undef; + } + + my $transport = Email::Sender::Transport::SMTP->new(%{$c->config->{smtp}}); my $mkit_path = $app->home->child('mkits')->to_string; diff --git a/Web/t/01_endpoints/02_auth/09_forgot.t b/Web/t/01_endpoints/02_auth/09_forgot.t new file mode 100644 index 0000000..e32cc87 --- /dev/null +++ b/Web/t/01_endpoints/02_auth/09_forgot.t @@ -0,0 +1,13 @@ +#!/usr/bin/env perl +use MJB::Web::Test; + +my $t = Test::Mojo::MJB->new('MJB::Web'); + +#== +# This test ensures that users can access the forgot password page. +#== + +$t->get_ok( '/forgot' ) + ->status_is( 200 ); + +done_testing(); diff --git a/Web/t/01_endpoints/02_auth/10_do_forgot.t b/Web/t/01_endpoints/02_auth/10_do_forgot.t new file mode 100644 index 0000000..58bdd0c --- /dev/null +++ b/Web/t/01_endpoints/02_auth/10_do_forgot.t @@ -0,0 +1,37 @@ +#!/usr/bin/env perl +use MJB::Web::Test; + +#== +# This test ensures that the forgot password controller works correctly. +# +# It will create an account, log out of that account, and request a password +# reset. It will confirm a token exists in the DB. +#== + +my $t = Test::Mojo::MJB->new('MJB::Web'); + +# Make sure that open registration method is enabled and create a user account. +$t->app->config->{register}{enable_open} = 1; +$t->post_ok( '/register/open', form => { + name => 'fred', + email => 'fred@blog.com', + password => 'SuperSecure', + password_confirm => 'SuperSecure', + }) + ->status_is( 302 ) + ->get_ok( '/logout' ) + ->status_is( 302 ) + ->header_is( location => '/' ); + +# Fill out the form and fetch the reset token. +my $token = $t->post_ok( '/forgot', form => { + email => 'fred@blog.com', + }) + ->status_is( 302 ) + ->header_is( location => '/forgot' ) + ->stash->{token}; + +# Confirm the token exists in the DB. +is $t->app->db->auth_tokens( { token => $token } )->count, 1, "The token exists."; + +done_testing(); diff --git a/Web/t/01_endpoints/02_auth/11_reset.t b/Web/t/01_endpoints/02_auth/11_reset.t new file mode 100644 index 0000000..847ca88 --- /dev/null +++ b/Web/t/01_endpoints/02_auth/11_reset.t @@ -0,0 +1,42 @@ +#!/usr/bin/env perl +use MJB::Web::Test; + +#== +# This test ensures that the reset password controller works correctly. +# +# It will create an account, log out of that account, and request a password +# reset. It will confirm a token exists in the DB, and then it will confirm it +# can access that page to reset the password. +#== + +my $t = Test::Mojo::MJB->new('MJB::Web'); + +# Make sure that open registration method is enabled and create a user account. +$t->app->config->{register}{enable_open} = 1; +$t->post_ok( '/register/open', form => { + name => 'fred', + email => 'fred@blog.com', + password => 'SuperSecure', + password_confirm => 'SuperSecure', + }) + ->status_is( 302 ) + ->get_ok( '/logout' ) + ->status_is( 302 ) + ->header_is( location => '/' ); + +# Fill out the form and fetch the reset token. +my $token = $t->post_ok( '/forgot', form => { + email => 'fred@blog.com', + }) + ->status_is( 302 ) + ->header_is( location => '/forgot' ) + ->stash->{token}; + +# Confirm the token exists in the DB. +is $t->app->db->auth_tokens( { token => $token } )->count, 1, "The token exists."; + +# Confirm the /reset/token page now exists. +$t->get_ok( "/reset/$token" ) + ->status_is( 200 ); + +done_testing(); diff --git a/Web/t/01_endpoints/02_auth/12_do_reset.t b/Web/t/01_endpoints/02_auth/12_do_reset.t new file mode 100644 index 0000000..9b826c1 --- /dev/null +++ b/Web/t/01_endpoints/02_auth/12_do_reset.t @@ -0,0 +1,67 @@ +#!/usr/bin/env perl +use MJB::Web::Test; + +#== +# This test ensures that the forgot password controller works correctly. +# +# It will create an account, log out of that account, and request a password +# reset. It will confirm a token exists in the DB, then reset the password, +# and finally log into the account with the new password. +#== + +my $t = Test::Mojo::MJB->new('MJB::Web'); + +# Make sure that open registration method is enabled and create a user account. +$t->app->config->{register}{enable_open} = 1; +$t->post_ok( '/register/open', form => { + name => 'fred', + email => 'fred@blog.com', + password => 'SuperSecure', + password_confirm => 'SuperSecure', + }) + ->status_is( 302 ) + ->get_ok( '/logout' ) + ->status_is( 302 ) + ->header_is( location => '/' ); + +# Fill out the form and fetch the reset token. +my $token = $t->post_ok( '/forgot', form => { + email => 'fred@blog.com', + }) + ->status_is( 302 ) + ->header_is( location => '/forgot' ) + ->stash->{token}; + +# Confirm the token exists in the DB. +is $t->app->db->auth_tokens( { token => $token } )->count, 1, "The token exists."; + +# Reset the password. +$t->post_ok( "/reset/$token", form => { + password => 'NewPassword', + password_confirm => 'NewPassword', + }) + ->status_is( 302 ) + ->header_is( location => '/dashboard' ); + +# Remove session information so we are logged out of the fred account. +$t->reset_session; + +# Confirm the reset session logged us out by testing if the /dashboard redirects to login. +$t->get_ok( '/dashboard' ) + ->status_is( 302 ) + ->header_is( location => '/login' ); + +# Try to login to the new password. +$t->post_ok( '/login', form => { + email => 'fred@blog.com', + password => 'NewPassword', + })->status_is( 302 + )->header_is( location => '/dashboard', 'Login redirected to dashboard' ) + ->code_block( sub { + is( scalar(@{shift->stash->{errors}}), 0, 'No errors' ); + })->get_ok( '/' + )->code_block( sub { + is(shift->stash->{person}->name, 'fred', 'Got the fred after login...'); + }); + +done_testing();