1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00
Files
browser/libs/auth/src/common/login-strategies/auth-request-login.strategy.ts
Todd Martin 7b508b1ad7 [PM-8933] Require userId on setUserKey (#9675)
* Updated all sets of user key to pass in userId

* Added userId on auth request login.

* Fixed tests.

* Fixed tests to pass in UserId

* Added parameter to tests.

* Addressed PR feedback.

* Merged main
2024-08-13 08:07:36 -04:00

138 lines
5.1 KiB
TypeScript

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>): 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<string>;
accessCode$: Observable<string>;
authRequestId$: Observable<string>;
protected cache: BehaviorSubject<AuthRequestLoginStrategyData>;
constructor(
data: AuthRequestLoginStrategyData,
private deviceTrustService: DeviceTrustServiceAbstraction,
...sharedDeps: ConstructorParameters<typeof LoginStrategy>
) {
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<AuthResult> {
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<void> {
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<void> {
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<void> {
await this.cryptoService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount(userId)),
userId,
);
}
exportCache(): CacheData {
return {
authRequest: this.cache.value,
};
}
}