lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/TOTP.pm

123 lines
3.1 KiB
Perl

# TOTP second factor authentication
#
# This plugin handle authentications to ask TOTP second factor for users that
# have registered their TOTP secret
package Lemonldap::NG::Portal::2F::TOTP;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADCREDENTIALS
PE_ERROR
PE_FORMEMPTY
PE_OK
PE_SENDRESPONSE
);
our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::SecondFactor',
'Lemonldap::NG::Common::TOTP';
# INITIALIZATION
has prefix => ( is => 'ro', default => 'totp' );
has logo => ( is => 'rw', default => 'totp.png' );
sub init {
my ($self) = @_;
# If self registration is enabled and "activation" is simply set to
# "enabled", replace the rule to detect if user has registered its key
if ( $self->conf->{totp2fSelfRegistration}
and $self->conf->{totp2fActivation} eq '1' )
{
$self->conf->{totp2fActivation} = '$_2fDevices =~ /"type":\s*"TOTP"/s';
}
return $self->SUPER::init();
}
# RUNNING METHODS
sub run {
my ( $self, $req, $token ) = @_;
$self->logger->debug('Generate TOTP form');
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("TOTP checkLogins set") if ($checkLogins);
# Prepare form
my $tmp = $self->p->sendHtml(
$req,
'totp2fcheck',
params => {
SKIN => $self->conf->{portalSkin},
TOKEN => $token,
CHECKLOGINS => $checkLogins
}
);
$self->logger->debug("Prepare TOTP 2F verification");
$req->response($tmp);
return PE_SENDRESPONSE;
}
sub verify {
my ( $self, $req, $session ) = @_;
$self->logger->debug('TOTP verification');
my $code;
unless ( $code = $req->param('code') ) {
$self->userLogger->error('TOTP 2F: no code');
return PE_FORMEMPTY;
}
my $secret = '';
my $_2fDevices;
if ( $session->{_2fDevices} ) {
$self->logger->debug("Loading 2F Devices ...");
# Read existing 2FDevices
$_2fDevices =
eval { from_json( $session->{_2fDevices}, { allow_nonref => 1 } ); };
if ($@) {
$self->logger->error("Bad encoding in _2fDevices: $@");
return PE_ERROR;
}
$self->logger->debug("2F Device(s) found");
foreach (@$_2fDevices) {
$self->logger->debug("Reading TOTP secret if exists ...");
if ( $_->{type} eq 'TOTP' ) {
$secret = $_->{_secret};
last;
}
}
}
unless ($secret) {
$self->logger->debug("No TOTP secret found");
return PE_BADCREDENTIALS;
}
my $r = $self->verifyCode(
$self->conf->{totp2fInterval},
$self->conf->{totp2fRange},
$self->conf->{totp2fDigits},
$secret, $code
);
if ( $r == -1 ) { return PE_ERROR; }
elsif ($r) {
$self->userLogger->info('TOTP succeed');
return PE_OK;
}
else {
$self->userLogger->notice( 'Invalid TOTP for '
. $session->{ $self->conf->{whatToTrace} }
. ')' );
return PE_BADCREDENTIALS;
}
}
1;