1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

Auth/PM-7811 - Refactor User Auto Unlock Key Hydration Process To Remove Race Conditions (#8979)

* PM-7811 - Refactor UserKeyInitService to UserAutoUnlockKeyService - remove active account listening logic as it introduced race conditions with user key memory retrieval happening before the user auto unlock key was set into memory.

* PM-7811 - CLI - (1) Fix deps (2) On CLI init (pre command execution), if there is an active account, then set the user key in memory from the user auto unlock key.

* PM-7811 - Browser Extension / desktop - (1) Update deps (2) Sets user key in memory if the auto unlock key is set on account switch and background init (must act on all accounts so that account switcher displays unlock status properly).

* PM-7811 - Web - (1) Update deps (2) Sets user key in memory if the auto unlock key is set on init

* PM-7811 - Fix account switcher service changes not being necessary.
This commit is contained in:
Jared Snider
2024-04-29 17:43:14 -04:00
committed by GitHub
parent 82ae2fe62c
commit 20de053770
9 changed files with 165 additions and 246 deletions

View File

@@ -113,7 +113,7 @@ import { MemoryStorageService } from "@bitwarden/common/platform/services/memory
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { SystemService } from "@bitwarden/common/platform/services/system.service";
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service";
import {
ActiveUserStateProvider,
@@ -334,7 +334,7 @@ export default class MainBackground {
billingAccountProfileStateService: BillingAccountProfileStateService;
// eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module
intraprocessMessagingSubject: Subject<Message<object>>;
userKeyInitService: UserKeyInitService;
userAutoUnlockKeyService: UserAutoUnlockKeyService;
scriptInjectorService: BrowserScriptInjectorService;
kdfConfigService: kdfConfigServiceAbstraction;
@@ -1064,11 +1064,7 @@ export default class MainBackground {
}
}
this.userKeyInitService = new UserKeyInitService(
this.accountService,
this.cryptoService,
this.logService,
);
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
}
async bootstrap() {
@@ -1079,7 +1075,18 @@ export default class MainBackground {
// This is here instead of in in the InitService b/c we don't plan for
// side effects to run in the Browser InitService.
this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
const accounts = await firstValueFrom(this.accountService.accounts$);
const setUserKeyInMemoryPromises = [];
for (const userId of Object.keys(accounts) as UserId[]) {
// For each acct, we must await the process of setting the user key in memory
// if the auto user key is set to avoid race conditions of any code trying to access
// the user key from mem.
setUserKeyInMemoryPromises.push(
this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId),
);
}
await Promise.all(setUserKeyInMemoryPromises);
await (this.i18nService as I18nService).init();
(this.eventUploadService as EventUploadService).init(true);

View File

@@ -3,6 +3,7 @@ import * as path from "path";
import { program } from "commander";
import * as jsdom from "jsdom";
import { firstValueFrom } from "rxjs";
import {
InternalUserDecryptionOptionsServiceAbstraction,
@@ -79,7 +80,7 @@ import { MigrationBuilderService } from "@bitwarden/common/platform/services/mig
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StateService } from "@bitwarden/common/platform/services/state.service";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import {
ActiveUserStateProvider,
DerivedStateProvider,
@@ -236,7 +237,7 @@ export class Main {
biometricStateService: BiometricStateService;
billingAccountProfileStateService: BillingAccountProfileStateService;
providerApiService: ProviderApiServiceAbstraction;
userKeyInitService: UserKeyInitService;
userAutoUnlockKeyService: UserAutoUnlockKeyService;
kdfConfigService: KdfConfigServiceAbstraction;
constructor() {
@@ -709,11 +710,7 @@ export class Main {
this.providerApiService = new ProviderApiService(this.apiService);
this.userKeyInitService = new UserKeyInitService(
this.accountService,
this.cryptoService,
this.logService,
);
this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService);
}
async run() {
@@ -757,7 +754,11 @@ export class Main {
this.containerService.attachToGlobal(global);
await this.i18nService.init();
this.twoFactorService.init();
this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount) {
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id);
}
}
}

View File

@@ -1,10 +1,12 @@
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
@@ -12,9 +14,10 @@ import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platfor
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
import { UserId } from "@bitwarden/common/types/guid";
import { SyncService as SyncServiceAbstraction } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { I18nRendererService } from "../../platform/services/i18n.renderer.service";
@@ -36,7 +39,8 @@ export class InitService {
private nativeMessagingService: NativeMessagingService,
private themingService: AbstractThemingService,
private encryptService: EncryptService,
private userKeyInitService: UserKeyInitService,
private userAutoUnlockKeyService: UserAutoUnlockKeyService,
private accountService: AccountService,
@Inject(DOCUMENT) private document: Document,
) {}
@@ -44,7 +48,18 @@ export class InitService {
return async () => {
this.nativeMessagingService.init();
await this.stateService.init({ runMigrations: false }); // Desktop will run them in main process
this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
const accounts = await firstValueFrom(this.accountService.accounts$);
const setUserKeyInMemoryPromises = [];
for (const userId of Object.keys(accounts) as UserId[]) {
// For each acct, we must await the process of setting the user key in memory
// if the auto user key is set to avoid race conditions of any code trying to access
// the user key from mem.
setUserKeyInMemoryPromises.push(
this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(userId),
);
}
await Promise.all(setUserKeyInMemoryPromises);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@@ -1,17 +1,19 @@
import { DOCUMENT } from "@angular/common";
import { Inject, Injectable } from "@angular/core";
import { firstValueFrom } from "rxjs";
import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service";
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { TwoFactorService as TwoFactorServiceAbstraction } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
import { StateService as StateServiceAbstraction } from "@bitwarden/common/platform/abstractions/state.service";
import { ContainerService } from "@bitwarden/common/platform/services/container.service";
import { UserKeyInitService } from "@bitwarden/common/platform/services/user-key-init.service";
import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service";
import { EventUploadService } from "@bitwarden/common/services/event/event-upload.service";
import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/vault-timeout.service";
@@ -28,14 +30,21 @@ export class InitService {
private cryptoService: CryptoServiceAbstraction,
private themingService: AbstractThemingService,
private encryptService: EncryptService,
private userKeyInitService: UserKeyInitService,
private userAutoUnlockKeyService: UserAutoUnlockKeyService,
private accountService: AccountService,
@Inject(DOCUMENT) private document: Document,
) {}
init() {
return async () => {
await this.stateService.init();
this.userKeyInitService.listenForActiveUserChangesToSetUserKey();
const activeAccount = await firstValueFrom(this.accountService.activeAccount$);
if (activeAccount) {
// If there is an active account, we must await the process of setting the user key in memory
// if the auto user key is set to avoid race conditions of any code trying to access the user key from mem.
await this.userAutoUnlockKeyService.setUserKeyInMemoryIfAutoUserKeySet(activeAccount.id);
}
setTimeout(() => this.notificationsService.init(), 3000);
await this.vaultTimeoutService.init(true);