package Lemonldap::NG::Portal::2F::UTOTP;

use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
);

our $VERSION = '2.0.8';

extends 'Lemonldap::NG::Portal::Main::SecondFactor';

# INITIALIZATION

has prefix => ( is => 'ro', default => 'utotp' );
has logo   => ( is => 'rw', default => 'utotp.png' );
has u2f    => ( is => 'rw' );
has totp   => ( is => 'rw' );

use Lemonldap::NG::Portal::Main::Constants qw(
  PE_OK
  PE_ERROR
  PE_FORMEMPTY
  PE_SENDRESPONSE
);

sub init {
    my ($self) = @_;
    if ( (
               $self->conf->{totp2fSelfRegistration}
            or $self->conf->{u2fSelfRegistration}
        )
        and $self->conf->{utotp2fActivation} eq '1'
      )
    {
        $self->conf->{utotp2fActivation} =
          '$_2fDevices && $_2fDevices =~ /"type":\s*"(?:TOTP|U2F)"/s';
    }
    foreach (qw(U2F TOTP)) {

        # Arg "noRoute" is set for sub 2F modules to avoid enabling direct
        # REST routes
        unless ( $self->{ lc($_) } =
                $self->p->loadModule( "::2F::$_", undef, noRoute => 1 )
            and $self->{ lc($_) }->init )
        {
            $self->error("Unable to load ::2F::$_");
            return 0;
        }
    }
    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("UTOTP: checkLogins set") if $checkLogins;

    my $stayconnected = $req->param('stayconnected');
    $self->logger->debug("UTOTP: stayconnected set") if $stayconnected;

    my %tplPrms = (
        MAIN_LOGO     => $self->conf->{portalMainLogo},
        SKIN          => $self->p->getSkin($req),
        TOKEN         => $token,
        CHECKLOGINS   => $checkLogins,
        STAYCONNECTED => $stayconnected
    );

    if ( my $res = $self->u2f->loadUser( $req, $req->sessionInfo ) ) {
        if ( $res > 0 ) {
            $self->logger->debug('U2F key is registered');

            # Get a challenge (from first key)
            my $data = eval {
                from_json(
                    $req->data->{crypter}->[0]->authenticationChallenge );
            };

            if ($@) {
                $self->logger->error( Crypt::U2F::Server::u2fclib_getError() );
                return PE_ERROR;
            }

            # Get registered keys
            my @rk =
              map {
                { keyHandle => $_->{keyHandle}, version => $data->{version} }
              } @{ $req->data->{crypter} };

            $self->ott->updateToken( $token, __ch => $data->{challenge} );

            # Serialize data
            $data = to_json( {
                    challenge      => $data->{challenge},
                    appId          => $data->{appId},
                    registeredKeys => \@rk,
                }
            );
            $tplPrms{DATA} = $data;
        }
    }

    # Prepare form
    my $tmp = $self->p->sendHtml( $req, 'utotp2fcheck', params => \%tplPrms, );
    $self->logger->debug("Prepare U2F-or-TOTP 2F verification");

    $req->response($tmp);
    return PE_SENDRESPONSE;
}

sub verify {
    my ( $self, $req, $session ) = @_;
    my ($r1);
    if ( $req->param('signature') ) {
        $self->logger->debug('UTOTP: U2F response detected');
        my $r1 = $self->u2f->verify( $req, $session );
        return PE_OK if ( $r1 == PE_OK );
    }
    if ( $req->param('code') ) {
        $self->logger->debug('UTOTP: TOTP response detected');
        return $self->totp->verify( $req, $session );
    }
    return ( $r1 ? $r1 : PE_FORMEMPTY );
}

1;
