diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 7dde61b6fd..da4a3d793e 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -710,7 +710,7 @@ describe("NotificationBackground", () => { ); tabSendMessageSpy = jest.spyOn(BrowserApi, "tabSendMessage").mockImplementation(); editItemSpy = jest.spyOn(notificationBackground as any, "editItem"); - setAddEditCipherInfoSpy = jest.spyOn(stateService, "setAddEditCipherInfo"); + setAddEditCipherInfoSpy = jest.spyOn(cipherService, "setAddEditCipherInfo"); openAddEditVaultItemPopoutSpy = jest.spyOn( notificationBackground as any, "openAddEditVaultItemPopout", diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 44decc01a9..7ec3d5ee88 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -575,14 +575,14 @@ export default class NotificationBackground { } /** - * Sets the add/edit cipher info in the state service + * Sets the add/edit cipher info in the cipher service * and opens the add/edit vault item popout. * * @param cipherView - The cipher to edit * @param senderTab - The tab that the message was sent from */ private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) { - await this.stateService.setAddEditCipherInfo({ + await this.cipherService.setAddEditCipherInfo({ cipher: cipherView, collectionIds: cipherView.collectionIds, }); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index c19505118b..f67213f980 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -562,7 +562,7 @@ describe("OverlayBackground", () => { beforeEach(() => { sender = mock({ tab: { id: 1 } }); jest - .spyOn(overlayBackground["stateService"], "setAddEditCipherInfo") + .spyOn(overlayBackground["cipherService"], "setAddEditCipherInfo") .mockImplementation(); jest.spyOn(overlayBackground as any, "openAddEditVaultItemPopout").mockImplementation(); }); @@ -570,7 +570,7 @@ describe("OverlayBackground", () => { it("will not open the add edit popout window if the message does not have a login cipher provided", () => { sendExtensionRuntimeMessage({ command: "autofillOverlayAddNewVaultItem" }, sender); - expect(overlayBackground["stateService"].setAddEditCipherInfo).not.toHaveBeenCalled(); + expect(overlayBackground["cipherService"].setAddEditCipherInfo).not.toHaveBeenCalled(); expect(overlayBackground["openAddEditVaultItemPopout"]).not.toHaveBeenCalled(); }); @@ -591,7 +591,7 @@ describe("OverlayBackground", () => { ); await flushPromises(); - expect(overlayBackground["stateService"].setAddEditCipherInfo).toHaveBeenCalled(); + expect(overlayBackground["cipherService"].setAddEditCipherInfo).toHaveBeenCalled(); expect(BrowserApi.sendMessage).toHaveBeenCalledWith( "inlineAutofillMenuRefreshAddEditCipher", ); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 49d27391cf..49986a8951 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -626,7 +626,7 @@ class OverlayBackground implements OverlayBackgroundInterface { cipherView.type = CipherType.Login; cipherView.login = loginView; - await this.stateService.setAddEditCipherInfo({ + await this.cipherService.setAddEditCipherInfo({ cipher: cipherView, collectionIds: cipherView.collectionIds, }); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index e1ec0678d3..53a5812f6d 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -16,6 +16,7 @@ import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broa import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService, SimpleDialogOptions } from "@bitwarden/components"; import { BrowserApi } from "../platform/browser/browser-api"; @@ -46,6 +47,7 @@ export class AppComponent implements OnInit, OnDestroy { private i18nService: I18nService, private router: Router, private stateService: BrowserStateService, + private cipherService: CipherService, private messagingService: MessagingService, private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, @@ -166,7 +168,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.clearComponentStates(); } if (url.startsWith("/tabs/")) { - await this.stateService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null); } (window as any).previousPopupUrl = url; diff --git a/apps/browser/src/tools/popup/generator/generator.component.ts b/apps/browser/src/tools/popup/generator/generator.component.ts index c683b6f4a6..e82f87a34a 100644 --- a/apps/browser/src/tools/popup/generator/generator.component.ts +++ b/apps/browser/src/tools/popup/generator/generator.component.ts @@ -9,6 +9,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; @@ -26,6 +27,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { platformUtilsService: PlatformUtilsService, i18nService: I18nService, stateService: StateService, + cipherService: CipherService, route: ActivatedRoute, logService: LogService, private location: Location, @@ -35,6 +37,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { usernameGenerationService, platformUtilsService, stateService, + cipherService, i18nService, logService, route, @@ -43,7 +46,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { } async ngOnInit() { - this.addEditCipherInfo = await this.stateService.getAddEditCipherInfo(); + this.addEditCipherInfo = await this.cipherService.getAddEditCipherInfo(); if (this.addEditCipherInfo != null) { this.cipherState = this.addEditCipherInfo.cipher; } @@ -64,7 +67,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { this.addEditCipherInfo.cipher = this.cipherState; // 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 - this.stateService.setAddEditCipherInfo(this.addEditCipherInfo); + this.cipherService.setAddEditCipherInfo(this.addEditCipherInfo); this.close(); } diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 8e52d44069..054a45f374 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -304,7 +304,7 @@ export class AddEditComponent extends BaseAddEditComponent { } private saveCipherState() { - return this.stateService.setAddEditCipherInfo({ + return this.cipherService.setAddEditCipherInfo({ cipher: this.cipher, collectionIds: this.collections == null diff --git a/apps/desktop/src/app/tools/generator.component.spec.ts b/apps/desktop/src/app/tools/generator.component.spec.ts index 53f919a596..51b5bf93a2 100644 --- a/apps/desktop/src/app/tools/generator.component.spec.ts +++ b/apps/desktop/src/app/tools/generator.component.spec.ts @@ -10,6 +10,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { GeneratorComponent } from "./generator.component"; @@ -54,6 +55,10 @@ describe("GeneratorComponent", () => { provide: LogService, useValue: mock(), }, + { + provide: CipherService, + useValue: mock(), + }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/apps/desktop/src/app/tools/generator.component.ts b/apps/desktop/src/app/tools/generator.component.ts index a36bffa0e5..ceb621a2c1 100644 --- a/apps/desktop/src/app/tools/generator.component.ts +++ b/apps/desktop/src/app/tools/generator.component.ts @@ -8,6 +8,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @Component({ selector: "app-generator", @@ -18,6 +19,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { passwordGenerationService: PasswordGenerationServiceAbstraction, usernameGenerationService: UsernameGenerationServiceAbstraction, stateService: StateService, + cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, route: ActivatedRoute, @@ -28,6 +30,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { usernameGenerationService, platformUtilsService, stateService, + cipherService, i18nService, logService, route, diff --git a/apps/web/src/app/tools/generator.component.ts b/apps/web/src/app/tools/generator.component.ts index ee422ca527..f172f32433 100644 --- a/apps/web/src/app/tools/generator.component.ts +++ b/apps/web/src/app/tools/generator.component.ts @@ -8,6 +8,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { UsernameGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/username"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DialogService } from "@bitwarden/components"; import { PasswordGeneratorHistoryComponent } from "./password-generator-history.component"; @@ -21,6 +22,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { passwordGenerationService: PasswordGenerationServiceAbstraction, usernameGenerationService: UsernameGenerationServiceAbstraction, stateService: StateService, + cipherService: CipherService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, logService: LogService, @@ -32,6 +34,7 @@ export class GeneratorComponent extends BaseGeneratorComponent { usernameGenerationService, platformUtilsService, stateService, + cipherService, i18nService, logService, route, diff --git a/libs/angular/src/tools/generator/components/generator.component.ts b/libs/angular/src/tools/generator/components/generator.component.ts index d1c82a37b3..c95559238c 100644 --- a/libs/angular/src/tools/generator/components/generator.component.ts +++ b/libs/angular/src/tools/generator/components/generator.component.ts @@ -19,6 +19,7 @@ import { UsernameGeneratorOptions, } from "@bitwarden/common/tools/generator/username"; import { EmailForwarderOptions } from "@bitwarden/common/tools/models/domain/email-forwarder-options"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @Directive() export class GeneratorComponent implements OnInit { @@ -57,6 +58,7 @@ export class GeneratorComponent implements OnInit { protected usernameGenerationService: UsernameGenerationServiceAbstraction, protected platformUtilsService: PlatformUtilsService, protected stateService: StateService, + protected cipherService: CipherService, protected i18nService: I18nService, protected logService: LogService, protected route: ActivatedRoute, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 2c216ad5ca..02dcca9c07 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -678,7 +678,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } async loadAddEditCipherInfo(): Promise { - const addEditCipherInfo: any = await this.stateService.getAddEditCipherInfo(); + const addEditCipherInfo: any = await this.cipherService.getAddEditCipherInfo(); const loadedSavedInfo = addEditCipherInfo != null; if (loadedSavedInfo) { @@ -691,7 +691,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } } - await this.stateService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null); return loadedSavedInfo; } diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 18b2f79483..43b3bb90a3 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -20,7 +20,6 @@ import { UriMatchType } from "../../vault/enums"; import { CipherData } from "../../vault/models/data/cipher.data"; import { LocalData } from "../../vault/models/data/local.data"; import { CipherView } from "../../vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { KdfType, ThemeType } from "../enums"; import { ServerConfigData } from "../models/data/server-config.data"; import { @@ -61,8 +60,6 @@ export abstract class StateService { getAccessToken: (options?: StorageOptions) => Promise; setAccessToken: (value: string, options?: StorageOptions) => Promise; - getAddEditCipherInfo: (options?: StorageOptions) => Promise; - setAddEditCipherInfo: (value: AddEditCipherInfo, options?: StorageOptions) => Promise; getAlwaysShowDock: (options?: StorageOptions) => Promise; setAlwaysShowDock: (value: boolean, options?: StorageOptions) => Promise; getApiKeyClientId: (options?: StorageOptions) => Promise; diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 67c71c610d..dc76bf8018 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -24,7 +24,6 @@ import { UriMatchType } from "../../vault/enums"; import { CipherData } from "../../vault/models/data/cipher.data"; import { LocalData } from "../../vault/models/data/local.data"; import { CipherView } from "../../vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "../../vault/types/add-edit-cipher-info"; import { EnvironmentService } from "../abstractions/environment.service"; import { LogService } from "../abstractions/log.service"; import { @@ -271,34 +270,6 @@ export class StateService< await this.saveAccount(account, options); } - async getAddEditCipherInfo(options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - // ensure prototype on cipher - const raw = account?.data?.addEditCipherInfo; - return raw == null - ? null - : { - cipher: - raw?.cipher.toJSON != null - ? raw.cipher - : CipherView.fromJSON(raw?.cipher as Jsonify), - collectionIds: raw?.collectionIds, - }; - } - - async setAddEditCipherInfo(value: AddEditCipherInfo, options?: StorageOptions): Promise { - const account = await this.getAccount( - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - account.data.addEditCipherInfo = value; - await this.saveAccount( - account, - this.reconcileOptions(options, await this.defaultInMemoryOptions()), - ); - } - async getAlwaysShowDock(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 2c3a7852af..7fe606174c 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -6,6 +6,7 @@ import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; +import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService { clearCache: (userId?: string) => Promise; @@ -89,4 +90,6 @@ export abstract class CipherService { ) => Promise; getKeyForCipherKeyDecryption: (cipher: Cipher) => Promise; decryptCiphers: (ciphers: Cipher[]) => Promise; + getAddEditCipherInfo: () => Promise; + setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; } diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index ce0fda56ef..246ecedccc 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -59,6 +59,7 @@ import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; import { PasswordHistoryView } from "../models/view/password-history.view"; +import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; import { DECRYPTED_CIPHERS, ENCRYPTED_CIPHERS } from "./key-state/ciphers.state"; @@ -68,6 +69,12 @@ const CIPHERS_DISK_KEY = new KeyDefinition>(CIPHERS_DI deserializer: (obj) => obj, }); +const ADD_EDIT_CIPHER_INFO_KEY = new KeyDefinition( + CIPHERS_DISK, + "addEditCipherInfo", + { deserializer: (obj) => obj }, +); + export class CipherService implements CipherServiceAbstraction { private sortedCiphersCache: SortedCiphersCache = new SortedCiphersCache( this.sortCiphersByLastUsed, @@ -76,10 +83,12 @@ export class CipherService implements CipherServiceAbstraction { localData$: Observable>; ciphers$: Observable>; cipherViews$: Observable; + addEditCipherInfo$: Observable; private localDataState: ActiveUserState>; private encryptedCiphersState: ActiveUserState>; private decryptedCiphersState: DerivedState; + private addEditCipherInfoState: ActiveUserState; constructor( private cryptoService: CryptoService, @@ -101,10 +110,12 @@ export class CipherService implements CipherServiceAbstraction { DECRYPTED_CIPHERS, { cipherService: this }, ); + this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); this.localData$ = this.localDataState.state$; this.ciphers$ = this.encryptedCiphersState.state$; this.cipherViews$ = this.decryptedCiphersState.state$; + this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; } async getDecryptedCipherCache(): Promise { @@ -1045,6 +1056,15 @@ export class CipherService implements CipherServiceAbstraction { ); } + async getAddEditCipherInfo(): Promise { + const info = await firstValueFrom(this.addEditCipherInfo$); + return info; + } + + async setAddEditCipherInfo(value: AddEditCipherInfo) { + await this.addEditCipherInfoState.update(() => value); + } + // Helpers // In the case of a cipher that is being shared with an organization, we want to decrypt the