import { Injectable , OnDestroy } from '@angular/core' ; import { NavigationEnd , Router } from '@angular/router' ; import { BehaviorSubject , Observable , of , Subscription } from 'rxjs' ; import { catchError , map , skip , take , throttleTime } from 'rxjs/operators' ; import { ApiService } from '../../../../common/api/api.service' ; import { FormToastService } from '../../../../common/services/form-toast.service' ; import { Session } from '../../../../services/session' ; import { SettingsV2Service } from '../../settings-v2.service' ; /**
 * Panel object - includes an intent for optional digestion within subscribers.
 */ export type TwoFactorSetupPanel = { panel : TwoFactorSetupPanelName ; intent ? : TwoFactorSetupIntent ; } ; /**
 * Panel identifiers.
 */ export type TwoFactorSetupPanelName = 'root' | 'password' | 'recovery-code' | 'app-connect' | 'disable' | 'sms' ; /**
 * Different valid intents.
 */ export type TwoFactorSetupIntent = 'setup-app' | 'disable' | 'sms' | 'enabled-sms' | 'enabled-totp' | 'disabled-sms' | 'disabled-totp' ; export const DEFAULT_TWO_FACTOR_START_PANEL = { panel : 'root' , } ; export type TwoFactorProtectionType = 'totp' | 'sms' | null ; /**
 * Two-factor authentication v2 service.
 * Holds shared state between 2fav2 components and is responsible for calls to server.
 */ @ Injectable ( { providedIn : 'root' } ) export class SettingsTwoFactorV2Service implements OnDestroy { protected subscriptions : Subscription [ ] = [ ] ; public readonly activePanel$ : BehaviorSubject < TwoFactorSetupPanel > = new BehaviorSubject < TwoFactorSetupPanel > ( DEFAULT_TWO_FACTOR_START_PANEL ) ; public readonly passwordConfirmed$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; public readonly secret$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; public readonly inProgress$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( true ) ; public readonly recoveryCode$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; constructor ( private toast , private api , private settings , private session , router ) { this . subscriptions . push ( router . events . pipe ( skip ( 1 ) ) . subscribe ( val => { if ( val instanceof NavigationEnd ) { this . reset ( ) ; } } ) ) ; this . reloadSettings ( ) ; } param { Observable < boolean > } get totpEnabled$ ( ) : Observable < boolean > { return this . settings . settings$ . pipe ( map ( settings => { return settings ?. has2fa ?. totp ; } ) ) ; } param { Observable < boolean > } get smsEnabled$ ( ) : Observable < boolean > { return this . settings . settings$ . pipe ( map ( settings => { return settings ?. has2fa ?. sms ; } ) ) ; } ngOnDestroy ( ) { this . reset ( ) ; } returns { void } public reset ( ) { this . inProgress$ . next ( true ) ; for ( let subscription of this . subscriptions ) { subscription . unsubscribe ( ) ; } this . recoveryCode$ . next ( '' ) ; this . secret$ . next ( '' ) ; this . activePanel$ . next ( DEFAULT_TWO_FACTOR_START_PANEL ) ; this . passwordConfirmed$ . next ( false ) ; this . inProgress$ . next ( false ) ; } returns { void } public fetchNewSecret ( ) { this . inProgress$ . next ( true ) ; this . subscriptions . push ( this . api . get ( 'api/v3/security/totp/new' ) . pipe ( take ( 1 ) , throttleTime ( 2000 ) , catchError ( error => { console . error ( error ) ; this . toast . error ( error . error ?. message ?? 'An unexpected error has occurred' ) ; this . inProgress$ . next ( false ) ; this . activePanel$ . next ( { panel : 'root' } ) ; return of ( null ) ; } ) ) . subscribe ( val => { this . inProgress$ . next ( false ) ; if ( ! val || val . status === 'error' ) { return ; } if ( val && val . secret ) { this . secret$ . next ( val . secret ) ; } } ) ) ; } param { string } code public submitCode ( code ) { this . inProgress$ . next ( true ) ; this . subscriptions . push ( this . api . post ( 'api/v3/security/totp/new' , { code : code , secret : this . secret$ . getValue ( ) , } ) . pipe ( take ( 1 ) , throttleTime ( 2000 ) , catchError ( error => { this . toast . error ( error . error . message ?? 'An unexpected error has occurred' ) ; this . inProgress$ . next ( false ) ; return of ( null ) ; } ) ) . subscribe ( val => { this . inProgress$ . next ( false ) ; if ( ! val || val . status === 'error' ) { return ; } this . recoveryCode$ . next ( val . recovery_code ) ; this . toast . success ( 'Two-factor authentication enabled' ) ; this . activePanel$ . next ( { panel : 'recovery-code' , } ) ; } ) ) ; } public removeTotp ( code ) { this . inProgress$ . next ( true ) ; this . subscriptions . push ( this . api . delete ( 'api/v3/security/totp' , { } , { } , { code : code } ) . pipe ( take ( 1 ) , throttleTime ( 2000 ) , catchError ( error => { console . error ( error ) ; this . toast . error ( error . error . message ?? 'An unexpected error has occurred' ) ; this . inProgress$ . next ( false ) ; return of ( null ) ; } ) ) . subscribe ( val => { this . inProgress$ . next ( false ) ; if ( ! val || val . status === 'error' ) { return ; } this . reset ( ) ; this . reloadSettings ( ) ; this . activePanel$ . next ( { panel : 'root' , intent : 'disabled-totp' } ) ; } ) ) ; } returns { void } public async reloadSettings ( ) { this . inProgress$ . next ( true ) ; await this . settings . loadSettings ( this . session . getLoggedInUser ( ) . guid ) ; this . inProgress$ . next ( false ) ; } }