package AppUser::Login::Command::MFAAuth;

use strict;
use warnings;
use URI::Escape;

use lib '/app/tools/appuser/lib';

use AppUser::Login::MFAAuthForm;

use AppUser::DB::Item::UserToken;

# this is a special command, there's no check for session or permission
# so we'll just derive from the basic RSApache::Command base class and define
# our "app" while other "user" Commands will derive from AppUser::Command
#
use lib '/app/tools/common/lib';

use RSApache::Response::XSLT;
use Common::RSApp;
use RSApache::RSWebApp;
use RSApache::Command;

use base 'RSApache::Command';

sub cmd  { return "auth"; }
sub area { return "login"; }
sub app  { return "rps"; }

use constant MFA_EXPIRY       => '+30d'; # 30 day expiration
use constant MFA_USER_COOKIE  => 'RS_MFA';
use constant CURRENT_DOMAIN   => '.royaltyshare.com';

use constant kTemplate => '/app/tools/appuser/templates/mfa_auth.xsl';

sub execute {
    my ($self) = @_;

    my $response;
    my $redirect = uri_unescape( $self->getParam("redirect") );
    my $session  = RSApache::RSWebApp::GetSession();
    my $cmd      = $self->getParam("c");
    my $token    = $self->getParam("token");
    my $phone    = $self->getParam("phone");
    my $qr       = $self->getParam("qr");  # uri path to qr image
    my $save     = $self->getParam("save"); # used by authc flow

    my $remember = $self->getParam("remember");

    my $form = AppUser::Login::MFAAuthForm->new();

    my $mfaType;
    my $mfaVerified;
    my $email;

    if ( $self->getParam("email") ) {
        $email = uri_unescape( $self->getParam("email") );
        my $userObj = AppUser::DB::Item::User->Lookup( email => $email );
        if ( defined $userObj && $userObj->user_id ) {
            $mfaType     = $userObj->mfa_type;  # default MFA type
            $mfaVerified = $userObj->mfa_verified;
        }
    }

    if ( $self->getParam("mtype") ) {
        $mfaType = $self->getParam("mtype"); # use this instead of default
    }

    if ( $self->getParam('submit') ) {

        # This section handles the auth / authc / submit commands
        # Typically, for the first time user the flow is to use
        # 'auth' to validate the login code (text or authenticator app; see MFAAuthForm validate),
        # then redirect to the 'success' screen.  When the user clicks 'Next' on the success screen
        # they end up here to do the actual login.  The login is accepted using LoginForm acceptLogin.
        # Subsequent logins don't show the success screen but still need to validate before
        # logging in; this is done via thee 'authc' command.
        #

        if ( $form->assignCGIParams( 'Form', $self->getParams() ) ) {

            $mfaType = $form->MFAType();
            $email   = $form->Email();
            $token   = $form->Token() if (!$token);

            my $okToLogin=0;

            if ( $cmd eq 'success' ) {
                $okToLogin = 1;
            }

            if ( $cmd eq 'auth' || $cmd eq 'authc' ) {
                if( defined $save && $save == 1 ) {
                    my $userObj = AppUser::DB::Item::User->Lookup( email => $email );
                    if ( defined $userObj && $userObj->user_id ) {
                        $userObj->mfa_type( AppUser::DB::Item::User::kMFATypeAuthenticator );
                        $userObj->save;
                    }
                }
                $save=0;

                $form->Token(1) if ( $session->IsValid() );  # manually set login token if we're already logged in

                if ( $form->validate() ) { # is auth code valid?
                    $self->_markUserAsVerified($email); # mark user as verified

                    if ( $remember ) {
                        # Remember MFA verification for this device.  This is done by setting a time-limited
                        # cookie in the user's browser if the cookie doesn't already exist.  This will allow
                        # the user to bypass MFA authentication while the cookie is active.  Once the cookie
                        # expires then the user will have to do MFA authentication again.
                        # See AppUser::Login::Command::Login for more information.
                        #
                        $self->SetMFACookie($email) if ( !$self->GetMFACookie($email) );
                    }

                    if ( $cmd eq 'auth' ) {

                        # redirect to the 'success' screen.
                        my $mfaType = AppUser::DB::Item::User::kMFATypeSMS;
                        my $_email  = uri_escape( $email );
                        my $_phone  = uri_escape( $phone );
                        my $uri     = "/rps/login?c=success&email=$_email&token=$token&mtype=$mfaType&phone=$_phone";
                        $response = RSApache::Response::Redirect->new($uri);
                    } else {
                        $okToLogin = 1;
                    }
                } else {
                    $form->setError(Common::FormObject::kErrMFACodeInvalid);
                    $self->addMessageXML( type => "error", code => Common::FormObject::kErrMFACodeInvalid );
                }
            }

            if ( $okToLogin ) {
                # We'll use LoginForm->acceptLogin to handle the actual login.
                #
                my $form = AppUser::Login::LoginForm->new( redirect => $redirect );


                # Note: In order for assignCGIParams to work with the LoginForm, we need to manually
                # add the MFAType and Token properties to the LoginForm so that everything copies over
                # nicely from getParams.
                #
                $form->{MFAType}  = Common::FormObject::Scalar::String->new( value => $mfaType );
                $form->{Token}    = Common::FormObject::Scalar::String->new( value => 'xxx' );

                my $masterUserID = Common::RSApp::GetMasterUserID();
                my $userObj;
                if ($masterUserID) {
                    $userObj = AppUser::DB::Item::User->Lookup( user_id => $masterUserID );
                    if ( defined $userObj && $userObj->email ) {
                        $email = $userObj->email;
                        $form->{Email} = $email;
                    }
                }

                $form->{ClientID} = Common::RSApp::GetClientID();

                if ( $form->assignCGIParams( 'Form', $self->getParams() ) ) {

                    # accept the login (see note below)
                    $response = $form->acceptLogin("?token=$token");

                    # Note:
                    # The behavior of acceptLogin falls into three categories:
                    # a) We're logging into a specific client site;
                    # b) We're logging into login.royaltyshare.com and the user is associated
                    #    with multiple clients.
                    # c) We're logging into login.royaltyshare.com and the user is associated
                    #    with only one client.
                    #
                    # In case 'a', acceptLogin will redirect the user to the default URI for the
                    # client (effectively putting the user at their landing page).
                    #
                    # In case 'b', acceptLogin will redirect the user to the 'select a client' screen.
                    # Once a client is selected they'll be sent to /rps/login to validate and accept
                    # the login to the selected client site.
                    #
                    # In case 'c', acceptLogin will redirect the user the site they have access to.
                    #
                    # In cases 'a' and 'c', the user login token will be cleared since its no longer needed.
                    # In all cases, a session will be created for the user.
                    # See AppUser/Login/LoginForm.pm for more information.


                    # Delete the QR image (if any; these can be regenerated as needed)
                    Common::MFA::Util->DeleteQRFile($qr) if ( $qr && $qr ne '' );

                }
            }# okToLogin
        } # assignCGIParams
    } # submit


    if ( !$response ) {
        $self->{xml}{Form}          = $form;
        $self->{xml}{MFAType}       = $mfaType;
        $self->{xml}{MFAVerified}   = $mfaVerified;
        $self->{xml}{Email}         = uri_escape( $email );
        $self->{xml}{EmailPlain}    = $email;
        $self->{xml}{Phone}         = $phone;
        $self->{xml}{Params}{token} = $token;
        $self->{xml}{Params}{phone} = $phone;
        $self->{xml}{QRPath}        = $qr;
        $self->{xml}{Save}          = $save if ( $save );

        $response = RSApache::Response::XSLT->new( $self->{xml}, kTemplate );
    }

    return $response;
}

