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:
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user