diff --git a/lib/Pear/LocalLoop.pm b/lib/Pear/LocalLoop.pm index 9d08712..a453e60 100644 --- a/lib/Pear/LocalLoop.pm +++ b/lib/Pear/LocalLoop.pm @@ -3,8 +3,6 @@ package Pear::LocalLoop; use Mojo::Base 'Mojolicious'; use Data::UUID; use Mojo::JSON; -use Email::Valid; -use Authen::Passphrase::BlowfishCrypt; use Scalar::Util qw(looks_like_number); use Pear::LocalLoop::Schema; use DateTime; @@ -40,7 +38,12 @@ sub startup { 'validate_user' => sub { my ( $c, $email, $password, $args) = @_; my $user = $c->schema->resultset('User')->find({email => $email}); - return $c->check_password_email($email, $password) ? $user->userid : undef; + if ( defined $user ) { + if ( $user->check_password( $password ) ) { + return $user->userid; + } + } + return undef; }, }); @@ -94,14 +97,11 @@ sub startup { $user_routes->get('/home')->to('root#home'); -$self->hook( before_dispatch => sub { - my $self = shift; - - $self->res->headers->header('Access-Control-Allow-Origin' => '*') if $self->app->mode eq 'development'; - - $self->remove_all_expired_sessions(); -}); + $self->hook( before_dispatch => sub { + my $self = shift; + $self->res->headers->header('Access-Control-Allow-Origin' => '*') if $self->app->mode eq 'development'; + }); $self->helper( is_admin => sub { my ($c, $user_id) = @_; @@ -119,154 +119,61 @@ $self->hook( before_dispatch => sub { } }); + $self->helper(get_active_user_id => sub { + my $self = shift; + my $token = $self->get_session_token(); + if (! defined $token){ + return undef; + } -$self->helper( valid_username => sub { - my ($self, $username) = @_; - return ($username =~ m/^[A-Za-z0-9]+$/); -}); - -$self->helper(valid_email => sub { - my ($self, $email) = @_; - return (Email::Valid->address($email)); -}); - -$self->helper(get_active_user_id => sub { - my $self = shift; - - my $token = $self->get_session_token(); - if (! defined $token){ - return undef; - } - - my @out = $self->db->selectrow_array("SELECT UserIdAssignedTo_FK FROM SessionTokens WHERE SessionTokenName = ?",undef,($token)); - if (! @out){ - return undef; - } - else{ - return $out[0]; - } -}); - -$self->helper(get_session_token => sub { - my $self = shift; - - #See if logged in. - my $sessionToken = undef; - - my $json = $self->req->json; - if (defined $json) { - $sessionToken = $json->{$self->app->config->{sessionTokenJsonName}}; - } - - if ( ! defined $sessionToken || $sessionToken eq "" ) { - $sessionToken = $self->session->{$self->app->config->{sessionTokenJsonName}}; - } - - if (defined $sessionToken && $sessionToken eq "" ) { - $sessionToken = undef; - } - - return $sessionToken; -}); - - -#This assumes the user has no current session on that device. -$self->helper(generate_session => sub { - my ($self, $userId) = @_; - - my $sessionToken = $self->generate_session_token(); - - my $insertStatement = $self->db->prepare('INSERT INTO SessionTokens (SessionTokenName, UserIdAssignedTo_FK, ExpireDateTime) VALUES (?, ?, ?)'); - my $rowsAdded = $insertStatement->execute($sessionToken, $userId, DateTime->now()->add( years => 1 )); - - return $sessionToken; -}); - -$self->helper(generate_session_token => sub { - my $self = shift; - return Data::UUID->new->create_str(); -}); - -$self->helper(expire_all_sessions => sub { - my $self = shift; - - my $rowsDeleted = $self->db->prepare("DELETE FROM SessionTokens")->execute(); - - return $rowsDeleted; -}); - -$self->helper(session_token_expiry_date_time => sub { - my $c = shift; - return time() + $c->app->config->{sessionTimeSeconds}; -}); - -$self->helper(remove_all_expired_sessions => sub { - my $self = shift; - - my $timeDateNow = time(); - - my $removeStatement = $self->db->prepare('DELETE FROM SessionTokens WHERE ExpireDateTime < ?'); - my $rowsRemoved = $removeStatement->execute($timeDateNow); - - return $rowsRemoved; -}); - - -#1 = session update, 0 = there was no session or it expired. -#We assume the token has a valid structure. -$self->helper(extend_session => sub { - my ( $self, $sessionToken ) = @_; - - my $timeDateExpire = $self->session_token_expiry_date_time(); - - my $updateStatement = $self->db->prepare('UPDATE SessionTokens SET ExpireDateTime = ? WHERE SessionTokenName = ?'); - my $rowsChanges = $updateStatement->execute($timeDateExpire, $sessionToken); - - #Has been updated. - if ($rowsChanges != 0) { - $self->session(expires => $timeDateExpire); - return 1; - } - else { - $self->session(expires => 1); - return 0; - } -}); - -$self->helper(get_session_expiry => sub { - my ( $self, $sessionToken ) = @_; - - my ( $expireTime ) = $self->db->selectrow_array("SELECT ExpireDateTime FROM SessionTokens WHERE SessionTokenName = ?", undef, ($sessionToken)); - - return $expireTime; - -}); - - #True for session was expire, false there was no session to expire. - $self->helper(expire_current_session => sub { - my $c = shift; - my $self = $c; - - my $sessionToken = $self->get_session_token(); - - $c->schema->resultset('SessionToken')->search({ - sessiontokenname => $sessionToken, - })->delete_all; - - ## TODO Does this need a seperate session cookie? - $self->session(expires => 1); - $self->session->{$self->app->config->{sessionTokenJsonName}} = $sessionToken; - - return 1; + my @out = $self->db->selectrow_array("SELECT UserIdAssignedTo_FK FROM SessionTokens WHERE SessionTokenName = ?",undef,($token)); + if (! @out){ + return undef; + } + else{ + return $out[0]; + } }); - $self->helper(is_token_unused => sub { - my ( $c, $token ) = @_; - return defined $c->schema->resultset('AccountToken')->find({ - accounttokenname => $token, - used => 0, - }); + $self->helper(get_session_token => sub { + my $self = shift; + + #See if logged in. + my $sessionToken = undef; + + my $json = $self->req->json; + if (defined $json) { + $sessionToken = $json->{$self->app->config->{sessionTokenJsonName}}; + } + + if ( ! defined $sessionToken || $sessionToken eq "" ) { + $sessionToken = $self->session->{$self->app->config->{sessionTokenJsonName}}; + } + + if (defined $sessionToken && $sessionToken eq "" ) { + $sessionToken = undef; + } + + return $sessionToken; + }); + + + #This assumes the user has no current session on that device. + $self->helper(generate_session => sub { + my ($self, $userId) = @_; + + my $sessionToken = $self->generate_session_token(); + + my $insertStatement = $self->db->prepare('INSERT INTO SessionTokens (SessionTokenName, UserIdAssignedTo_FK, ExpireDateTime) VALUES (?, ?, ?)'); + my $rowsAdded = $insertStatement->execute($sessionToken, $userId, DateTime->now()->add( years => 1 )); + + return $sessionToken; + }); + + $self->helper(generate_session_token => sub { + my $self = shift; + return Data::UUID->new->create_str(); }); $self->helper(does_organisational_id_exist => sub { @@ -279,50 +186,6 @@ $self->helper(get_session_expiry => sub { my $age_range = $c->schema->resultset('AgeRange')->find({ agerangestring => $age_string }); return defined $age_range ? $age_range->agerangeid : undef; }); - - $self->helper(get_userid_foreign_key => sub { - my ( $c, $email ) = @_; - my $user = $c->schema->resultset('User')->find({ email => $email }); - return defined $user ? $user->userid : undef; - }); - - $self->helper(does_username_exist => sub { - my ( $c, $username ) = @_; - return defined $c->schema->resultset('Customer')->find({ username => $username }); - }); - - $self->helper(does_email_exist => sub { - my ( $c, $email ) = @_; - return defined $c->schema->resultset('User')->find({ email => $email }); - }); - - $self->helper(set_token_as_used => sub { - my ( $c, $token ) = @_; - return defined $c->schema->resultset('AccountToken')->find({ - accounttokenname => $token, - used => 0, - })->update({ used => 1 }); - }); - - $self->helper(generate_hashed_password => sub { - my ( $c, $password ) = @_; - my $ppr = Authen::Passphrase::BlowfishCrypt->new( - cost => 8, - salt_random => 1, - passphrase => $password, - ); - return $ppr->as_crypt; - }); - - # We assume the user already exists. - $self->helper(check_password_email => sub { - my ( $c, $email, $password ) = @_; - my $user = $c->schema->resultset('User')->find({ email => $email }); - return undef unless defined $user; - my $ppr = Authen::Passphrase::BlowfishCrypt->from_crypt($user->hashedpassword); - return $ppr->match($password); - }); - } 1; diff --git a/lib/Pear/LocalLoop/Controller/Register.pm b/lib/Pear/LocalLoop/Controller/Register.pm index 49e2d94..0a34896 100644 --- a/lib/Pear/LocalLoop/Controller/Register.pm +++ b/lib/Pear/LocalLoop/Controller/Register.pm @@ -47,7 +47,7 @@ sub register { } else { my $new_user = $c->schema->resultset('User')->find_or_new({ email => $validation->param('email'), - hashedpassword => $c->generate_hashed_password( $validation->param('password') ), + hashedpassword => $validation->param('password'), joindate => DateTime->now(), customer => { username => $validation->param('name'), diff --git a/t/login.t b/t/login.t deleted file mode 100644 index f579599..0000000 --- a/t/login.t +++ /dev/null @@ -1,302 +0,0 @@ -use Mojo::Base -strict; - -use File::Temp; -use Test::More; -use Test::Mojo; -use Mojo::JSON; -use Time::Fake; - -my $file = File::Temp->new; - -print $file <<'END'; -{ - dsn => "dbi:SQLite::memory:", - user => undef, - pass => undef, -} -END -$file->seek( 0, SEEK_END ); - -$ENV{MOJO_CONFIG} = $file->filename; - -my $t = Test::Mojo->new('Pear::LocalLoop'); -my $schema = $t->app->schema; -$schema->deploy; - -$schema->resultset('AgeRange')->populate([ - [ qw/ agerangestring / ], - [ '20-35' ], - [ '35-50' ], - [ '50+' ], -]); - -$schema->resultset('AccountToken')->create({ - accounttokenname => 'a', -}); - -my $accountToken = 'a'; - -my $sessionTimeSeconds = 60 * 60 * 24 * 7; #1 week. -my $sessionTokenJsonName = 'session_key'; -my $sessionExpiresJsonName = 'sessionExpires'; - -my $location_is = sub { - my ($t, $value, $desc) = @_; - $desc ||= "Location: $value"; - local $Test::Builder::Level = $Test::Builder::Level + 1; - return $t->success(is($t->tx->res->headers->location, $value, $desc)); -}; - -#This depends on "register.t" working - -#Valid customer, this also tests that redirects are disabled for register. -my $email = 'rufus@shinra.energy'; -my $password = 'MakoGold'; -my $testJson = { - 'usertype' => 'customer', - 'token' => $accountToken, - 'username' => 'RufusShinra', - 'email' => $email, - 'postcode' => 'LA1 1AA', - 'password' => $password, - 'age' => '20-35' -}; -$t->post_ok('/api/register' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true); - -#Test login, this also checks that redirects are disabled for login when logged out. -$testJson = { - 'email' => $email, - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true) - ->json_has("/$sessionTokenJsonName"); - - -#Does login/logout work with a cookie based session. -print "test 5 - Logout (cookies)\n"; -$t->post_ok('/api/logout') - ->status_is(200) - ->json_is('/success', Mojo::JSON->true) - ->content_like(qr/you were successfully logged out/i); - -$t->reset_session; - -#Login. -print "test 6 - Login (json)\n"; -$testJson = { - 'email' => $email, - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true) - ->json_has("/$sessionTokenJsonName") - ->json_has("/$sessionExpiresJsonName"); - -my $sessionJsonTest = $t->tx->res->json; -my $expires = $sessionJsonTest->{$sessionExpiresJsonName}; -my $sessionToken = $sessionJsonTest->{$sessionTokenJsonName}; - -#Reset the current state so you are still logged in but there are no cookies. -$t->reset_session; - -#Redirect, as no cookies are set -print "test 7 - Login, no cookies or json redirect to login\n"; -$t->get_ok('/api/') - ->status_is(303) - ->$location_is('/api/login'); - -print "test 8 - Login, no redirect on login paths (json)\n"; -$t->get_ok('/api/' => json => {$sessionTokenJsonName => $sessionToken}) - ->status_is(200); - -#No token send so redirect -print "test 9 - Logout, no cookies or json\n"; -$t->post_ok('/api/logout') - ->status_is(303) - ->$location_is('/api/login'); - -#Token sent logout -print "test 10 - Logout, (json)\n"; -$t->post_ok('/api/logout' => json => {$sessionTokenJsonName => $sessionToken}) - ->status_is(200); - -#Send logged out expired token, -print "test 11 - Logout,expired session redirect (json)\n"; -$t->post_ok('/api/logout' => json => {$sessionTokenJsonName => $sessionToken}) - ->status_is(303) - ->$location_is('/api/login'); - -$t->reset_session; - -#TODO it's difficult to test cookies as they automatically get removed. - -#Login. -print "test 12 - Login test with fake time (json)\n"; -$testJson = { - 'email' => $email, - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(200) - ->json_is('/success', Mojo::JSON->true) - ->json_has("/$sessionTokenJsonName") - ->json_has("/$sessionExpiresJsonName"); - -$sessionJsonTest = $t->tx->res->json; -$expires = $sessionJsonTest->{$sessionExpiresJsonName}; -$sessionToken = $sessionJsonTest->{$sessionTokenJsonName}; - -#Clear cookies -$t->reset_session; - -#Offset time -Time::Fake->offset("+".($sessionTimeSeconds * 2)."s"); - -#Send time expired token, -print "test 13 - Fake time expired session redirect (json)\n"; -$t->post_ok('/api/logout' => json => {$sessionTokenJsonName => $sessionToken}) - ->status_is(303) - ->$location_is('/api/login'); - -Time::Fake->reset(); - -$t->reset_session; - -#Attempt to logout without any session -# This is different from the one above as it's has no state. -print "test 14 - Logout, no session\n"; -$t->post_ok('/api/logout') - ->status_is(303) - ->$location_is('/api/login'); - -#Clear the session state -$t->reset_session; - -#Not logged in, redirect to login. -print "test 15 - Not logged in, get request redirect to login\n"; -$t->get_ok('/api/') - ->status_is(303) - ->$location_is('/api/login'); - -$t->reset_session; - -#Not logged in, redirect to login. -print "test 16 - Not logged in, get request one redirection is ok.\n"; -$t->ua->max_redirects(1); -$t->get_ok('/api/') - ->status_is(200); -$t->ua->max_redirects(0); - -$t->reset_session; - -#Not logged in, redirect to login. -print "test 17 - Not logged in, post request redirect to login\n"; -$t->post_ok('/api/') - ->status_is(303) - ->$location_is('/api/login'); - -$t->reset_session; - -#Not logged in, redirect to login. -print "test 18 - Not logged in, post request one redirection is ok.\n"; -$t->ua->max_redirects(1); -$t->post_ok('/api/') - ->status_is(200); -$t->ua->max_redirects(0); - -$t->reset_session; - -#Here on is just input params checking, no session testing. - -#Test no JSON sent. -print "test 19 - No JSON sent.\n"; -$t->post_ok('/api/login') - ->status_is(400) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/No json sent/i); - -$t->reset_session; - -#Test no email sent -print "test 20 - Email missing\n"; -$testJson = { - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(400) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/No email sent/i); - -$t->reset_session; - -#Invalid email -print "test 21 - Invalid email\n"; -$testJson = { - 'email' => ($email . '@'), - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(400) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/email is invalid/i); - -$t->reset_session; - -#Test no password sent -print "test 22 - No password sent.\n"; -$testJson = { - 'email' => $email, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(400) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/No password sent/i); - -$t->reset_session; - -#Email does not exist -print "test 23 - Email does not exist in the database\n"; -$testJson = { - 'email' => 'heidegger@shinra.energy', - 'password' => $password, -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(401) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/Email or password is invalid/i); - -$t->reset_session; - -#Password is wrong -print "test 24 - Password is wrong\n"; -$testJson = { - 'email' => $email, - 'password' => ($password . 'MoreText'), -}; -$t->post_ok('/api/login' => json => $testJson) - ->status_is(401) - ->json_is('/success', Mojo::JSON->false) - ->content_like(qr/Email or password is invalid/i); - -$t->reset_session; - - - -#$testJson = { -# 'email' => $email, -# 'password' => $password, -#}; -#$t->post_ok('/api/login' => json => $testJson) -# ->status_is(200) -# ->json_is('/success', Mojo::JSON->true); - - -#TODO expire session. - - -done_testing();