sub _markUserAsVerified {
    my($self,$email) = @_;

    my $userObj = AppUser::DB::Item::User->Lookup( email => $email );
    if ( $userObj ) {
        $userObj->mfa_verified(1);
        $userObj->save;
    }
}

sub _mfaCookie {
    my $self    = shift;
    my $email   = shift;
    my $userObj = AppUser::DB::Item::User->Lookup( email => $email );
    my $userID  = ($userObj) ? $userObj->user_id : 0;
    return MFA_USER_COOKIE . '_' . $userID;
}

sub SetMFACookie {
    my $self   = shift;
    my $email  = shift;
    my $cookie = CGI::Cookie->new(
        -name    => $self->_mfaCookie($email),
        -value   => '1',
        -domain  => CURRENT_DOMAIN,
        -expires => MFA_EXPIRY,
    );

    my $r = $ENV{MOD_PERL_API_VERSION} >= 2
        ? Apache2::RequestUtil->request
        : Apache->request();
    $r->headers_out->add( "Set-Cookie" => $cookie );
}

sub DeleteMFACookie {
    my $self   = shift;
    my $userID = shift;
    my $cookie = CGI::Cookie->new(
        #-name    => $self->_mfaCookie($email),
        -name    => MFA_USER_COOKIE . '_' . $userID,
        -value   => '',
        -domain  => CURRENT_DOMAIN,
        -expires => '-1d',
    );

    my $r = $ENV{MOD_PERL_API_VERSION} >= 2
        ? Apache2::RequestUtil->request
        : Apache->request();
    $r->header_out( "Set-Cookie" => $cookie );
}

sub GetMFACookie {
    my $self    = shift;
    my $email   = shift;
    my %cookies = CGI::Cookie->fetch;
    return $cookies{ $self->_mfaCookie($email) };
};

1;
