import { firstValueFrom, Observable, map, BehaviorSubject } from "rxjs"; import { Jsonify } from "type-fest"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { PasswordTokenRequest } from "@bitwarden/common/auth/models/request/identity-token/password-token.request"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { UserId } from "@bitwarden/common/types/guid"; import { AuthRequestLoginCredentials } from "../models/domain/login-credentials"; import { CacheData } from "../services/login-strategies/login-strategy.state"; import { LoginStrategy, LoginStrategyData } from "./login.strategy"; export class AuthRequestLoginStrategyData implements LoginStrategyData { tokenRequest: PasswordTokenRequest; captchaBypassToken: string; authRequestCredentials: AuthRequestLoginCredentials; static fromJSON(obj: Jsonify): AuthRequestLoginStrategyData { const data = Object.assign(new AuthRequestLoginStrategyData(), obj, { tokenRequest: PasswordTokenRequest.fromJSON(obj.tokenRequest), authRequestCredentials: AuthRequestLoginCredentials.fromJSON(obj.authRequestCredentials), }); return data; } } export class AuthRequestLoginStrategy extends LoginStrategy { email$: Observable; accessCode$: Observable; authRequestId$: Observable; protected cache: BehaviorSubject; constructor( data: AuthRequestLoginStrategyData, private deviceTrustService: DeviceTrustServiceAbstraction, ...sharedDeps: ConstructorParameters ) { super(...sharedDeps); this.cache = new BehaviorSubject(data); this.email$ = this.cache.pipe(map((data) => data.tokenRequest.email)); this.accessCode$ = this.cache.pipe(map((data) => data.authRequestCredentials.accessCode)); this.authRequestId$ = this.cache.pipe(map((data) => data.authRequestCredentials.authRequestId)); } override async logIn(credentials: AuthRequestLoginCredentials) { const data = new AuthRequestLoginStrategyData(); data.tokenRequest = new PasswordTokenRequest( credentials.email, credentials.accessCode, null, await this.buildTwoFactor(credentials.twoFactor, credentials.email), await this.buildDeviceRequest(), ); data.tokenRequest.setAuthRequestAccessCode(credentials.authRequestId); data.authRequestCredentials = credentials; this.cache.next(data); const [authResult] = await this.startLogIn(); return authResult; } override async logInTwoFactor( twoFactor: TokenTwoFactorRequest, captchaResponse: string, ): Promise { const data = this.cache.value; data.tokenRequest.captchaResponse = captchaResponse ?? data.captchaBypassToken; this.cache.next(data); return super.logInTwoFactor(twoFactor); } protected override async setMasterKey(response: IdentityTokenResponse, userId: UserId) { const authRequestCredentials = this.cache.value.authRequestCredentials; if ( authRequestCredentials.decryptedMasterKey && authRequestCredentials.decryptedMasterKeyHash ) { await this.masterPasswordService.setMasterKey( authRequestCredentials.decryptedMasterKey, userId, ); await this.masterPasswordService.setMasterKeyHash( authRequestCredentials.decryptedMasterKeyHash, userId, ); } } protected override async setUserKey( response: IdentityTokenResponse, userId: UserId, ): Promise { const authRequestCredentials = this.cache.value.authRequestCredentials; // User now may or may not have a master password // but set the master key encrypted user key if it exists regardless await this.cryptoService.setMasterKeyEncryptedUserKey(response.key); if (authRequestCredentials.decryptedUserKey) { await this.cryptoService.setUserKey(authRequestCredentials.decryptedUserKey, userId); } else { await this.trySetUserKeyWithMasterKey(userId); // Establish trust if required after setting user key await this.deviceTrustService.trustDeviceIfRequired(userId); } } private async trySetUserKeyWithMasterKey(userId: UserId): Promise { const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); if (masterKey) { const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(masterKey); await this.cryptoService.setUserKey(userKey, userId); } } protected override async setPrivateKey( response: IdentityTokenResponse, userId: UserId, ): Promise { await this.cryptoService.setPrivateKey( response.privateKey ?? (await this.createKeyPairForOldAccount(userId)), userId, ); } exportCache(): CacheData { return { authRequest: this.cache.value, }; } }