1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 17:53:39 +00:00

[PM-5255, PM-3339] Refactor login strategy to use state providers (#7821)

* add key definition and StrategyData classes

* use state providers for login strategies

* serialize login data for cache

* use state providers for auth request notification

* fix registrations

* add docs to abstraction

* fix sso strategy

* fix password login strategy tests

* fix base login strategy tests

* fix user api login strategy tests

* PM-3339 add tests for admin auth request in sso strategy

* fix auth request login strategy tests

* fix webauthn login strategy tests

* create login strategy state

* use barrel file in common/spec

* test login strategy cache deserialization

* use global state provider

* add test for login strategy service

* fix auth request storage

* add recursive prototype checking and json deserializers to nested objects

* fix CLI

* Create wrapper for login strategy cache

* use behavior subjects in strategies instead of global state

* rename userApi to userApiKey

* pr feedback

* fix tests

* fix deserialization tests

* fix tests

---------

Co-authored-by: rr-bw <102181210+rr-bw@users.noreply.github.com>
This commit is contained in:
Jake Fink
2024-03-12 14:19:50 -04:00
committed by GitHub
parent 6b1da67f3a
commit a0e0637bb6
35 changed files with 1414 additions and 362 deletions

View File

@@ -1,3 +1,6 @@
import { Observable, map, BehaviorSubject } from "rxjs";
import { Jsonify } from "type-fest";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@@ -14,26 +17,33 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { AuthRequestLoginCredentials } from "../models/domain/login-credentials";
import { CacheData } from "../services/login-strategies/login-strategy.state";
import { LoginStrategy } from "./login.strategy";
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 {
get email() {
return this.tokenRequest.email;
}
email$: Observable<string>;
accessCode$: Observable<string>;
authRequestId$: Observable<string>;
get accessCode() {
return this.authRequestCredentials.accessCode;
}
get authRequestId() {
return this.authRequestCredentials.authRequestId;
}
tokenRequest: PasswordTokenRequest;
private authRequestCredentials: AuthRequestLoginCredentials;
protected cache: BehaviorSubject<AuthRequestLoginStrategyData>;
constructor(
data: AuthRequestLoginStrategyData,
cryptoService: CryptoService,
apiService: ApiService,
tokenService: TokenService,
@@ -56,22 +66,26 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
stateService,
twoFactorService,
);
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) {
// NOTE: To avoid DeadObject references on Firefox, do not set the credentials object directly
// Use deep copy in future if objects are added that were created in popup
this.authRequestCredentials = { ...credentials };
this.tokenRequest = new PasswordTokenRequest(
const data = new AuthRequestLoginStrategyData();
data.tokenRequest = new PasswordTokenRequest(
credentials.email,
credentials.accessCode,
null,
await this.buildTwoFactor(credentials.twoFactor),
await this.buildDeviceRequest(),
);
data.tokenRequest.setAuthRequestAccessCode(credentials.authRequestId);
data.authRequestCredentials = credentials;
this.cache.next(data);
this.tokenRequest.setAuthRequestAccessCode(credentials.authRequestId);
const [authResult] = await this.startLogIn();
return authResult;
}
@@ -80,27 +94,32 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
twoFactor: TokenTwoFactorRequest,
captchaResponse: string,
): Promise<AuthResult> {
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
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) {
const authRequestCredentials = this.cache.value.authRequestCredentials;
if (
this.authRequestCredentials.decryptedMasterKey &&
this.authRequestCredentials.decryptedMasterKeyHash
authRequestCredentials.decryptedMasterKey &&
authRequestCredentials.decryptedMasterKeyHash
) {
await this.cryptoService.setMasterKey(this.authRequestCredentials.decryptedMasterKey);
await this.cryptoService.setMasterKeyHash(this.authRequestCredentials.decryptedMasterKeyHash);
await this.cryptoService.setMasterKey(authRequestCredentials.decryptedMasterKey);
await this.cryptoService.setMasterKeyHash(authRequestCredentials.decryptedMasterKeyHash);
}
}
protected override async setUserKey(response: IdentityTokenResponse): 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 (this.authRequestCredentials.decryptedUserKey) {
await this.cryptoService.setUserKey(this.authRequestCredentials.decryptedUserKey);
if (authRequestCredentials.decryptedUserKey) {
await this.cryptoService.setUserKey(authRequestCredentials.decryptedUserKey);
} else {
await this.trySetUserKeyWithMasterKey();
// Establish trust if required after setting user key
@@ -121,4 +140,10 @@ export class AuthRequestLoginStrategy extends LoginStrategy {
response.privateKey ?? (await this.createKeyPairForOldAccount()),
);
}
exportCache(): CacheData {
return {
authRequest: this.cache.value,
};
}
}