Ado
view release on metacpan or search on metacpan
lib/Ado/Plugin/Auth.pm view on Meta::CPAN
# general condition for authenticating users - redirects to /login
sub authenticated {
my ($route, $c) = @_;
$c->debug('in condition "authenticated"') if $Ado::Control::DEV_MODE;
if ($c->user->login_name eq 'guest') {
$c->session(over_route => $c->req->url);
$c->debug('session(over_route => $c->req->url):' . $c->session('over_route'))
if $Ado::Control::DEV_MODE;
$c->redirect_to('/login');
return;
}
return 1;
}
#expires the session.
sub logout {
my ($c) = @_;
$c->session(expires => 1);
$c->redirect_to('/');
return;
}
#authenticate a user /login route implementation is here
sub login {
my ($c) = @_;
#TODO: add json format
#prepare redirect url for after login
unless ($c->session('over_route')) {
my $base_url = $c->url_for('/')->base;
my $referrer = $c->req->headers->referrer // $base_url;
$referrer = $base_url unless $referrer =~ m|^$base_url|;
$c->session('over_route' => $referrer);
$c->debug('over_route is ' . $referrer) if $Ado::Control::DEV_MODE;
}
my $auth_method = Mojo::Util::trim($c->param('auth_method'));
return $c->render(status => 200, template => 'login')
if $c->req->method ne 'POST' && $auth_method eq 'ado';
#derive a helper name for login the user
my $login_helper = 'login_' . $auth_method;
$c->debug('Chosen $login_helper: ' . $login_helper) if $Ado::Control::DEV_MODE;
my $authnticated = 0;
if (eval { $authnticated = $c->$login_helper(); 1 }) {
if ($authnticated) {
$c->app->plugins->emit_hook(after_login => $c);
# Redirect to referrer page with a 302 response
$c->debug('redirecting to ' . $c->session('over_route'))
if $Ado::Control::DEV_MODE;
$c->redirect_to($c->session('over_route'));
return;
}
else {
unless ($c->res->code // '' eq '403') {
$c->stash(error_login => 'Wrong credentials! Please try again!');
$c->render(status => 401, template => 'login');
return;
}
}
}
else {
$c->app->log->error("Error calling \$login_helper:[$login_helper][$@]");
$c->stash(error_login => 'Please choose one of the supported login methods.');
$c->render(status => 401, template => 'login');
return;
}
return;
}
#used as helper 'login_ado' returns 1 on success, '' otherwise
sub _login_ado {
my ($c) = @_;
#1. do basic validation first
my $val = $c->validation;
return '' unless $val->has_data;
if ($val->csrf_protect->has_error('csrf_token')) {
delete $c->session->{csrf_token};
$c->render(error_login => 'Bad CSRF token!', status => 403, template => 'login');
return '';
}
my $_checks = Ado::Model::Users->CHECKS;
$val->required('login_name')->like($_checks->{login_name}{allow});
$val->required('digest')->like(qr/^[0-9a-f]{40}$/);
if ($val->has_error) {
delete $c->session->{csrf_token};
return '';
}
#2. find the user and do logical checks
my $login_name = $val->param('login_name');
my $user = Ado::Model::Users->by_login_name($login_name);
if ((not $user->id) or $user->disabled) {
delete $c->session->{csrf_token};
$c->stash(error_login_name => "No such user '$login_name'!");
return '';
}
#3. really authnticate the user
my $checksum = Mojo::Util::sha1_hex($c->session->{csrf_token} . $user->login_password);
if ($checksum eq $val->param('digest')) {
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
delete $c->session->{csrf_token};
return 1;
}
$c->debug('We should not be here! - wrong password') if $Ado::Control::DEV_MODE;
delete $c->session->{csrf_token};
return '';
}
#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_google {
my ($c) = @_;
state $app = $c->app;
my $provider = $c->param('auth_method');
my $providers = $app->config('Ado::Plugin::Auth')->{providers};
#second call should get the token it self
my $response = $c->oauth2->get_token($provider, $providers->{$provider});
do {
$c->debug("in _login_google \$response: " . $c->dumper($response));
$c->debug("in _login_google error from provider: " . ($c->param('error') || 'no error'));
} if $Ado::Control::DEV_MODE;
if ($response->{access_token}) { #Athenticate, create and login the user.
return _create_or_authenticate_google_user(
$c,
$response->{access_token},
$providers->{$provider}
);
}
else {
#Redirect to front-page and say sorry
# We are very sorry but we need to know you are a reasonable human being.
$c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
. ($c->param('error') || ''));
$c->app->log->error('error_response:' . $c->dumper($response));
$c->res->code(307); #307 Temporary Redirect
$c->redirect_to('/');
}
return;
}
#used as helper within login()
# this method is called as return_url after the user
# agrees or denies access for the application
sub _login_facebook {
my ($c) = @_;
state $app = $c->app;
my $provider = $c->param('auth_method');
my $providers = $app->config('Ado::Plugin::Auth')->{providers};
#second call should get the token it self
my $response = $c->oauth2->get_token($provider, $providers->{$provider});
do {
$c->debug("in _login_facebook \$response: " . $c->dumper($response));
$c->debug(
"in _login_facebook error from provider: " . ($c->param('error') || 'no error'));
} if $Ado::Control::DEV_MODE;
if ($response->{access_token}) { #Athenticate, create and login the user.
return _create_or_authenticate_facebook_user(
$c,
$response->{access_token},
$providers->{$provider}
);
}
else {
#Redirect to front-page and say sorry
# We are very sorry but we need to know you are a reasonable human being.
$c->flash(error_login => $c->l('oauth2_sorry[_1]', ucfirst($provider))
. ($c->param('error') || ''));
$c->app->log->error('error_response:' . $c->dumper($response));
$c->res->code(307); #307 Temporary Redirect
$c->redirect_to('/');
}
return;
}
sub _authenticate_oauth2_user {
my ($c, $user, $time) = @_;
if ( $user->disabled
|| ($user->stop_date != 0 && $user->stop_date < $time)
|| $user->start_date > $time)
{
$c->flash(login_message => $c->l('oauth2_disabled'));
$c->redirect_to('/');
return;
}
$c->session(login_name => $user->login_name);
$c->user($user);
$c->app->log->info('$user ' . $user->login_name . ' logged in!');
return 1;
}
#Creates a user using given info from provider
sub _create_oauth2_user {
my ($c, $user_info, $provider) = @_;
state $app = $c->app;
if (my $user = Ado::Model::Users->add(_user_info_to_args($user_info, $provider))) {
$app->plugins->emit_hook(after_user_add => $c, $user, $user_info);
$c->user($user);
$c->session(login_name => $user->login_name);
$app->log->info($user->description . ' New $user ' . $user->login_name . ' logged in!');
$c->flash(login_message => $c->l('oauth2_wellcome[_1]', $user->name));
$c->redirect_to('/');
return 1;
}
$app->log->error($@);
return;
}
#next two methods
#(_create_or_authenticate_facebook_user and _create_or_authenticate_google_user)
# exist only because we pass different parameters in the form
# which are specific to the provider.
# TODO: think of a way to map the generation of the form arguments to the
# specific provider so we can dramatically reduce the number of provider
# specific subroutines
sub _create_or_authenticate_facebook_user {
my ($c, $access_token, $provider) = @_;
my $ua = Mojo::UserAgent->new;
my $appsecret_proof = Digest::SHA::hmac_sha256_hex($access_token, $provider->{secret});
$c->debug('$appsecret_proof:' . $appsecret_proof);
my $user_info =
$ua->get($provider->{info_url},
form => {access_token => $access_token, appsecret_proof => $appsecret_proof})->res->json;
$c->debug('Response from info_url:' . $c->dumper($user_info)) if $Ado::Control::DEV_MODE;
my $user = Ado::Model::Users->by_email($user_info->{email});
my $time = time;
if ($user->id) {
return _authenticate_oauth2_user($c, $user, $time);
}
#else create the user
return _create_oauth2_user($c, $user_info, $provider);
}
sub _create_or_authenticate_google_user {
my ($c, $access_token, $provider) = @_;
#make request for the user info
my $token_type = 'Bearer';
my $ua = Mojo::UserAgent->new;
my $user_info =
$ua->get($provider->{info_url} => {Authorization => "$token_type $access_token"})
->res->json;
my $user = Ado::Model::Users->by_email($user_info->{email});
my $time = time;
if ($user->id) {
return _authenticate_oauth2_user($c, $user, $time);
}
#else create the user
return _create_oauth2_user($c, $user_info, $provider);
}
# Redirects to Consent screen
sub authorize {
my ($c) = @_;
my $m = $c->param('auth_method');
my $params = $c->app->config('Ado::Plugin::Auth')->{providers}{$m};
$params->{redirect_uri} = '' . $c->url_for("/login/$m")->to_abs;
#This call will redirect the user to the provider Consent screen.
( run in 0.561 second using v1.01-cache-2.11-cpan-39bf76dae61 )