223 lines
7.1 KiB
Perl
223 lines
7.1 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');
|
|
} ## end sub main
|
|
|
|
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);
|
|
} ## end if ($trt eq 'RESET')
|
|
|
|
# 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');
|
|
}
|
|
} ## end if ($alias)
|
|
|
|
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');
|
|
} ## end else [ if (SrvMngr::Model::Main...)]
|
|
$from = $c->home_page if ($from eq 'login');
|
|
$c->redirect_to($from);
|
|
} ## end sub login
|
|
|
|
sub pwdrescue {
|
|
my $c = shift;
|
|
$c->stash(trt => 'RESET');
|
|
$c->render('login');
|
|
} ## end sub pwdrescue
|
|
|
|
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');
|
|
}
|
|
} ## end if (!$res)
|
|
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';
|
|
} ## end sub mail_rescue
|
|
|
|
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);
|
|
} ## end sub logout
|
|
|
|
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);
|
|
} ## end sub confpwd
|
|
|
|
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;
|
|
}
|
|
} ## end else [ if ($result eq 'RESET')]
|
|
} ## end sub record_login_attempt
|
|
|
|
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;
|
|
} ## end sub is_denied
|
|
1;
|