smeserver-manager/root/usr/share/smanager/lib/SrvMngr/Controller/Login.pm

274 lines
6.5 KiB
Perl

package SrvMngr::Controller::Login;
#----------------------------------------------------------------------
# heading : Support
# description : Login
# navigation : 0000 001
# menu : N
#
# routes : end
#----------------------------------------------------------------------
# for information
# $r->get('/login')->to('login#main')->name('login');
# $r->post('/login')->to('login#login')->name('signin');
# $r->get('/login2')->to('login#pwdrescue')->name('pwdresc');
# $r->get('/loginc')->to('login#confpwd')->name('resetpwdconf');
# for information
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
use Locale::gettext;
use esmith::AccountsDB;
use SrvMngr::I18N;
use SrvMngr::Model::Main;
use SrvMngr qw( theme_list init_session );
my $MAX_LOGIN_ATTEMPTS = 3;
my $DURATION_BLOCKED = 30 * 60; # access blocked for 30 min
my $TIMEOUT_FAILED_LOGIN = 1;
my $RESET_DURATION = 2 * 60 * 60; # 2 hours for resetting
our $adb = esmith::AccountsDB->open() or die "Couldn't open DB Accounts\n";
my $allowed_user_re = qr/^\w{5,10}$/;
my %Login_Attempts;
sub main {
my $c = shift;
$c->stash( trt => 'NORM' );
$c->render('login');
}
sub login {
my $c = shift;
my $trt = $c->param('Trt');
# password reset request
if ( $trt eq 'RESET' ) {
my $res = $c->mail_rescue();
if ( $res ne 'OK' ) {
$c->stash( error => $res, trt => $trt );
return $c->render('login');
}
$c->flash( success => $c->l('use_RESET_REGISTERED') );
record_login_attempt($c, 'RESET');
return $c->redirect_to( $c->home_page );
}
# normal loggin
my $name = $c->param('Username');
my $pass = $c->param('Password');
my $from = $c->param('From');
if ( is_denied($c) ) {
$c->stash( error => $c->l('use_TOO_MANY_LOGIN'), trt => 'NORM' );
return $c->render('login');
}
# untaint
unless ( ($name =~ /^([a-z][\-\_\.a-z0-9]*)$/) && ($pass =~ /^([ -~]+)$/) ) {
record_login_attempt($c, 'FAILED');
$c->stash( error => $c->l('use_INVALID_DATA'), trt => 'NORM' );
return $c->render('login');
}
my $alias = SrvMngr::Model::Main->check_adminalias( $c );
if ( $alias ) {
if ( $name eq $alias ) {
$name = 'admin';
} elsif ( $name eq 'admin' ) {
record_login_attempt($c, 'FAILED');
$c->stash( error => $c->l('use_SORRY'), trt => 'NORM' );
return $c->render('login');
}
}
if (SrvMngr::Model::Main->check_credentials($name, $pass)) {
$c->session(logged_in => 1); # set the logged_in flag
$c->session(username => $name); # keep a copy of the username
# if ( $name eq 'admin' || $adb->is_user_in_group($name, 'AdmiN') ) # for futur use
if ( $name eq 'admin' ) {
$c->session(is_admin => 1);
} else {
$c->session(is_admin => 0);
}
$c->session(expiration => 600); # expire this session in 10 minutes
$c->flash( success => $c->l('use_WELCOME') );
record_login_attempt($c, 'SUCCESS');
} else {
record_login_attempt($c, 'FAILED');
sleep $TIMEOUT_FAILED_LOGIN;
$c->stash( error => $c->l('use_SORRY'), trt => 'NORM' );
return $c->render('login');
}
$from = $c->home_page if ( $from eq 'login' );
$c->redirect_to( $from );
}
sub pwdrescue {
my $c = shift;
$c->stash( trt => 'RESET' );
$c->render('login');
}
sub mail_rescue {
my $c = shift;
my $name = $c->param('Username');
my $from = $c->param('From');
my $res;
$res .= $c->l('use_TOO_MANY_LOGIN') if ( is_denied($c) );
# untaint
if ( ! $res && $name !~ /^([a-z][\-\_\.a-z0-9]*)$/ ) {
record_login_attempt($c, 'FAILED');
$res .= $c->l('use_ERR_NAME');
}
if ( ! $res && $name eq 'admin' ) {
$res .= $c->l('use_NOT_THAT_OPER');
}
# user exists ?
if ( ! $res ) {
my $acct = $adb->get($name);
if ( ! $acct || $acct->prop('type') ne "user" || $acct->prop('PasswordSet') ne 'yes' ) {
$res .= $c->l('use_NOT_THAT_OPER');
}
}
return $res if $res;
# send email
my $email = $name .'@'. $c->session->{DomainName};
my $until = time() + $RESET_DURATION;
$c->pwdrst->{$name} = {
email => $email,
date => $until,
confirmed => 0,
};
my $jwt = $c->jwt->claims({username => $name})->encode;
my $url = $c->url_for('loginc')->to_abs->query(jwt => $jwt);
# $c->email( $email, $c->l('use_CONFIRM_RESET'), $c->render_to_string(inline => $c->l('use_GO_TO_URL', $url) ) );
# directly (without minion)
$c->send_email( $email, $c->l('use_CONFIRM_RESET'), $c->render_to_string(inline => $c->l('use_GO_TO_URL', $url) ) );
return 'OK';
}
sub logout {
my $c = shift;
$c->app->log->info($c->log_req);
$c->session( expires => 1 );
$c->flash( success => $c->l('use_BYE') );
$c->flash( error => 'Byegood' );
$c->redirect_to( $c->home_page );
}
sub confpwd {
my $c = shift;
my $jwt = $c->param('jwt');
my $name = $c->jwt->decode($jwt)->{username};
# request already treated or outdated
if ( $c->pwdrst->{$name}{confirmed} != 0 or $c->pwdrst->{$name}{date} < time() ) {
$c->flash( error => $c->l('use_INVALID_REQUEST'));
return $c->redirect_to( $c->home_page );
}
# reset password for this account
$c->pwdrst->{$name}{confirmed} = 1;
$c->flash( success => $c->l('use_OK_FOR_RESET') );
# call userpassword with encoded name
my $url = $c->url_for('userpasswordr')->to_abs->query(jwt => $jwt);
# warn "confpwd: " . $url . "\n";
return $c->redirect_to( $url );
}
sub record_login_attempt {
my ($c, $result) = @_;
my $user = $c->param('Username');
my $ip_address = $c->tx->remote_address;
if ($result eq 'RESET') {
$c->app->log->info(join "\t", "Password reset requested for : $user at ", $ip_address);
} elsif ($result eq 'SUCCESS') {
$c->app->log->info(join "\t", "Login succeeded: $user", $ip_address);
$Login_Attempts{$ip_address}->{tries} = 0; # reset the number of login attempts
} else {
$c->app->log->info(join "\t", "Login FAILED: $user", $ip_address);
$Login_Attempts{$ip_address}->{tries}++;
if ( $Login_Attempts{$ip_address}->{tries} > $MAX_LOGIN_ATTEMPTS ) {
$Login_Attempts{$ip_address}->{denied_until} = time() + $DURATION_BLOCKED;
}
}
}
sub is_denied {
my ($c) = @_;
my $ip_address = $c->tx->remote_address;
return unless exists $Login_Attempts{$ip_address}
&& exists $Login_Attempts{$ip_address}->{denied_until};
return 'Denied'
if $Login_Attempts{$ip_address}->{denied_until} > time();
# TIMEOUT has expired, reset attempts
delete $Login_Attempts{$ip_address}->{denied_until};
$Login_Attempts{$ip_address}->{tries} = 0;
return;
}
1;