import { HttpRequest } from '@angular/common/http' ; import { Injectable , OnDestroy } from '@angular/core' ; import { Router } from '@angular/router' ; import { Subject } from 'rxjs' ; import { BehaviorSubject , EMPTY , Observable , of , Subscription } from 'rxjs' ; import { last } from 'rxjs/operators' ; import { catchError , map , switchMap , take , throttleTime } from 'rxjs/operators' ; import { ApiService } from '../../../../common/api/api.service' ; import { FormToastService } from '../../../../common/services/form-toast.service' ; import { MindsUser } from '../../../../interfaces/entities' ; /**
 * Credentials payload
 */ export type CredentialsPayload = { username : string ; password : string ; } ; /**
 * Response from authenticate endpoint
 */ export type AuthResponse = { status : string ; user : MindsUser ; } ; /**
 * Object containing data needed to construct payload.
 */ export type MFAPayload = { smsSecretKey ? : string ; emailSecretKey ? : string ; code ? : string ; } ; /**
 * Modal entry-points.
 */ export type MultiFactorRootPanel = 'sms' | 'totp' | 'email' ; /**
 * Different panels that can be displayed.
 */ export type MultiFactorPanel = MultiFactorRootPanel | 'totp-code' | 'totp-recovery' | '' ; /**
 * Service for Multi-factor authentication.
 * Handles shared state.
 */ @ Injectable ( { providedIn : 'root' } ) export class MultiFactorAuthService { /**
     * Currently active panel.
     */ public readonly activePanel$ : BehaviorSubject < MultiFactorPanel > = new BehaviorSubject < MultiFactorPanel > ( 'totp' ) ; /**
     * Fired on success.
     */ public readonly inProgress$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( null ) ; /**
     * This is the original request that triggered the modal
     * Mainly used for recovery reset as we need the users credentials
     */ public readonly originalRequest$ : BehaviorSubject < HttpRequest < any > > = new BehaviorSubject < HttpRequest < any > > ( null ) ; /**
     * The type of 2fa being used
     * This determines how the payload is build
     */ public readonly mfaType$ : BehaviorSubject < MultiFactorRootPanel > = new BehaviorSubject < MultiFactorRootPanel > ( null ) ; /**
     * Email and SMS contain a a secret key with the request
     * Devices do not so should set to null
     */ public readonly mfaSecretKey$ : BehaviorSubject < string | null > = new BehaviorSubject < string | null > ( null ) ; /**
     * MFA Request payload for new request (interceptor will merge)
     */ public readonly mfaPayload$ : Subject < MFAPayload > = new Subject < MFAPayload > ( ) ; constructor ( private toast , private api , private router ) { } param { string } code returns { void } public completeMultiFactor ( code ? ) { const payload = { code , } ; if ( this . mfaType$ . getValue ( ) === 'sms' ) { payload . smsSecretKey = this . mfaSecretKey$ . getValue ( ) ; } else if ( this . mfaType$ . getValue ( ) === 'email' ) { payload . emailSecretKey = this . mfaSecretKey$ . getValue ( ) ; } this . inProgress$ . next ( true ) ; this . mfaPayload$ . next ( payload ) ; } param { string } code returns { void } public async validateRecoveryCode ( code ) { this . inProgress$ . next ( true ) ; const req = this . originalRequest$ . getValue ( ) ; const response = await this . api . post ( 'api/v3/security/totp/recovery' , { ... JSON . parse ( req . body ) , recovery_code : code , } ) . toPromise ( ) ; this . inProgress$ . next ( false ) ; if ( ! response ) { return ; } if ( ! response . matches ) { this . toast . error ( 'Invalid recovery code' ) ; return ; } this . toast . success ( 'Successfully recovered MFA. Please login again.' ) ; this . router . navigate ( [ '/' ] ) ; } }