TOTP.pm 4.06 KB
Newer Older
Yadd's avatar
Yadd committed
1 2 3 4 5 6 7 8
# Self U2F registration
package Lemonldap::NG::Portal::2F::Register::TOTP;

use strict;
use Mouse;

our $VERSION = '2.0.0';

9
extends 'Lemonldap::NG::Portal::Main::Plugin', 'Lemonldap::NG::Common::TOTP';
Yadd's avatar
Yadd committed
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

# INITIALIZATION

has ott => (
    is      => 'rw',
    lazy    => 1,
    default => sub {
        my $ott =
          $_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
        $ott->timeout( $_[0]->conf->{formTimeout} );
        return $ott;
    }
);

sub init {
    my ($self) = @_;
26 27 28 29 30
    $self->addAuthRoute(
        totpregister => { ':action' => 'selfRegister' },
        ['POST']
    );
    $self->addAuthRoute( 'totpregister.html' => undef, ['GET'] );
Yadd's avatar
Yadd committed
31 32 33 34 35 36 37 38 39
    return 1;
}

sub selfRegister {
    my ( $self, $req ) = @_;
    my $action = $req->param('action');
    my $user   = $req->userData->{ $self->conf->{whatToTrace} };
    unless ($user) {
        return $self->p->sendError( $req,
40
            'No ' . $self->conf->{whatToTrace} . ' found in user data', 500 );
Yadd's avatar
Yadd committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    }

    # Verification that user has a valid TOTP app
    if ( $action eq 'verify' ) {

        # Get form token
        my $token = $req->param('token');
        unless ($token) {
            $self->userLogger->warn(
                "TOTP registration: register try without token for $user");
            return $self->p->sendError( $req, 'Go away', 400 );
        }

        # Verify that token exists in DB (note that "keep" flag is set to
        # permit more than 1 try during token life
        unless ( $token = $self->ott->getToken( $token, 1 ) ) {
            $self->userLogger->notice(
                "TOTP registration: token expired for $user");
            return $self->p->sendError( $req, 'PE82', 400 );
        }

        # Token is valid, so we have the master key proposed
        # ($token->{_totp2fSecret})

        # Now check TOTP code to verify that user has a valid TOTP app
        my $code = $req->param('code');
        unless ($code) {
            $self->logger->userInfo('TOTP registration: empty validation form');
Yadd's avatar
Yadd committed
69
            return $self->p->sendError( $req, 'missingCode', 200 );
Yadd's avatar
Yadd committed
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
        }
        my $r = $self->verifyCode(
            $self->conf->{totp2fInterval},
            $self->conf->{totp2fRange},
            $token->{_totp2fSecret}, $code
        );
        if ( $r == -1 ) {
            return $self->p->sendError( 'serverError', 500 );
        }

        # Invalid try is returned with a 200 code. Javascript will read error
        # and propose to retry
        elsif ( $r == 0 ) {
            $self->userLogger->notice(
                "TOTP registration: invalid TOTP for $user");
            return $self->p->sendError( $req, 'badCode', 200 );
        }
        $self->logger->debug('TOTP code verified');

89
        # Now code is verified, let's store the master key in persistent data
Yadd's avatar
Yadd committed
90 91
        $self->p->updatePersistentSession( $req,
            { _totp2fSecret => $token->{_totp2fSecret} } );
Yadd's avatar
Yadd committed
92
        $self->userLogger->notice('TOTP registration succeed');
Yadd's avatar
Yadd committed
93 94 95
        return [ 200, [ 'Content-Type' => 'application/json' ],
            ['{"result":1}'] ];
    }
96

Yadd's avatar
Yadd committed
97
    # Get or generate master key
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
    elsif ( $action eq 'getkey' ) {
        my $nk = 0;
        my $secret;
        if ( $req->param('newkey') or not $req->userData->{_totp2fSecret} ) {
            $secret = $self->newSecret;
            $nk     = 1;
        }
        else {
            $secret = $req->userData->{_totp2fSecret};
        }

        # Secret is stored in a token: we choose to not accept secret returned
        # by Ajax request to avoid some attacks
        my $token = $self->ott->createToken(
            {
                _totp2fSecret => $secret,
            }
        );

        my $portal = $self->conf->{portal};
        $portal =~ s#^https?://([^/:]+).*$#$1#;

        # QR-code will be generated by a javascript, here we just send data
        return $self->p->sendJSONresponse(
            $req,
            {
                secret => $secret,
                token  => $token,
                portal => $portal,
                user   => $user,
                newkey => $nk,
            }
        );
Yadd's avatar
Yadd committed
131 132 133 134
    }
}

1;