From 128c9093b0e40effb82830189b27caf5a096f12c Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:43:14 -0400 Subject: [PATCH] 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. --- .../browser/src/background/main.background.ts | 23 ++- apps/cli/src/bw.ts | 17 +- apps/desktop/src/app/services/init.service.ts | 21 ++- apps/web/src/app/core/init.service.ts | 15 +- .../src/services/jslib-services.module.ts | 9 +- .../user-auto-unlock-key.service.spec.ts | 71 ++++++++ .../services/user-auto-unlock-key.service.ts | 36 ++++ .../services/user-key-init.service.spec.ts | 162 ------------------ .../services/user-key-init.service.ts | 57 ------ 9 files changed, 165 insertions(+), 246 deletions(-) create mode 100644 libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts create mode 100644 libs/common/src/platform/services/user-auto-unlock-key.service.ts delete mode 100644 libs/common/src/platform/services/user-key-init.service.spec.ts delete mode 100644 libs/common/src/platform/services/user-key-init.service.ts diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index c9928df3a8..83986299ea 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -114,7 +114,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, @@ -337,7 +337,7 @@ export default class MainBackground { billingAccountProfileStateService: BillingAccountProfileStateService; // eslint-disable-next-line rxjs/no-exposed-subjects -- Needed to give access to services module intraprocessMessagingSubject: Subject>; - userKeyInitService: UserKeyInitService; + userAutoUnlockKeyService: UserAutoUnlockKeyService; scriptInjectorService: BrowserScriptInjectorService; onUpdatedRan: boolean; @@ -1085,11 +1085,7 @@ export default class MainBackground { } } - this.userKeyInitService = new UserKeyInitService( - this.accountService, - this.cryptoService, - this.logService, - ); + this.userAutoUnlockKeyService = new UserAutoUnlockKeyService(this.cryptoService); } async bootstrap() { @@ -1099,7 +1095,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(); await (this.eventUploadService as EventUploadService).init(true); diff --git a/apps/cli/src/bw.ts b/apps/cli/src/bw.ts index cd711a4b0a..507b37c5c7 100644 --- a/apps/cli/src/bw.ts +++ b/apps/cli/src/bw.ts @@ -3,6 +3,7 @@ import * as path from "path"; import { program } from "commander"; import * as jsdom from "jsdom"; +import { firstValueFrom } from "rxjs"; import { InternalUserDecryptionOptionsServiceAbstraction, @@ -77,7 +78,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, @@ -234,7 +235,7 @@ export class Main { biometricStateService: BiometricStateService; billingAccountProfileStateService: BillingAccountProfileStateService; providerApiService: ProviderApiServiceAbstraction; - userKeyInitService: UserKeyInitService; + userAutoUnlockKeyService: UserAutoUnlockKeyService; constructor() { let p = null; @@ -694,11 +695,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() { @@ -742,7 +739,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); + } } } diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index ae2e1ba97c..0452e9be83 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -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 diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index dab6ed5e3d..55dc1544ff 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -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); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a2c3fa5a34..667838f52a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -53,7 +53,6 @@ import { ProviderApiService } from "@bitwarden/common/admin-console/services/pro import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; import { - AccountService, AccountService as AccountServiceAbstraction, InternalAccountService, } from "@bitwarden/common/auth/abstractions/account.service"; @@ -160,7 +159,7 @@ import { MigrationRunner } from "@bitwarden/common/platform/services/migration-r import { NoopNotificationsService } from "@bitwarden/common/platform/services/noop-notifications.service"; 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 { ValidationService } from "@bitwarden/common/platform/services/validation.service"; import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { @@ -1126,9 +1125,9 @@ const safeProviders: SafeProvider[] = [ deps: [StateProvider], }), safeProvider({ - provide: UserKeyInitService, - useClass: UserKeyInitService, - deps: [AccountService, CryptoServiceAbstraction, LogService], + provide: UserAutoUnlockKeyService, + useClass: UserAutoUnlockKeyService, + deps: [CryptoServiceAbstraction], }), safeProvider({ provide: ErrorHandler, diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts new file mode 100644 index 0000000000..f0d60158c1 --- /dev/null +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.spec.ts @@ -0,0 +1,71 @@ +import { mock } from "jest-mock-extended"; + +import { CsprngArray } from "../../types/csprng"; +import { UserId } from "../../types/guid"; +import { UserKey } from "../../types/key"; +import { KeySuffixOptions } from "../enums"; +import { Utils } from "../misc/utils"; +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; + +import { CryptoService } from "./crypto.service"; +import { UserAutoUnlockKeyService } from "./user-auto-unlock-key.service"; + +describe("UserAutoUnlockKeyService", () => { + let userAutoUnlockKeyService: UserAutoUnlockKeyService; + + const mockUserId = Utils.newGuid() as UserId; + + const cryptoService = mock(); + + beforeEach(() => { + userAutoUnlockKeyService = new UserAutoUnlockKeyService(cryptoService); + }); + + describe("setUserKeyInMemoryIfAutoUserKeySet", () => { + it("does nothing if the userId is null", async () => { + // Act + await (userAutoUnlockKeyService as any).setUserKeyInMemoryIfAutoUserKeySet(null); + + // Assert + expect(cryptoService.getUserKeyFromStorage).not.toHaveBeenCalled(); + expect(cryptoService.setUserKey).not.toHaveBeenCalled(); + }); + + it("does nothing if the autoUserKey is null", async () => { + // Arrange + const userId = mockUserId; + + cryptoService.getUserKeyFromStorage.mockResolvedValue(null); + + // Act + await (userAutoUnlockKeyService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); + + // Assert + expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( + KeySuffixOptions.Auto, + userId, + ); + expect(cryptoService.setUserKey).not.toHaveBeenCalled(); + }); + + it("sets the user key in memory if the autoUserKey is not null", async () => { + // Arrange + const userId = mockUserId; + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockAutoUserKey: UserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; + + cryptoService.getUserKeyFromStorage.mockResolvedValue(mockAutoUserKey); + + // Act + await (userAutoUnlockKeyService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); + + // Assert + expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( + KeySuffixOptions.Auto, + userId, + ); + expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockAutoUserKey, userId); + }); + }); +}); diff --git a/libs/common/src/platform/services/user-auto-unlock-key.service.ts b/libs/common/src/platform/services/user-auto-unlock-key.service.ts new file mode 100644 index 0000000000..6c8ce3f048 --- /dev/null +++ b/libs/common/src/platform/services/user-auto-unlock-key.service.ts @@ -0,0 +1,36 @@ +import { UserId } from "../../types/guid"; +import { CryptoService } from "../abstractions/crypto.service"; +import { KeySuffixOptions } from "../enums"; + +// TODO: this is a half measure improvement which allows us to reduce some side effects today (cryptoService.getUserKey setting user key in memory if auto key exists) +// but ideally, in the future, we would be able to put this logic into the cryptoService +// after the vault timeout settings service is transitioned to state provider so that +// the getUserKey logic can simply go to the correct location based on the vault timeout settings +// similar to the TokenService (it would either go to secure storage for the auto user key or memory for the user key) + +export class UserAutoUnlockKeyService { + constructor(private cryptoService: CryptoService) {} + + /** + * The presence of the user key in memory dictates whether the user's vault is locked or unlocked. + * However, for users that have the auto unlock user key set, we need to set the user key in memory + * on application bootstrap and on active account changes so that the user's vault loads unlocked. + * @param userId - The user id to check for an auto user key. + */ + async setUserKeyInMemoryIfAutoUserKeySet(userId: UserId): Promise { + if (userId == null) { + return; + } + + const autoUserKey = await this.cryptoService.getUserKeyFromStorage( + KeySuffixOptions.Auto, + userId, + ); + + if (autoUserKey == null) { + return; + } + + await this.cryptoService.setUserKey(autoUserKey, userId); + } +} diff --git a/libs/common/src/platform/services/user-key-init.service.spec.ts b/libs/common/src/platform/services/user-key-init.service.spec.ts deleted file mode 100644 index 567320ded6..0000000000 --- a/libs/common/src/platform/services/user-key-init.service.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { mock } from "jest-mock-extended"; - -import { FakeAccountService, mockAccountServiceWith } from "../../../spec"; -import { CsprngArray } from "../../types/csprng"; -import { UserId } from "../../types/guid"; -import { UserKey } from "../../types/key"; -import { LogService } from "../abstractions/log.service"; -import { KeySuffixOptions } from "../enums"; -import { Utils } from "../misc/utils"; -import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; - -import { CryptoService } from "./crypto.service"; -import { UserKeyInitService } from "./user-key-init.service"; - -describe("UserKeyInitService", () => { - let userKeyInitService: UserKeyInitService; - - const mockUserId = Utils.newGuid() as UserId; - - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); - - const cryptoService = mock(); - const logService = mock(); - - beforeEach(() => { - userKeyInitService = new UserKeyInitService(accountService, cryptoService, logService); - }); - - describe("listenForActiveUserChangesToSetUserKey()", () => { - it("calls setUserKeyInMemoryIfAutoUserKeySet if there is an active user", () => { - // Arrange - accountService.activeAccountSubject.next({ - id: mockUserId, - name: "name", - email: "email", - }); - - (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet = jest.fn(); - - // Act - - const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); - - // Assert - - expect(subscription).not.toBeFalsy(); - - expect((userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet).toHaveBeenCalledWith( - mockUserId, - ); - }); - - it("calls setUserKeyInMemoryIfAutoUserKeySet if there is an active user and tracks subsequent emissions", () => { - // Arrange - accountService.activeAccountSubject.next({ - id: mockUserId, - name: "name", - email: "email", - }); - - const mockUser2Id = Utils.newGuid() as UserId; - - jest - .spyOn(userKeyInitService as any, "setUserKeyInMemoryIfAutoUserKeySet") - .mockImplementation(() => Promise.resolve()); - - // Act - - const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); - - accountService.activeAccountSubject.next({ - id: mockUser2Id, - name: "name", - email: "email", - }); - - // Assert - - expect(subscription).not.toBeFalsy(); - - expect((userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet).toHaveBeenCalledTimes( - 2, - ); - - expect( - (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, - ).toHaveBeenNthCalledWith(1, mockUserId); - expect( - (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, - ).toHaveBeenNthCalledWith(2, mockUser2Id); - - subscription.unsubscribe(); - }); - - it("does not call setUserKeyInMemoryIfAutoUserKeySet if there is not an active user", () => { - // Arrange - accountService.activeAccountSubject.next(null); - - (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet = jest.fn(); - - // Act - - const subscription = userKeyInitService.listenForActiveUserChangesToSetUserKey(); - - // Assert - - expect(subscription).not.toBeFalsy(); - - expect( - (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet, - ).not.toHaveBeenCalledWith(mockUserId); - }); - }); - - describe("setUserKeyInMemoryIfAutoUserKeySet", () => { - it("does nothing if the userId is null", async () => { - // Act - await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(null); - - // Assert - expect(cryptoService.getUserKeyFromStorage).not.toHaveBeenCalled(); - expect(cryptoService.setUserKey).not.toHaveBeenCalled(); - }); - - it("does nothing if the autoUserKey is null", async () => { - // Arrange - const userId = mockUserId; - - cryptoService.getUserKeyFromStorage.mockResolvedValue(null); - - // Act - await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); - - // Assert - expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( - KeySuffixOptions.Auto, - userId, - ); - expect(cryptoService.setUserKey).not.toHaveBeenCalled(); - }); - - it("sets the user key in memory if the autoUserKey is not null", async () => { - // Arrange - const userId = mockUserId; - - const mockRandomBytes = new Uint8Array(64) as CsprngArray; - const mockAutoUserKey: UserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey; - - cryptoService.getUserKeyFromStorage.mockResolvedValue(mockAutoUserKey); - - // Act - await (userKeyInitService as any).setUserKeyInMemoryIfAutoUserKeySet(userId); - - // Assert - expect(cryptoService.getUserKeyFromStorage).toHaveBeenCalledWith( - KeySuffixOptions.Auto, - userId, - ); - expect(cryptoService.setUserKey).toHaveBeenCalledWith(mockAutoUserKey, userId); - }); - }); -}); diff --git a/libs/common/src/platform/services/user-key-init.service.ts b/libs/common/src/platform/services/user-key-init.service.ts deleted file mode 100644 index 1f6aacce8f..0000000000 --- a/libs/common/src/platform/services/user-key-init.service.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { EMPTY, Subscription, catchError, filter, from, switchMap } from "rxjs"; - -import { AccountService } from "../../auth/abstractions/account.service"; -import { UserId } from "../../types/guid"; -import { CryptoService } from "../abstractions/crypto.service"; -import { LogService } from "../abstractions/log.service"; -import { KeySuffixOptions } from "../enums"; - -// TODO: this is a half measure improvement which allows us to reduce some side effects today (cryptoService.getUserKey setting user key in memory if auto key exists) -// but ideally, in the future, we would be able to put this logic into the cryptoService -// after the vault timeout settings service is transitioned to state provider so that -// the getUserKey logic can simply go to the correct location based on the vault timeout settings -// similar to the TokenService (it would either go to secure storage for the auto user key or memory for the user key) - -export class UserKeyInitService { - constructor( - private accountService: AccountService, - private cryptoService: CryptoService, - private logService: LogService, - ) {} - - // Note: must listen for changes to support account switching - listenForActiveUserChangesToSetUserKey(): Subscription { - return this.accountService.activeAccount$ - .pipe( - filter((activeAccount) => activeAccount != null), - switchMap((activeAccount) => - from(this.setUserKeyInMemoryIfAutoUserKeySet(activeAccount?.id)).pipe( - catchError((err: unknown) => { - this.logService.warning( - `setUserKeyInMemoryIfAutoUserKeySet failed with error: ${err}`, - ); - // Returning EMPTY to protect observable chain from cancellation in case of error - return EMPTY; - }), - ), - ), - ) - .subscribe(); - } - - private async setUserKeyInMemoryIfAutoUserKeySet(userId: UserId) { - if (userId == null) { - return; - } - - const autoUserKey = await this.cryptoService.getUserKeyFromStorage( - KeySuffixOptions.Auto, - userId, - ); - if (autoUserKey == null) { - return; - } - - await this.cryptoService.setUserKey(autoUserKey, userId); - } -}