From 47364bf258d9aed474203c5f2887cc26fb54b417 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Tue, 19 Nov 2024 10:34:33 -0500 Subject: [PATCH] Simplified to accept a single user id instead of an observable --- .../notification.background.spec.ts | 22 ++-- .../background/notification.background.ts | 21 ++-- .../browser/src/background/main.background.ts | 6 +- .../add-edit-folder-dialog.component.spec.ts | 2 +- .../add-edit-folder-dialog.component.ts | 12 +- .../vault-popup-list-filters.service.ts | 110 +++++++++--------- .../popup/settings/folders-v2.component.ts | 6 +- .../vault/popup/settings/folders.component.ts | 9 +- apps/cli/src/commands/edit.command.ts | 10 +- apps/cli/src/commands/get.command.ts | 17 ++- apps/cli/src/commands/list.command.ts | 6 +- .../service-container/service-container.ts | 6 +- apps/cli/src/vault/create.command.ts | 12 +- apps/cli/src/vault/delete.command.ts | 8 +- .../migrate-legacy-encryption.component.ts | 2 +- .../bulk-move-dialog.component.ts | 3 +- .../folder-add-edit.component.ts | 6 +- .../services/vault-filter.service.ts | 20 ++-- .../src/services/jslib-services.module.ts | 2 +- .../vault/components/add-edit.component.ts | 8 +- .../components/folder-add-edit.component.ts | 14 ++- .../src/vault/components/view.component.ts | 6 +- .../services/vault-filter.service.ts | 12 +- .../src/platform/sync/core-sync.service.ts | 21 ++-- .../folder/folder-api.service.abstraction.ts | 8 +- .../folder/folder.service.abstraction.ts | 16 +-- .../services/folder/folder-api.service.ts | 19 ++- .../services/folder/folder.service.spec.ts | 25 ++-- .../vault/services/folder/folder.service.ts | 37 +++--- .../src/components/import.component.ts | 11 +- .../individual-vault-export.service.ts | 6 +- .../default-cipher-form-config.service.ts | 6 +- .../src/cipher-view/cipher-view.component.ts | 3 +- 33 files changed, 232 insertions(+), 240 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index d4ed299a055..37c05a55a3a 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -2,7 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; -import { AccountInfo } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { ExtensionCommand } from "@bitwarden/common/autofill/constants"; @@ -11,10 +11,8 @@ import { UserNotificationSettingsService } from "@bitwarden/common/autofill/serv import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -60,15 +58,20 @@ describe("NotificationBackground", () => { const logService = mock(); const themeStateService = mock(); const configService = mock(); - let accountService: FakeAccountService; + const accountService = mock(); - const userId = Utils.newGuid() as UserId; + const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ + id: "testId" as UserId, + email: "test@example.com", + emailVerified: true, + name: "Test User", + }); beforeEach(() => { - accountService = mockAccountServiceWith(userId); activeAccountStatusMock$ = new BehaviorSubject(AuthenticationStatus.Locked); authService = mock(); authService.activeAccountStatus$ = activeAccountStatusMock$; + accountService.activeAccount$ = activeAccountSubject; notificationBackground = new NotificationBackground( autofillService, cipherService, @@ -688,13 +691,6 @@ describe("NotificationBackground", () => { }); describe("saveOrUpdateCredentials", () => { - const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ - id: "testId" as UserId, - email: "test@example.com", - emailVerified: true, - name: "Test User", - }); - let getDecryptedCipherByIdSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; let updatePasswordSpy: jest.SpyInstance; diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index ff51d82ad8a..75c5d90d3e8 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -569,9 +569,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { @@ -611,10 +609,8 @@ export default class NotificationBackground { return; } - const cipher = await this.cipherService.encrypt( - cipherView, - await firstValueFrom(this.activeUserId$), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipher = await this.cipherService.encrypt(cipherView, activeUserId); try { // We've only updated the password, no need to broadcast editedCipher message await this.cipherService.updateWithServer(cipher); @@ -646,17 +642,15 @@ export default class NotificationBackground { if (Utils.isNullOrWhitespace(folderId) || folderId === "null") { return false; } - - const folders = await firstValueFrom(this.folderService.folderViews$(this.activeUserId$)); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId)); return folders.some((x) => x.id === folderId); } private async getDecryptedCipherById(cipherId: string) { const cipher = await this.cipherService.get(cipherId); if (cipher != null && cipher.type === CipherType.Login) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -696,7 +690,8 @@ export default class NotificationBackground { * Returns the first value found from the folder service's folderViews$ observable. */ private async getFolderData() { - return await firstValueFrom(this.folderService.folderViews$(this.activeUserId$)); + const activeUserId = await firstValueFrom(this.activeUserId$); + return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } private async getWebVaultUrl(): Promise { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f9ebbac0d2c..fb92ebe04ae 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -833,11 +833,7 @@ export default class MainBackground { this.cipherService, this.stateProvider, ); - this.folderApiService = new FolderApiService( - this.folderService, - this.apiService, - this.accountService, - ); + this.folderApiService = new FolderApiService(this.folderService, this.apiService); this.userVerificationService = new UserVerificationService( this.keyService, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts index 4e222a554f7..cbec7903031 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.spec.ts @@ -171,7 +171,7 @@ describe("AddEditFolderDialogComponent", () => { it("deletes the folder", async () => { await component.deleteFolder(); - expect(deleteFolder).toHaveBeenCalledWith(folderView.id); + expect(deleteFolder).toHaveBeenCalledWith(folderView.id, ""); expect(showToast).toHaveBeenCalledWith({ variant: "success", title: null, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index c0cbf877480..d2abe6d0cc2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -11,7 +11,7 @@ import { } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -65,6 +65,7 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { name: ["", Validators.required], }); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroyRef = inject(DestroyRef); constructor( @@ -112,10 +113,10 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { this.folder.name = this.folderForm.controls.name.value; try { - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - await this.folderApiService.save(folder); + await this.folderApiService.save(folder, activeUserId); this.toastService.showToast({ variant: "success", @@ -142,7 +143,8 @@ export class AddEditFolderDialogComponent implements AfterViewInit, OnInit { } try { - await this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.folderApiService.delete(this.folder.id, activeUserId); this.toastService.showToast({ variant: "success", title: null, diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index c8de792c9e8..d94814944fe 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -243,61 +243,65 @@ export class VaultPopupListFiltersService { /** * Folder array structured to be directly passed to `ChipSelectComponent` */ - folders$: Observable[]> = combineLatest([ - this.filters$.pipe( - distinctUntilChanged( - (previousFilter, currentFilter) => - // Only update the collections when the organizationId filter changes - previousFilter.organization?.id === currentFilter.organization?.id, + folders$: Observable[]> = this.activeUserId$.pipe( + switchMap((userId) => + combineLatest([ + this.filters$.pipe( + distinctUntilChanged( + (previousFilter, currentFilter) => + // Only update the collections when the organizationId filter changes + previousFilter.organization?.id === currentFilter.organization?.id, + ), + ), + this.folderService.folderViews$(userId), + this.cipherViews$, + ]).pipe( + map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { + if (folders.length === 1 && folders[0].id === null) { + // Do not display folder selections when only the "no folder" option is available. + return [filters, [], cipherViews]; + } + + // Sort folders by alphabetic name + folders.sort(Utils.getSortFunction(this.i18nService, "name")); + let arrangedFolders = folders; + + const noFolder = folders.find((f) => f.id === null); + + if (noFolder) { + // Update `name` of the "no folder" option to "Items with no folder" + noFolder.name = this.i18nService.t("itemsWithNoFolder"); + + // Move the "no folder" option to the end of the list + arrangedFolders = [...folders.filter((f) => f.id !== null), noFolder]; + } + return [filters, arrangedFolders, cipherViews]; + }), + map(([filters, folders, cipherViews]) => { + const organizationId = filters.organization?.id ?? null; + + // When no org or "My vault" is selected, return all folders + if (organizationId === null || organizationId === MY_VAULT_ID) { + return folders; + } + + const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); + + // Return only the folders that have ciphers within the filtered organization + return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); + }), + map((folders) => { + const nestedFolders = this.getAllFoldersNested(folders); + return new DynamicTreeNode({ + fullList: folders, + nestedList: nestedFolders, + }); + }), + map((folders) => + folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), + ), ), ), - this.folderService.folderViews$(this.activeUserId$), - this.cipherViews$, - ]).pipe( - map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { - if (folders.length === 1 && folders[0].id === null) { - // Do not display folder selections when only the "no folder" option is available. - return [filters, [], cipherViews]; - } - - // Sort folders by alphabetic name - folders.sort(Utils.getSortFunction(this.i18nService, "name")); - let arrangedFolders = folders; - - const noFolder = folders.find((f) => f.id === null); - - if (noFolder) { - // Update `name` of the "no folder" option to "Items with no folder" - noFolder.name = this.i18nService.t("itemsWithNoFolder"); - - // Move the "no folder" option to the end of the list - arrangedFolders = [...folders.filter((f) => f.id !== null), noFolder]; - } - return [filters, arrangedFolders, cipherViews]; - }), - map(([filters, folders, cipherViews]) => { - const organizationId = filters.organization?.id ?? null; - - // When no org or "My vault" is selected, return all folders - if (organizationId === null || organizationId === MY_VAULT_ID) { - return folders; - } - - const orgCiphers = cipherViews.filter((c) => c.organizationId === organizationId); - - // Return only the folders that have ciphers within the filtered organization - return folders.filter((f) => orgCiphers.some((oc) => oc.folderId === f.id)); - }), - map((folders) => { - const nestedFolders = this.getAllFoldersNested(folders); - return new DynamicTreeNode({ - fullList: folders, - nestedList: nestedFolders, - }); - }), - map((folders) => - folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), - ), ); /** diff --git a/apps/browser/src/vault/popup/settings/folders-v2.component.ts b/apps/browser/src/vault/popup/settings/folders-v2.component.ts index 4cc4e029dee..cb3e295d808 100644 --- a/apps/browser/src/vault/popup/settings/folders-v2.component.ts +++ b/apps/browser/src/vault/popup/settings/folders-v2.component.ts @@ -1,6 +1,6 @@ import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; -import { map, Observable } from "rxjs"; +import { map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -53,13 +53,13 @@ export class FoldersV2Component { private dialogService: DialogService, private accountService: AccountService, ) { - this.folders$ = this.folderService.folderViews$(this.activeUserId$).pipe( + this.folders$ = this.activeUserId$.pipe( + switchMap((userId) => this.folderService.folderViews$(userId)), map((folders) => { // Remove the last folder, which is the "no folder" option folder if (folders.length > 0) { return folders.slice(0, folders.length - 1); } - return folders; }), ); diff --git a/apps/browser/src/vault/popup/settings/folders.component.ts b/apps/browser/src/vault/popup/settings/folders.component.ts index fa849bbaf37..c05630f300d 100644 --- a/apps/browser/src/vault/popup/settings/folders.component.ts +++ b/apps/browser/src/vault/popup/settings/folders.component.ts @@ -1,6 +1,6 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { map, Observable } from "rxjs"; +import { map, Observable, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -20,12 +20,13 @@ export class FoldersComponent { private router: Router, private accountService: AccountService, ) { - this.folders$ = this.folderService.folderViews$(this.activeUserId$).pipe( + this.folders$ = this.activeUserId$.pipe( + switchMap((userId) => this.folderService.folderViews$(userId)), map((folders) => { + // Remove the last folder, which is the "no folder" option folder if (folders.length > 0) { - folders = folders.slice(0, folders.length - 1); + return folders.slice(0, folders.length - 1); } - return folders; }), ); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 8795b5c045b..b7964cb6067 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -136,7 +136,8 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const folder = await this.folderService.getFromState(id, this.activeUserId$); + const activeUserId = await firstValueFrom(this.activeUserId$); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } @@ -144,12 +145,11 @@ export class EditCommand { let folderView = await folder.decrypt(); folderView = FolderExport.toView(req, folderView); - const activeUserId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId.id); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const encFolder = await this.folderService.encrypt(folderView, userKey); try { - await this.folderApiService.save(encFolder); - const updatedFolder = await this.folderService.get(folder.id, this.activeUserId$); + await this.folderApiService.save(encFolder, activeUserId); + const updatedFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await updatedFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index f5297bf07f7..64089b343b5 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -116,11 +116,9 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id); if (cipher != null) { + const activeUserId = await firstValueFrom(this.activeUserId$); decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - cipher, - await firstValueFrom(this.activeUserId$), - ), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); } } else if (id.trim() !== "") { @@ -385,13 +383,14 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; + const activeUserId = await firstValueFrom(this.activeUserId$); if (Utils.isGuid(id)) { - const folder = await this.folderService.getFromState(id, this.activeUserId$); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { decFolder = await folder.decrypt(); } } else if (id.trim() !== "") { - let folders = await this.folderService.getAllDecryptedFromState(this.activeUserId$); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); folders = CliUtils.searchFolders(folders, id); if (folders.length > 1) { return Response.multipleResults(folders.map((f) => f.id)); @@ -553,9 +552,9 @@ export class GetCommand extends DownloadCommand { private async getFingerprint(id: string) { let fingerprint: string[] = null; if (id === "me") { - const userId = await firstValueFrom(this.activeUserId$); - const publicKey = await firstValueFrom(this.keyService.userPublicKey$(userId)); - fingerprint = await this.keyService.getFingerprint(userId, publicKey); + const activeUserId = await firstValueFrom(this.activeUserId$); + const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); + fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { try { const response = await this.apiService.getUserPublicKey(id); diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index f5f5b0df292..92da86b696a 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -137,8 +137,10 @@ export class ListCommand { } private async listFolders(options: Options) { - const activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - let folders = await this.folderService.getAllDecryptedFromState(activeUserId$); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); if (options.search != null && options.search.trim() !== "") { folders = CliUtils.searchFolders(folders, options.search); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 39c0d67c140..ae627e82e75 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -672,11 +672,7 @@ export class ServiceContainer { this.stateProvider, ); - this.folderApiService = new FolderApiService( - this.folderService, - this.apiService, - this.accountService, - ); + this.folderApiService = new FolderApiService(this.folderService, this.apiService); const lockedCallback = async (userId?: string) => await this.keyService.clearStoredUserKey(KeySuffixOptions.Auto); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index c2ffd093c9a..06cfc52d779 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -86,9 +86,7 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); @@ -169,12 +167,12 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { - await this.folderApiService.save(folder); - const newFolder = await this.folderService.get(folder.id, this.activeUserId$); + await this.folderApiService.save(folder, activeUserId); + const newFolder = await this.folderService.get(folder.id, activeUserId); const decFolder = await newFolder.decrypt(); const res = new FolderResponse(decFolder); return Response.success(res); diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 97960c11ba8..6b66b8bc7bb 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -105,14 +105,16 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - const folder = await this.folderService.getFromState(id, activeUserId$); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); } try { - await this.folderApiService.delete(id); + await this.folderApiService.delete(id, activeUserId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts index 68ef95fef6f..635adfcf707 100644 --- a/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts +++ b/apps/web/src/app/key-management/migrate-encryption/migrate-legacy-encryption.component.ts @@ -81,7 +81,7 @@ export class MigrateFromLegacyEncryptionComponent { }); if (deleteFolders) { - await this.folderApiService.deleteAll(); + await this.folderApiService.deleteAll(activeUser.id); await this.syncService.fullSync(true, true); await this.submit(); return; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index fd79fad6a73..f39f254273f 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -62,7 +62,8 @@ export class BulkMoveDialogComponent implements OnInit { } async ngOnInit() { - this.folders$ = this.folderService.folderViews$(this.activeUserId$); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.folders$ = this.folderService.folderViews$(activeUserId); this.formGroup.patchValue({ folderId: (await firstValueFrom(this.folders$))[0].id, }); diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 95f3e60d8c2..33045b30aff 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -59,7 +59,7 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - await this.folderApiService.delete(this.folder.id); + await this.folderApiService.delete(this.folder.id, await firstValueFrom(this.activeUserId$)); this.toastService.showToast({ variant: "success", title: null, @@ -80,10 +80,10 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { } try { - const activeAccountId = (await firstValueFrom(this.accountSerivce.activeAccount$)).id; + const activeAccountId = await firstValueFrom(this.activeUserId$); const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId); const folder = await this.folderService.encrypt(this.folder, userKey); - this.formPromise = this.folderApiService.save(folder); + this.formPromise = this.folderApiService.save(folder, activeAccountId); await this.formPromise; this.platformUtilsService.showToast( "success", diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts index f7536d0f259..d86380b36a6 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.ts @@ -58,14 +58,18 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { protected _organizationFilter = new BehaviorSubject(null); - filteredFolders$: Observable = this.folderService - .folderViews$(this.activeUserId$) - .pipe( - combineLatestWith(this.cipherService.cipherViews$, this._organizationFilter), - switchMap(([folders, ciphers, org]) => { - return this.filterFolders(folders, ciphers, org); - }), - ); + filteredFolders$: Observable = this.activeUserId$.pipe( + switchMap((userId) => + combineLatest([ + this.folderService.folderViews$(userId), + this.cipherService.cipherViews$, + this._organizationFilter, + ]), + ), + switchMap(([folders, ciphers, org]) => { + return this.filterFolders(folders, ciphers, org); + }), + ); folderTree$: Observable> = this.filteredFolders$.pipe( map((folders) => this.buildFolderTree(folders)), ); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 0cae73300de..340e8f567cb 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -514,7 +514,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: FolderApiServiceAbstraction, useClass: FolderApiService, - deps: [InternalFolderService, ApiServiceAbstraction, AccountServiceAbstraction], + deps: [InternalFolderService, ApiServiceAbstraction], }), safeProvider({ provide: AccountApiServiceAbstraction, diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index e053401360f..b2565be6f4b 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -261,14 +261,12 @@ export class AddEditComponent implements OnInit, OnDestroy { const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(); + const activeUserId = await firstValueFrom(this.activeUserId$); if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(); this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - cipher, - await firstValueFrom(this.activeUserId$), - ), + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); // Adjust Cipher Name if Cloning @@ -325,7 +323,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.login.fido2Credentials = null; } - this.folders$ = this.folderService.folderViews$(this.activeUserId$); + this.folders$ = this.folderService.folderViews$(activeUserId); if (this.editMode && this.previousCipherId !== this.cipherId) { void this.eventCollectionService.collectMany(EventType.Cipher_ClientViewed, [this.cipher]); diff --git a/libs/angular/src/vault/components/folder-add-edit.component.ts b/libs/angular/src/vault/components/folder-add-edit.component.ts index 448209aa6f1..74e668b99d7 100644 --- a/libs/angular/src/vault/components/folder-add-edit.component.ts +++ b/libs/angular/src/vault/components/folder-add-edit.component.ts @@ -25,7 +25,7 @@ export class FolderAddEditComponent implements OnInit { deletePromise: Promise; protected componentName = ""; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); formGroup = this.formBuilder.group({ name: ["", [Validators.required]], @@ -59,10 +59,10 @@ export class FolderAddEditComponent implements OnInit { } try { - const activeAccountId = await firstValueFrom(this.accountService.activeAccount$); - const userKey = await this.keyService.getUserKeyWithLegacySupport(activeAccountId.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(this.folder, userKey); - this.formPromise = this.folderApiService.save(folder); + this.formPromise = this.folderApiService.save(folder, activeUserId); await this.formPromise; this.platformUtilsService.showToast( "success", @@ -90,7 +90,8 @@ export class FolderAddEditComponent implements OnInit { } try { - this.deletePromise = this.folderApiService.delete(this.folder.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.deletePromise = this.folderApiService.delete(this.folder.id, activeUserId); await this.deletePromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedFolder")); this.onDeletedFolder.emit(this.folder); @@ -107,8 +108,9 @@ export class FolderAddEditComponent implements OnInit { if (this.editMode) { this.editMode = true; this.title = this.i18nService.t("editFolder"); + const activeUserId = await firstValueFrom(this.activeUserId$); this.folder = await firstValueFrom( - this.folderService.getDecrypted$(this.folderId, this.activeUserId$), + this.folderService.getDecrypted$(this.folderId, activeUserId), ); } else { this.title = this.i18nService.t("addFolder"); diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 07eede86321..21c83c463de 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -141,9 +141,7 @@ export class ViewComponent implements OnDestroy, OnInit { this.cleanUp(); const cipher = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -158,7 +156,7 @@ export class ViewComponent implements OnDestroy, OnInit { if (this.cipher.folderId) { this.folder = await ( - await firstValueFrom(this.folderService.folderViews$(this.activeUserId$)) + await firstValueFrom(this.folderService.folderViews$(activeUserId)) ).find((f) => f.id == this.cipher.folderId); } diff --git a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts index 2ecff42a7f2..4317e84b1f9 100644 --- a/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts +++ b/libs/angular/src/vault/vault-filter/services/vault-filter.service.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { firstValueFrom, from, map, mergeMap, Observable } from "rxjs"; +import { firstValueFrom, from, map, mergeMap, Observable, switchMap } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { @@ -83,9 +83,10 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti }); }; - return this.folderService - .folderViews$(this.activeUserId$) - .pipe(mergeMap((folders) => from(transformation(folders)))); + return this.activeUserId$.pipe( + switchMap((userId) => this.folderService.folderViews$(userId)), + mergeMap((folders) => from(transformation(folders))), + ); } async buildCollections(organizationId?: string): Promise> { @@ -128,8 +129,9 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async getFolderNested(id: string): Promise> { + const activeUserId = await firstValueFrom(this.activeUserId$); const folders = await this.getAllFoldersNested( - await firstValueFrom(this.folderService.folderViews$(this.activeUserId$)), + await firstValueFrom(this.folderService.folderViews$(activeUserId)), ); return ServiceUtils.getTreeNodeObjectFromList(folders, id) as TreeNode; } diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index 7a3be90350a..92d5da65223 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -87,23 +87,20 @@ export abstract class CoreSyncService implements SyncService { async syncUpsertFolder(notification: SyncFolderNotification, isEdit: boolean): Promise { this.syncStarted(); - const authStatus = await firstValueFrom( - this.authService.authStatusFor$(await firstValueFrom(this.activeUserId$)), - ); + + const activeUserId = await firstValueFrom(this.activeUserId$); + const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId)); if (authStatus >= AuthenticationStatus.Locked) { try { - const localFolder = await this.folderService.get(notification.id, this.activeUserId$); + const localFolder = await this.folderService.get(notification.id, activeUserId); if ( (!isEdit && localFolder == null) || (isEdit && localFolder != null && localFolder.revisionDate < notification.revisionDate) ) { const remoteFolder = await this.folderApiService.get(notification.id); if (remoteFolder != null) { - await this.folderService.upsert( - new FolderData(remoteFolder), - await firstValueFrom(this.activeUserId$), - ); + await this.folderService.upsert(new FolderData(remoteFolder), activeUserId); this.messageSender.send("syncedUpsertedFolder", { folderId: notification.id }); return this.syncCompleted(true); } @@ -117,12 +114,12 @@ export abstract class CoreSyncService implements SyncService { async syncDeleteFolder(notification: SyncFolderNotification): Promise { this.syncStarted(); - const authStatus = await firstValueFrom( - this.authService.authStatusFor$(await firstValueFrom(this.activeUserId$)), - ); + + const activeUserId = await firstValueFrom(this.activeUserId$); + const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId)); if (authStatus >= AuthenticationStatus.Locked) { - await this.folderService.delete(notification.id, await firstValueFrom(this.activeUserId$)); + await this.folderService.delete(notification.id, activeUserId); this.messageSender.send("syncedDeletedFolder", { folderId: notification.id }); this.syncCompleted(true); return true; diff --git a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts index 1762400b3dd..b975e6e61ed 100644 --- a/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder-api.service.abstraction.ts @@ -1,9 +1,11 @@ +import { UserId } from "@bitwarden/common/types/guid"; + import { Folder } from "../../models/domain/folder"; import { FolderResponse } from "../../models/response/folder.response"; export class FolderApiServiceAbstraction { - save: (folder: Folder) => Promise; - delete: (id: string) => Promise; + save: (folder: Folder, userId: UserId) => Promise; + delete: (id: string, userId: UserId) => Promise; get: (id: string) => Promise; - deleteAll: () => Promise; + deleteAll: (userId: UserId) => Promise; } diff --git a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts index 8cb9a0b296b..c7527bb35d1 100644 --- a/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts +++ b/libs/common/src/vault/abstractions/folder/folder.service.abstraction.ts @@ -11,27 +11,27 @@ import { FolderWithIdRequest } from "../../models/request/folder-with-id.request import { FolderView } from "../../models/view/folder.view"; export abstract class FolderService implements UserKeyRotationDataProvider { - folders$: (userId$: Observable) => Observable; - folderViews$: (userId$: Observable) => Observable; + folders$: (userId: UserId) => Observable; + folderViews$: (userId: UserId) => Observable; clearDecryptedFolderState: (userId: UserId) => Promise; encrypt: (model: FolderView, key: SymmetricCryptoKey) => Promise; - get: (id: string, userId$: Observable) => Promise; - getDecrypted$: (id: string, userId$: Observable) => Observable; + get: (id: string, userId: UserId) => Promise; + getDecrypted$: (id: string, userId: UserId) => Observable; /** * @deprecated Use firstValueFrom(folders$) directly instead - * @param userId$ The observable of user ID + * @param userId The user id * @returns Promise of folders array */ - getAllFromState: (userId$: Observable) => Promise; + getAllFromState: (userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getFromState: (id: string, userId$: Observable) => Promise; + getFromState: (id: string, userId: UserId) => Promise; /** * @deprecated Only use in CLI! */ - getAllDecryptedFromState: (userId$: Observable) => Promise; + getAllDecryptedFromState: (userId: UserId) => Promise; /** * Returns user folders re-encrypted with the new user key. * @param originalUserKey the original user key diff --git a/libs/common/src/vault/services/folder/folder-api.service.ts b/libs/common/src/vault/services/folder/folder-api.service.ts index 84bfa7a60e4..24831393668 100644 --- a/libs/common/src/vault/services/folder/folder-api.service.ts +++ b/libs/common/src/vault/services/folder/folder-api.service.ts @@ -1,6 +1,4 @@ -import { firstValueFrom, map } from "rxjs"; - -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { ApiService } from "../../../abstractions/api.service"; import { FolderApiServiceAbstraction } from "../../../vault/abstractions/folder/folder-api.service.abstraction"; @@ -11,15 +9,12 @@ import { FolderRequest } from "../../../vault/models/request/folder.request"; import { FolderResponse } from "../../../vault/models/response/folder.response"; export class FolderApiService implements FolderApiServiceAbstraction { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private folderService: InternalFolderService, private apiService: ApiService, - private accountService: AccountService, ) {} - async save(folder: Folder): Promise { + async save(folder: Folder, userId: UserId): Promise { const request = new FolderRequest(folder); let response: FolderResponse; @@ -31,17 +26,17 @@ export class FolderApiService implements FolderApiServiceAbstraction { } const data = new FolderData(response); - await this.folderService.upsert(data, await firstValueFrom(this.activeUserId$)); + await this.folderService.upsert(data, userId); } - async delete(id: string): Promise { + async delete(id: string, userId: UserId): Promise { await this.deleteFolder(id); - await this.folderService.delete(id, await firstValueFrom(this.activeUserId$)); + await this.folderService.delete(id, userId); } - async deleteAll(): Promise { + async deleteAll(userId: UserId): Promise { await this.apiService.send("DELETE", "/folders/all", null, true, false); - await this.folderService.clear(await firstValueFrom(this.activeUserId$)); + await this.folderService.clear(userId); } async get(id: string): Promise { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 0b382ce5692..6374395922a 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -1,5 +1,5 @@ import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, firstValueFrom, of } from "rxjs"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; import { KeyService } from "../../../../../key-management/src/abstractions/key.service"; import { makeEncString } from "../../../../spec"; @@ -29,7 +29,6 @@ describe("Folder Service", () => { let stateProvider: FakeStateProvider; const mockUserId = Utils.newGuid() as UserId; - const mockUserId$ = of(mockUserId); let accountService: FakeAccountService; let folderState: FakeSingleUserState>; @@ -73,7 +72,7 @@ describe("Folder Service", () => { mockUserId, ); - const result = await firstValueFrom(folderService.folders$(mockUserId$)); + const result = await firstValueFrom(folderService.folders$(mockUserId)); expect(result.length).toBe(2); expect(result).toIncludeAllPartialMembers([ @@ -94,7 +93,7 @@ describe("Folder Service", () => { mockUserId, ); - const result = await firstValueFrom(folderService.folderViews$(mockUserId$)); + const result = await firstValueFrom(folderService.folderViews$(mockUserId)); expect(result.length).toBe(3); expect(result).toIncludeAllPartialMembers([ @@ -125,7 +124,7 @@ describe("Folder Service", () => { describe("get", () => { it("exists", async () => { - const result = await folderService.get("1", mockUserId$); + const result = await folderService.get("1", mockUserId); expect(result).toEqual({ id: "1", @@ -135,7 +134,7 @@ describe("Folder Service", () => { }); it("not exists", async () => { - const result = await folderService.get("2", mockUserId$); + const result = await folderService.get("2", mockUserId); expect(result).toBe(undefined); }); @@ -144,7 +143,7 @@ describe("Folder Service", () => { it("upsert", async () => { await folderService.upsert(folderData("2"), mockUserId); - expect(await firstValueFrom(folderService.folders$(mockUserId$))).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { id: "1", name: makeEncString("ENC_STRING_" + 1), @@ -161,7 +160,7 @@ describe("Folder Service", () => { it("replace", async () => { await folderService.replace({ "4": folderData("4") }, mockUserId); - expect(await firstValueFrom(folderService.folders$(mockUserId$))).toEqual([ + expect(await firstValueFrom(folderService.folders$(mockUserId))).toEqual([ { id: "4", name: makeEncString("ENC_STRING_" + 4), @@ -173,7 +172,7 @@ describe("Folder Service", () => { it("delete", async () => { await folderService.delete("1", mockUserId); - expect((await firstValueFrom(folderService.folders$(mockUserId$))).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); }); describe("clearDecryptedFolderState", () => { @@ -186,16 +185,16 @@ describe("Folder Service", () => { it("userId provided", async () => { await folderService.clearDecryptedFolderState(mockUserId); - expect((await firstValueFrom(folderService.folders$(mockUserId$))).length).toBe(1); - expect((await firstValueFrom(folderService.folderViews$(mockUserId$))).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(1); + expect((await firstValueFrom(folderService.folderViews$(mockUserId))).length).toBe(0); }); }); it("clear", async () => { await folderService.clear(mockUserId); - expect((await firstValueFrom(folderService.folders$(mockUserId$))).length).toBe(0); - expect((await firstValueFrom(folderService.folderViews$(mockUserId$))).length).toBe(0); + expect((await firstValueFrom(folderService.folders$(mockUserId))).length).toBe(0); + expect((await firstValueFrom(folderService.folderViews$(mockUserId))).length).toBe(0); }); describe("getRotatedData", () => { diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 984e49c4f41..34104fc92e2 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -1,4 +1,4 @@ -import { Observable, firstValueFrom, map, of, shareReplay, switchMap, takeWhile } from "rxjs"; +import { Observable, firstValueFrom, map, shareReplay } from "rxjs"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -26,10 +26,8 @@ export class FolderService implements InternalFolderServiceAbstraction { private stateProvider: StateProvider, ) {} - folders$(userId$: Observable): Observable { - return userId$.pipe( - takeWhile((userId) => userId != null), - switchMap((userId) => this.encryptedFoldersState(userId).state$), + folders$(userId: UserId): Observable { + return this.encryptedFoldersState(userId).state$.pipe( map((folders) => { if (folders == null) { return []; @@ -40,11 +38,8 @@ export class FolderService implements InternalFolderServiceAbstraction { ); } - folderViews$(userId$: Observable): Observable { - return userId$.pipe( - takeWhile((userId) => userId != null), - switchMap((userId) => this.decryptedFoldersState(userId).state$), - ); + folderViews$(userId: UserId): Observable { + return this.decryptedFoldersState(userId).state$; } async clearDecryptedFolderState(userId: UserId): Promise { @@ -63,29 +58,29 @@ export class FolderService implements InternalFolderServiceAbstraction { return folder; } - async get(id: string, userId$: Observable): Promise { - const folders = await firstValueFrom(this.folders$(userId$)); + async get(id: string, userId: UserId): Promise { + const folders = await firstValueFrom(this.folders$(userId)); return folders.find((folder) => folder.id === id); } - getDecrypted$(id: string, userId$: Observable): Observable { - return this.folderViews$(userId$).pipe( + getDecrypted$(id: string, userId: UserId): Observable { + return this.folderViews$(userId).pipe( map((folders) => folders.find((folder) => folder.id === id)), shareReplay({ refCount: true, bufferSize: 1 }), ); } - async getAllFromState(userId$: Observable): Promise { - return await firstValueFrom(this.folders$(userId$)); + async getAllFromState(userId: UserId): Promise { + return await firstValueFrom(this.folders$(userId)); } /** * @deprecated For the CLI only * @param id id of the folder */ - async getFromState(id: string, userId$: Observable): Promise { - const folder = await this.get(id, userId$); + async getFromState(id: string, userId: UserId): Promise { + const folder = await this.get(id, userId); if (!folder) { return null; } @@ -96,8 +91,8 @@ export class FolderService implements InternalFolderServiceAbstraction { /** * @deprecated Only use in CLI! */ - async getAllDecryptedFromState(userId$: Observable): Promise { - return await firstValueFrom(this.folderViews$(userId$)); + async getAllDecryptedFromState(userId: UserId): Promise { + return await firstValueFrom(this.folderViews$(userId)); } async upsert(folderData: FolderData | FolderData[], userId: UserId): Promise { @@ -178,7 +173,7 @@ export class FolderService implements InternalFolderServiceAbstraction { } let encryptedFolders: FolderWithIdRequest[] = []; - const folders = await firstValueFrom(this.folderViews$(of(userId))); + const folders = await firstValueFrom(this.folderViews$(userId)); if (!folders) { return encryptedFolders; } diff --git a/libs/importer/src/components/import.component.ts b/libs/importer/src/components/import.component.ts index e746460b153..4f79252572e 100644 --- a/libs/importer/src/components/import.component.ts +++ b/libs/importer/src/components/import.component.ts @@ -14,7 +14,7 @@ import { import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import * as JSZip from "jszip"; import { concat, Observable, Subject, lastValueFrom, combineLatest, firstValueFrom } from "rxjs"; -import { filter, map, takeUntil } from "rxjs/operators"; +import { filter, map, switchMap, takeUntil } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -259,9 +259,12 @@ export class ImportComponent implements OnInit, OnDestroy, AfterViewInit { private handleImportInit() { // Filter out the no folder-item from folderViews$ - this.folders$ = this.folderService - .folderViews$(this.activeUserId$) - .pipe(map((folders) => folders.filter((f) => f.id != null))); + this.folders$ = this.activeUserId$.pipe( + switchMap((userId) => { + return this.folderService.folderViews$(userId); + }), + map((folders) => folders.filter((f) => f.id != null)), + ); this.formGroup.controls.targetSelector.disable(); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index c1ebd3b4d04..113b68eebc3 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -62,9 +62,10 @@ export class IndividualVaultExportService let decFolders: FolderView[] = []; let decCiphers: CipherView[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( - firstValueFrom(this.folderService.folderViews$(this.activeUserId$)).then((folders) => { + firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => { decFolders = folders; }), ); @@ -88,9 +89,10 @@ export class IndividualVaultExportService let folders: Folder[] = []; let ciphers: Cipher[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( - firstValueFrom(this.folderService.folders$(this.activeUserId$)).then((f) => { + firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => { folders = f; }), ); diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts index d77e06b13c8..fef118c1ed9 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-config.service.ts @@ -39,6 +39,8 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { cipherId?: CipherId, cipherType?: CipherType, ): Promise { + const activeUserId = await firstValueFrom(this.activeUserId$); + const [organizations, collections, allowPersonalOwnership, folders, cipher] = await firstValueFrom( combineLatest([ @@ -51,9 +53,9 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { ), ), this.allowPersonalOwnership$, - this.folderService.folders$(this.activeUserId$).pipe( + this.folderService.folders$(activeUserId).pipe( switchMap((f) => - this.folderService.folderViews$(this.activeUserId$).pipe( + this.folderService.folderViews$(activeUserId).pipe( filter((d) => d.length - 1 === f.length), // -1 for "No Folder" in folderViews$ ), ), diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 25c1ca1aea5..bdf9e166e12 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -126,8 +126,9 @@ export class CipherViewComponent implements OnChanges, OnDestroy { } if (this.cipher.folderId) { + const activeUserId = await firstValueFrom(this.activeUserId$); this.folder$ = this.folderService - .getDecrypted$(this.cipher.folderId, this.activeUserId$) + .getDecrypted$(this.cipher.folderId, activeUserId) .pipe(takeUntil(this.destroyed$)); } }