import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { IsActiveMatchOptions, Router, RouterModule } from "@angular/router"; import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AuthRequestLoginCredentials, AuthRequestServiceAbstraction, LoginEmailServiceAbstraction, LoginStrategyServiceAbstraction, LoginSuccessHandlerService, } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request"; import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, LinkModule, ToastService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service"; import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service"; enum Flow { StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated AdminAuthRequest, // when user clicks "Request admin approval" from /login-initiated } const matchOptions: IsActiveMatchOptions = { paths: "exact", queryParams: "ignored", fragment: "ignored", matrixParams: "ignored", }; @Component({ standalone: true, templateUrl: "./login-via-auth-request.component.html", imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule], providers: [{ provide: LoginViaAuthRequestCacheService }], }) export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private authRequest: AuthRequest | undefined = undefined; private authRequestKeyPair: | { publicKey: Uint8Array | undefined; privateKey: Uint8Array | undefined } | undefined = undefined; private authStatus: AuthenticationStatus | undefined = undefined; private showResendNotificationTimeoutSeconds = 12; protected backToRoute = "/login"; protected clientType: ClientType; protected ClientType = ClientType; protected email: string | undefined = undefined; protected fingerprintPhrase: string | undefined = undefined; protected showResendNotification = false; protected Flow = Flow; protected flow = Flow.StandardAuthRequest; protected webVaultUrl: string | undefined = undefined; protected deviceManagementUrl: string | undefined; constructor( private accountService: AccountService, private anonymousHubService: AnonymousHubService, private appIdService: AppIdService, private authRequestApiService: AuthRequestApiService, private authRequestService: AuthRequestServiceAbstraction, private authService: AuthService, private cryptoFunctionService: CryptoFunctionService, private deviceTrustService: DeviceTrustServiceAbstraction, private environmentService: EnvironmentService, private i18nService: I18nService, private logService: LogService, private loginEmailService: LoginEmailServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction, private passwordGenerationService: PasswordGenerationServiceAbstraction, private platformUtilsService: PlatformUtilsService, private router: Router, private toastService: ToastService, private validationService: ValidationService, private loginSuccessHandlerService: LoginSuccessHandlerService, private loginViaAuthRequestCacheService: LoginViaAuthRequestCacheService, private configService: ConfigService, ) { this.clientType = this.platformUtilsService.getClientType(); // Gets SignalR push notification // Only fires on approval to prevent enumeration this.authRequestService.authRequestPushNotification$ .pipe(takeUntilDestroyed()) .subscribe((requestId) => { this.verifyAndHandleApprovedAuthReq(requestId).catch((e: Error) => { this.toastService.showToast({ variant: "error", title: this.i18nService.t("error"), message: e.message, }); this.logService.error("Failed to use approved auth request: " + e.message); }); }); // Get the web vault URL from the environment service this.environmentService.environment$.pipe(takeUntilDestroyed()).subscribe((env) => { this.webVaultUrl = env.getWebVaultUrl(); this.deviceManagementUrl = `${this.webVaultUrl}/#/settings/security/device-management`; }); } async ngOnInit(): Promise { // Get the authStatus early because we use it in both flows this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$); await this.loginViaAuthRequestCacheService.init(); const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; if (userHasAuthenticatedViaSSO) { this.backToRoute = "/login-initiated"; } /** * The LoginViaAuthRequestComponent handles both the `login-with-device` and * the `admin-approval-requested` routes. Therefore, we check the route to determine * which flow to initialize. */ if (this.router.isActive("admin-approval-requested", matchOptions)) { await this.initAdminAuthRequestFlow(); } else { await this.initStandardAuthRequestFlow(); } } private async initAdminAuthRequestFlow(): Promise { this.flow = Flow.AdminAuthRequest; // Get email from state for admin auth requests because it is available and also // prevents it from being lost on refresh as the loginEmailService email does not persist. this.email = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), ); if (!this.email) { await this.handleMissingEmail(); return; } // We only allow a single admin approval request to be active at a time // so we must check state to see if we have an existing one or not const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (!userId) { this.logService.error( "Not able to get a user id from the account service active account observable.", ); return; } const existingAdminAuthRequest = await this.authRequestService.getAdminAuthRequest(userId); if (existingAdminAuthRequest) { await this.handleExistingAdminAuthRequest(existingAdminAuthRequest, userId); } else { await this.startAdminAuthRequestLogin(); } } private async initStandardAuthRequestFlow(): Promise { this.flow = Flow.StandardAuthRequest; this.email = (await firstValueFrom(this.loginEmailService.loginEmail$)) || undefined; if (!this.email) { await this.handleMissingEmail(); return; } await this.startStandardAuthRequestLogin(); } private async handleMissingEmail(): Promise { this.toastService.showToast({ variant: "error", message: this.i18nService.t("userEmailMissing"), }); await this.router.navigate([this.backToRoute]); } async ngOnDestroy(): Promise { await this.anonymousHubService.stopHubConnection(); this.loginViaAuthRequestCacheService.clearCacheLoginView(); } private async startAdminAuthRequestLogin(): Promise { try { await this.buildAuthRequest(AuthRequestType.AdminApproval); if (!this.authRequest) { this.logService.error("Auth request failed to build."); return; } if (!this.authRequestKeyPair) { this.logService.error("Key pairs failed to initialize from buildAuthRequest."); return; } const authRequestResponse = await this.authRequestApiService.postAdminAuthRequest( this.authRequest as AuthRequest, ); const adminAuthReqStorable = new AdminAuthRequestStorable({ id: authRequestResponse.id, privateKey: this.authRequestKeyPair.privateKey, }); const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (!userId) { this.logService.error( "Not able to get a user id from the account service active account observable.", ); return; } await this.authRequestService.setAdminAuthRequest(adminAuthReqStorable, userId); if (authRequestResponse.id) { await this.anonymousHubService.createHubConnection(authRequestResponse.id); } } catch (e) { this.logService.error(e); } } protected async startStandardAuthRequestLogin( clearCachedRequest: boolean = false, ): Promise { this.showResendNotification = false; if (await this.configService.getFeatureFlag(FeatureFlag.PM9112_DeviceApprovalPersistence)) { // Used for manually refreshing the auth request when clicking the resend auth request // on the ui. if (clearCachedRequest) { this.loginViaAuthRequestCacheService.clearCacheLoginView(); } try { const loginAuthRequestView: LoginViaAuthRequestView | null = this.loginViaAuthRequestCacheService.getCachedLoginViaAuthRequestView(); if (!loginAuthRequestView) { await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); // I tried several ways to get the IDE/linter to play nice with checking for null values // in less code / more efficiently, but it struggles to identify code paths that // are more complicated than this. if (!this.authRequest) { this.logService.error("AuthRequest failed to initialize from buildAuthRequest."); return; } if (!this.fingerprintPhrase) { this.logService.error("FingerprintPhrase failed to initialize from buildAuthRequest."); return; } if (!this.authRequestKeyPair) { this.logService.error("KeyPair failed to initialize from buildAuthRequest."); return; } const authRequestResponse: AuthRequestResponse = await this.authRequestApiService.postAuthRequest(this.authRequest); this.loginViaAuthRequestCacheService.cacheLoginView( this.authRequest, authRequestResponse, this.fingerprintPhrase, this.authRequestKeyPair, ); if (authRequestResponse.id) { await this.anonymousHubService.createHubConnection(authRequestResponse.id); } } else { // Grab the cached information and store it back in component state. // We don't need the public key for handling the authentication request because // the verifyAndHandleApprovedAuthReq function will receive the public key back // from the looked up auth request and all we need is to make sure that // we can use the cached private key that is associated with it. this.authRequest = loginAuthRequestView.authRequest; this.fingerprintPhrase = loginAuthRequestView.fingerprintPhrase; this.authRequestKeyPair = { privateKey: loginAuthRequestView.privateKey ? Utils.fromB64ToArray(loginAuthRequestView.privateKey) : undefined, publicKey: undefined, }; if (!loginAuthRequestView.authRequestResponse) { this.logService.error("No cached auth request response."); return; } if (loginAuthRequestView.authRequestResponse.id) { await this.anonymousHubService.createHubConnection( loginAuthRequestView.authRequestResponse.id, ); } } } catch (e) { this.logService.error(e); } } else { try { await this.buildAuthRequest(AuthRequestType.AuthenticateAndUnlock); if (!this.authRequest) { this.logService.error("No auth request found."); return; } const authRequestResponse = await this.authRequestApiService.postAuthRequest( this.authRequest, ); if (authRequestResponse.id) { await this.anonymousHubService.createHubConnection(authRequestResponse.id); } } catch (e) { this.logService.error(e); } } setTimeout(() => { this.showResendNotification = true; }, this.showResendNotificationTimeoutSeconds * 1000); } private async buildAuthRequest(authRequestType: AuthRequestType): Promise { const authRequestKeyPairArray = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); this.authRequestKeyPair = { publicKey: authRequestKeyPairArray[0], privateKey: authRequestKeyPairArray[1], }; const deviceIdentifier = await this.appIdService.getAppId(); if (!this.authRequestKeyPair.publicKey) { this.logService.error("AuthRequest public key not set to value in building auth request."); return; } const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair.publicKey); const accessCode = await this.passwordGenerationService.generatePassword({ type: "password", length: 25, }); if (!this.email) { this.logService.error("Email not defined when building auth request."); return; } this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( this.email, this.authRequestKeyPair.publicKey, ); this.authRequest = new AuthRequest( this.email, deviceIdentifier, publicKey, authRequestType, accessCode, ); } private async handleExistingAdminAuthRequest( adminAuthRequestStorable: AdminAuthRequestStorable, userId: UserId, ): Promise { // Note: on login, the SSOLoginStrategy will also call to see if an existing admin auth req // has been approved and handle it if so. // Regardless, we always retrieve the auth request from the server and verify and handle status changes here as well let adminAuthRequestResponse: AuthRequestResponse; try { adminAuthRequestResponse = await this.authRequestApiService.getAuthRequest( adminAuthRequestStorable.id, ); } catch (error) { if (error instanceof ErrorResponse && error.statusCode === HttpStatusCode.NotFound) { return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); } this.logService.error(error); return; } // Request doesn't exist anymore if (!adminAuthRequestResponse) { return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); } // Re-derive the user's fingerprint phrase // It is important to not use the server's public key here as it could have been compromised via MITM const derivedPublicKeyArrayBuffer = await this.cryptoFunctionService.rsaExtractPublicKey( adminAuthRequestStorable.privateKey, ); if (!this.email) { this.logService.error("Email not defined when handling an existing an admin auth request."); return; } this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( this.email, derivedPublicKeyArrayBuffer, ); // Request denied if (adminAuthRequestResponse.isAnswered && !adminAuthRequestResponse.requestApproved) { return await this.handleExistingAdminAuthReqDeletedOrDenied(userId); } // Request approved if (adminAuthRequestResponse.requestApproved) { return await this.decryptViaApprovedAuthRequest( adminAuthRequestResponse, adminAuthRequestStorable.privateKey, userId, ); } // Request still pending response from admin set keypair and create hub connection // so that any approvals will be received via push notification this.authRequestKeyPair = { privateKey: adminAuthRequestStorable.privateKey, publicKey: undefined, }; await this.anonymousHubService.createHubConnection(adminAuthRequestStorable.id); } private async verifyAndHandleApprovedAuthReq(requestId: string): Promise { /** * *********************************** * Standard Auth Request Flows * *********************************** * * Flow 1: Unauthed user requests approval from device; Approving device has a masterKey in memory. * * Unauthed user clicks "Login with device" > navigates to /login-with-device which creates a StandardAuthRequest * > receives approval from a device with authRequestPublicKey(masterKey) > decrypts masterKey > decrypts userKey > proceed to vault * * Flow 2: Unauthed user requests approval from device; Approving device does NOT have a masterKey in memory. * * Unauthed user clicks "Login with device" > navigates to /login-with-device which creates a StandardAuthRequest * > receives approval from a device with authRequestPublicKey(userKey) > decrypts userKey > proceeds to vault * * Note: this flow is an uncommon scenario and relates to TDE off-boarding. The following describes how a user could get into this flow: * 1) An SSO TD user logs into a device via an Admin auth request approval, therefore this device does NOT have a masterKey in memory. * 2) The org admin... * (2a) Changes the member decryption options from "Trusted devices" to "Master password" AND * (2b) Turns off the "Require single sign-on authentication" policy * 3) On another device, the user clicks "Login with device", which they can do because the org no longer requires SSO. * 4) The user approves from the device they had previously logged into with SSO TD, which does NOT have a masterKey in memory (see step 1 above). * * Flow 3: Authed SSO TD user requests approval from device; Approving device has a masterKey in memory. * * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Approve from your other device" * > navigates to /login-with-device which creates a StandardAuthRequest > receives approval from device with authRequestPublicKey(masterKey) * > decrypts masterKey > decrypts userKey > establishes trust (if required) > proceeds to vault * * Flow 4: Authed SSO TD user requests approval from device; Approving device does NOT have a masterKey in memory. * * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Approve from your other device" * > navigates to /login-with-device which creates a StandardAuthRequest > receives approval from device with authRequestPublicKey(userKey) * > decrypts userKey > establishes trust (if required) > proceeds to vault * * *********************************** * Admin Auth Request Flow * *********************************** * * Flow: Authed SSO TD user requests admin approval. * * SSO TD user authenticates via SSO > navigates to /login-initiated > clicks "Request admin approval" * > navigates to /admin-approval-requested which creates an AdminAuthRequest > receives approval from device with authRequestPublicKey(userKey) * > decrypts userKey > establishes trust (if required) > proceeds to vault * * Note: TDE users are required to be enrolled in admin password reset, which gives the admin access to the user's userKey. * This is how admins are able to send over the authRequestPublicKey(userKey) to the user to allow them to unlock. * * * Summary Table * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| * | Flow | Auth Status | Clicks Button [active route] | Navigates to | Approving device has masterKey in memory (see note 1) | * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| * | Standard Flow 1 | unauthed | "Login with device" [/login] | /login-with-device | yes | * | Standard Flow 2 | unauthed | "Login with device" [/login] | /login-with-device | no | * | Standard Flow 3 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | yes | * | Standard Flow 4 | authed | "Approve from your other device" [/login-initiated] | /login-with-device | no | * | Admin Flow | authed | "Request admin approval" [/login-initiated] | /admin-approval-requested | NA - admin requests always send encrypted userKey | * |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------| * * Note 1: The phrase "in memory" here is important. It is possible for a user to have a master password for their account, but not have a masterKey IN MEMORY for * a specific device. For example, if a user registers an account with a master password, then joins an SSO TD org, then logs in to a device via SSO and * admin auth request, they are now logged into that device but that device does not have masterKey IN MEMORY. */ try { const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; if (userHasAuthenticatedViaSSO) { // Get the auth request from the server // User is authenticated, therefore the endpoint does not require an access code. const authRequestResponse = await this.authRequestApiService.getAuthRequest(requestId); if (authRequestResponse.requestApproved) { // Handles Standard Flows 3-4 and Admin Flow await this.handleAuthenticatedFlows(authRequestResponse); } } else { if (!this.authRequest) { this.logService.error("No auth request defined when handling approved auth request."); return; } // Get the auth request from the server // User is unauthenticated, therefore the endpoint requires an access code for user verification. const authRequestResponse = await this.authRequestApiService.getAuthResponse( requestId, this.authRequest.accessCode, ); if (authRequestResponse.requestApproved) { // Handles Standard Flows 1-2 await this.handleUnauthenticatedFlows(authRequestResponse, requestId); } } } catch (error) { if (error instanceof ErrorResponse) { await this.router.navigate([this.backToRoute]); this.validationService.showError(error); return; } this.logService.error(error); } finally { // Manually clean out the cache to make sure sensitive // data does not persist longer than it needs to. this.loginViaAuthRequestCacheService.clearCacheLoginView(); } } private async handleAuthenticatedFlows(authRequestResponse: AuthRequestResponse) { const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; if (!userId) { this.logService.error( "Not able to get a user id from the account service active account observable.", ); return; } if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) { this.logService.error("No private key set when handling the authenticated flows."); return; } await this.decryptViaApprovedAuthRequest( authRequestResponse, this.authRequestKeyPair.privateKey, userId, ); } private async handleUnauthenticatedFlows( authRequestResponse: AuthRequestResponse, requestId: string, ) { const authRequestLoginCredentials = await this.buildAuthRequestLoginCredentials( requestId, authRequestResponse, ); if (!authRequestLoginCredentials) { this.logService.error("Didn't set up auth request login credentials properly."); return; } // Note: keys are set by AuthRequestLoginStrategy success handling const authResult = await this.loginStrategyService.logIn(authRequestLoginCredentials); await this.handlePostLoginNavigation(authResult); } private async decryptViaApprovedAuthRequest( authRequestResponse: AuthRequestResponse, privateKey: ArrayBuffer, userId: UserId, ): Promise { /** * See verifyAndHandleApprovedAuthReq() for flow details. * * We determine the type of `key` based on the presence or absence of `masterPasswordHash`: * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) */ if (authRequestResponse.masterPasswordHash) { // ...in Standard Auth Request Flow 3 await this.authRequestService.setKeysAfterDecryptingSharedMasterKeyAndHash( authRequestResponse, privateKey, userId, ); } else { // ...in Standard Auth Request Flow 4 or Admin Auth Request Flow await this.authRequestService.setUserKeyAfterDecryptingSharedUserKey( authRequestResponse, privateKey, userId, ); } // clear the admin auth request from state so it cannot be used again (it's a one time use) // TODO: this should eventually be enforced via deleting this on the server once it is used await this.authRequestService.clearAdminAuthRequest(userId); this.toastService.showToast({ variant: "success", message: this.i18nService.t("loginApproved"), }); // Now that we have a decrypted user key in memory, we can check if we // need to establish trust on the current device const activeAccount = await firstValueFrom(this.accountService.activeAccount$); if (!activeAccount) { this.logService.error("No active account defined from the account service."); return; } await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); await this.handleSuccessfulLoginNavigation(userId); } /** * Takes an `AuthRequestResponse` and decrypts the `key` to build an `AuthRequestLoginCredentials` * object for use in the `AuthRequestLoginStrategy`. * * The credentials object that gets built is affected by whether the `authRequestResponse.key` * is an encrypted MasterKey or an encrypted UserKey. */ private async buildAuthRequestLoginCredentials( requestId: string, authRequestResponse: AuthRequestResponse, ): Promise { if (!this.authRequestKeyPair || !this.authRequestKeyPair.privateKey) { this.logService.error("No private key set when building auth request login credentials."); return; } if (!this.email) { this.logService.error("Email not defined."); return; } if (!this.authRequest) { this.logService.error( "AuthRequest not defined when building auth request login credentials.", ); return; } /** * See verifyAndHandleApprovedAuthReq() for flow details. * * We determine the type of `key` based on the presence or absence of `masterPasswordHash`: * - If `masterPasswordHash` has a value, we receive the `key` as an authRequestPublicKey(masterKey) [plus we have authRequestPublicKey(masterPasswordHash)] * - If `masterPasswordHash` does not have a value, we receive the `key` as an authRequestPublicKey(userKey) */ if (authRequestResponse.masterPasswordHash) { // ...in Standard Auth Request Flow 1 const { masterKey, masterKeyHash } = await this.authRequestService.decryptPubKeyEncryptedMasterKeyAndHash( authRequestResponse.key, authRequestResponse.masterPasswordHash, this.authRequestKeyPair.privateKey, ); return new AuthRequestLoginCredentials( this.email, this.authRequest.accessCode, requestId, null, // no userKey masterKey, masterKeyHash, ); } else { // ...in Standard Auth Request Flow 2 const userKey = await this.authRequestService.decryptPubKeyEncryptedUserKey( authRequestResponse.key, this.authRequestKeyPair.privateKey, ); return new AuthRequestLoginCredentials( this.email, this.authRequest.accessCode, requestId, userKey, null, // no masterKey null, // no masterKeyHash ); } } private async handleExistingAdminAuthReqDeletedOrDenied(userId: UserId) { // clear the admin auth request from state await this.authRequestService.clearAdminAuthRequest(userId); // start new auth request await this.startAdminAuthRequestLogin(); } private async handlePostLoginNavigation(loginResponse: AuthResult) { if (loginResponse.requiresTwoFactor) { await this.router.navigate(["2fa"]); } else if (loginResponse.forcePasswordReset != ForceSetPasswordReason.None) { await this.router.navigate(["update-temp-password"]); } else { await this.handleSuccessfulLoginNavigation(loginResponse.userId); } } private async handleSuccessfulLoginNavigation(userId: UserId) { if (this.flow === Flow.StandardAuthRequest) { // Only need to set remembered email on standard login with auth req flow await this.loginEmailService.saveEmailSettings(); } await this.loginSuccessHandlerService.run(userId); await this.router.navigate(["vault"]); } }