diff --git a/lib/cyberman.pm b/lib/cyberman.pm index 04a3ead..d70002c 100644 --- a/lib/cyberman.pm +++ b/lib/cyberman.pm @@ -1,20 +1,13 @@ package cyberman; use Dancer2; +use Dancer2::Plugin::Database; use cyberman::Domains; +use cyberman::Auth; +use cyberman::Helper; -use Dancer2::Plugin::Database; -use Digest::Bcrypt; -use Math::Random::Secure qw(rand irand); - -##### -# cyberman.pm -# index page and authentication -# maybe this could be split into another file at a later juncture -##### - -# misc authentication subs +# Index route, hook and helper functions for authentication sub get_auth { my $uid = shift; @@ -29,20 +22,6 @@ sub get_auth { } } -sub randstring { - my $len = shift; - - my @chars = (0..9, "a".."z", "A".."Z"); - my $ret; - - for (1..$len) { - $ret .= $chars[irand(scalar(@chars))]; - } - return $ret; -} - -prefix undef; - hook 'before' => sub { sub cookieval { my $name = shift; @@ -83,118 +62,4 @@ get qr{^/(index)?$} => sub { }; }; -post '/register' => sub { - my %errs; - - for my $param ("password", "password2", "email") { - if (!param($param)) { - $errs{"e_no_$param"} = 1; - } - } - - if (!exists $errs{"e_no_password"} || !exists $errs{"e_no_password2"}) { - if (param("password") ne param("password2")) { - $errs{"e_pass_match"} = 1; - } - - if (length param("password") < 8) { - $errs{"e_pass_len"} = 1; - } - } - - if (scalar(keys(%errs)) != 0) { - return template 'register' => { - error => 1, - %errs, - }; - } - - # Hash password - my $salt = randstring(16); - - my $b = new Digest::Bcrypt; - $b->cost(8); - $b->salt($salt); - $b->add(param "password"); - - # Create the account in the database - database->quick_insert( - "user", - { - "email" => param("email"), - "password" => $b->bcrypt_b64digest, - "salt" => $salt, - }, - ); - - # TODO: send confirmation email - - template 'login' => { - account_created => 1, - }; -}; - -post '/login' => sub { - my %errs; - - my $user = database->quick_select( - "user", - { - "email" => param("email"), - }, - ); - - if (!$user) { - $errs{"e_no_user"} = 1; - } - - if (scalar(keys(%errs)) == 0) { - my $b = new Digest::Bcrypt; - $b->cost(8); - $b->salt($user->{"salt"}); - $b->add(param "password"); - - $errs{"e_pass"} = 1 unless $b->bcrypt_b64digest eq $user->{"password"}; - } - - if (scalar(keys(%errs)) == 0) { - $errs{"e_not_confirmed"} = 1 unless $user->{"active"}; - } - - if (scalar(keys(%errs)) != 0) { - return template 'login' => { - error => 1, - %errs, - }; - } - - # checks finished, we can create a session now - - my $token = randstring(32); - - database->quick_insert( - "session", - { - "token" => $token, - "uid" => $user->{"id"}, - "since" => time, - }, - ); - - cookie id => $user->{"id"}, http_only => 1; - cookie token => $token, http_only => 1; - - template 'redir' => { - "redir" => "domains", - }; -}; - -post '/logout' => sub { - cookie 'id' => undef; - cookie 'token' => undef; - template 'redir' => { - "redir" => "index", - }; -}; - true; diff --git a/lib/cyberman/Auth.pm b/lib/cyberman/Auth.pm new file mode 100644 index 0000000..1f5ad7f --- /dev/null +++ b/lib/cyberman/Auth.pm @@ -0,0 +1,127 @@ +package cyberman::Auth; + +use Dancer2 appname => "cyberman"; +use Dancer2::Plugin::Database; +use Digest::Bcrypt; +use Math::Random::Secure qw(irand); + +use cyberman::Helper; + +# This file: auth-related routes (register, login, logout) +# Hooks and helper functions for authentication are in cyberman.pm + +post '/register' => sub { + my %errs; + + for my $param ("password", "password2", "email") { + if (!param($param)) { + $errs{"e_no_$param"} = 1; + } + } + + if (!exists $errs{"e_no_password"} || !exists $errs{"e_no_password2"}) { + if (param("password") ne param("password2")) { + $errs{"e_pass_match"} = 1; + } + + if (length param("password") < 8) { + $errs{"e_pass_len"} = 1; + } + } + + if (scalar(keys(%errs)) != 0) { + return template 'register' => { + error => 1, + %errs, + }; + } + + # Hash password + my $salt = randstring(16); + + my $b = new Digest::Bcrypt; + $b->cost(8); + $b->salt($salt); + $b->add(param "password"); + + # Create the account in the database + database->quick_insert( + "user", + { + "email" => param("email"), + "password" => $b->bcrypt_b64digest, + "salt" => $salt, + }, + ); + + # TODO: send confirmation email + + template 'login' => { + account_created => 1, + }; +}; + +post '/login' => sub { + my %errs; + + my $user = database->quick_select( + "user", + { + "email" => param("email"), + }, + ); + + if (!$user) { + $errs{"e_no_user"} = 1; + } + + if (scalar(keys(%errs)) == 0) { + my $b = new Digest::Bcrypt; + $b->cost(8); + $b->salt($user->{"salt"}); + $b->add(param "password"); + + $errs{"e_pass"} = 1 unless $b->bcrypt_b64digest eq $user->{"password"}; + } + + if (scalar(keys(%errs)) == 0) { + $errs{"e_not_confirmed"} = 1 unless $user->{"active"}; + } + + if (scalar(keys(%errs)) != 0) { + return template 'login' => { + error => 1, + %errs, + }; + } + + # checks finished, we can create a session now + + my $token = randstring(32); + + database->quick_insert( + "session", + { + "token" => $token, + "uid" => $user->{"id"}, + "since" => time, + }, + ); + + cookie id => $user->{"id"}, http_only => 1; + cookie token => $token, http_only => 1; + + template 'redir' => { + "redir" => "domains", + }; +}; + +post '/logout' => sub { + cookie 'id' => undef; + cookie 'token' => undef; + template 'redir' => { + "redir" => "index", + }; +}; + +true; diff --git a/lib/cyberman/Helper.pm b/lib/cyberman/Helper.pm index 960ac51..77c17a0 100644 --- a/lib/cyberman/Helper.pm +++ b/lib/cyberman/Helper.pm @@ -1,9 +1,14 @@ package cyberman::Helper; use base qw(Exporter); use Dancer2 appname => "cyberman"; + +use Math::Random::Secure qw(irand); + use Exporter qw(import); -our @EXPORT = qw(auth_test); +our @EXPORT = qw(auth_test randstring); + +# Helper functions sub auth_test { my $id = undef; @@ -24,4 +29,16 @@ sub auth_test { } } +sub randstring { + my $len = shift; + + my @chars = (0..9, "a".."z", "A".."Z"); + my $ret; + + for (1..$len) { + $ret .= $chars[irand(scalar(@chars))]; + } + return $ret; +} + 1;