import { Directive , OnDestroy , OnInit } from "@angular/core" ; import { ActivatedRoute , Router } from "@angular/router" ; import { first } from "rxjs/operators" ; import { TwoFactorProviderType } from "jslib-common/enums/twoFactorProviderType" ; import { TwoFactorEmailRequest } from "jslib-common/models/request/twoFactorEmailRequest" ; import { AuthResult } from "jslib-common/models/domain/authResult" ; import { ApiService } from "jslib-common/abstractions/api.service" ; import { AuthService } from "jslib-common/abstractions/auth.service" ; import { EnvironmentService } from "jslib-common/abstractions/environment.service" ; import { I18nService } from "jslib-common/abstractions/i18n.service" ; import { LogService } from "jslib-common/abstractions/log.service" ; import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service" ; import { StateService } from "jslib-common/abstractions/state.service" ; import { TwoFactorProviders } from "jslib-common/services/auth.service" ; import * as DuoWebSDK from "duo_web_sdk" ; import { WebAuthnIFrame } from "jslib-common/misc/webauthn_iframe" ; @ Directive ( ) export class TwoFactorComponent implements OnInit , OnDestroy { token : string = "" ; remember : boolean = false ; webAuthnReady : boolean = false ; webAuthnNewTab : boolean = false ; providers = TwoFactorProviders ; providerType = TwoFactorProviderType ; selectedProviderType : TwoFactorProviderType = TwoFactorProviderType . Authenticator ; webAuthnSupported : boolean = false ; webAuthn : WebAuthnIFrame = null ; title : string = "" ; twoFactorEmail : string = null ; formPromise : Promise < any > ; emailPromise : Promise < any > ; identifier : string = null ; onSuccessfulLogin : ( ) => Promise < any > ; onSuccessfulLoginNavigate : ( ) => Promise < any > ; get webAuthnAllow ( ) : string { return `publickey-credentials-get ${ this . environmentService . getWebVaultUrl ( ) }` ; } protected loginRoute = "login" ; protected successRoute = "vault" ; constructor ( protected authService , protected router , protected i18nService , protected apiService , protected platformUtilsService , protected win , protected environmentService , protected stateService , protected route , protected logService ) { this . webAuthnSupported = this . platformUtilsService . supportsWebAuthn ( win ) ; } async ngOnInit ( ) { if ( ! this . authing || this . authService . twoFactorProvidersData == null ) { this . router . navigate ( [ this . loginRoute ] ) ; return ; } this . route . queryParams . pipe ( first ( ) ) . subscribe ( ( qParams ) => { if ( qParams . identifier != null ) { this . identifier = qParams . identifier ; } } ) ; if ( this . needsLock ) { this . successRoute = "lock" ; } if ( this . win != null && this . webAuthnSupported ) { const webVaultUrl = this . environmentService . getWebVaultUrl ( ) ; this . webAuthn = new WebAuthnIFrame ( this . win , webVaultUrl , this . webAuthnNewTab , this . platformUtilsService , this . i18nService , ( token ) => { this . token = token ; this . submit ( ) ; } , ( error ) => { this . platformUtilsService . showToast ( "error" , this . i18nService . t ( "errorOccurred" ) , error ) ; } , ( info ) => { if ( info === "ready" ) { this . webAuthnReady = true ; } } ) ; } this . selectedProviderType = this . authService . getDefaultTwoFactorProvider ( this . webAuthnSupported ) ; await this . init ( ) ; } ngOnDestroy ( ) { this . cleanupWebAuthn ( ) ; this . webAuthn = null ; } async init ( ) { if ( this . selectedProviderType == null ) { this . title = this . i18nService . t ( "loginUnavailable" ) ; return ; } this . cleanupWebAuthn ( ) ; this . title = ( TwoFactorProviders as any ) [ this . selectedProviderType ] . name ; const providerData = this . authService . twoFactorProvidersData . get ( this . selectedProviderType ) ; switch ( this . selectedProviderType ) { case TwoFactorProviderType . WebAuthn : if ( ! this . webAuthnNewTab ) { setTimeout ( ( ) => { this . authWebAuthn ( ) ; } , 500 ) ; } break ; case TwoFactorProviderType . Duo : case TwoFactorProviderType . OrganizationDuo : setTimeout ( ( ) => { DuoWebSDK . init ( { iframe : undefined , host : providerData . Host , sig_request : providerData . Signature , submit_callback : async ( f ) => { const sig = f . querySelector ( 'input[name="sig_response"]' ) as HTMLInputElement ; if ( sig != null ) { this . token = sig . value ; await this . submit ( ) ; } } , } ) ; } , 0 ) ; break ; case TwoFactorProviderType . Email : this . twoFactorEmail = providerData . Email ; if ( this . authService . twoFactorProvidersData . size > 1 ) { await this . sendEmail ( false ) ; } break ; default : break ; } } async submit ( ) { if ( this . token == null || this . token === "" ) { this . platformUtilsService . showToast ( "error" , this . i18nService . t ( "errorOccurred" ) , this . i18nService . t ( "verificationCodeRequired" ) ) ; return ; } if ( this . selectedProviderType === TwoFactorProviderType . WebAuthn ) { if ( this . webAuthn != null ) { this . webAuthn . stop ( ) ; } else { return ; } } else if ( this . selectedProviderType === TwoFactorProviderType . Email || this . selectedProviderType === TwoFactorProviderType . Authenticator ) { this . token = this . token . replace ( " " , "" ) . trim ( ) ; } try { await this . doSubmit ( ) ; } catch { if ( this . selectedProviderType === TwoFactorProviderType . WebAuthn && this . webAuthn != null ) { this . webAuthn . start ( ) ; } } } async doSubmit ( ) { this . formPromise = this . authService . logInTwoFactor ( this . selectedProviderType , this . token , this . remember ) ; const response = await this . formPromise ; const disableFavicon = await this . stateService . getDisableFavicon ( ) ; await this . stateService . setDisableFavicon ( ! ! disableFavicon ) ; if ( this . onSuccessfulLogin != null ) { this . onSuccessfulLogin ( ) ; } if ( response . resetMasterPassword ) { this . successRoute = "set-password" ; } if ( response . forcePasswordReset ) { this . successRoute = "update-temp-password" ; } if ( this . onSuccessfulLoginNavigate != null ) { this . onSuccessfulLoginNavigate ( ) ; } else { this . router . navigate ( [ this . successRoute ] , { queryParams : { identifier : this . identifier , } , } ) ; } } async sendEmail ( doToast ) { if ( this . selectedProviderType !== TwoFactorProviderType . Email ) { return ; } if ( this . emailPromise != null ) { return ; } try { const request = new TwoFactorEmailRequest ( ) ; request . email = this . authService . email ; request . masterPasswordHash = this . authService . masterPasswordHash ; this . emailPromise = this . apiService . postTwoFactorEmail ( request ) ; await this . emailPromise ; if ( doToast ) { this . platformUtilsService . showToast ( "success" , null , this . i18nService . t ( "verificationCodeEmailSent" , this . twoFactorEmail ) ) ; } } catch ( e ) { this . logService . error ( e ) ; } this . emailPromise = null ; } authWebAuthn ( ) { const providerData = this . authService . twoFactorProvidersData . get ( this . selectedProviderType ) ; if ( ! this . webAuthnSupported || this . webAuthn == null ) { return ; } this . webAuthn . init ( providerData ) ; } private cleanupWebAuthn ( ) { if ( this . webAuthn != null ) { this . webAuthn . stop ( ) ; this . webAuthn . cleanup ( ) ; } } get authing ( ) : boolean { return ( this . authService . authingWithPassword ( ) || this . authService . authingWithSso ( ) || this . authService . authingWithApiKey ( ) ) ; } get needsLock ( ) : boolean { return this . authService . authingWithSso ( ) || this . authService . authingWithApiKey ( ) ; } }