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 )