From c861612d7672a4b4b84f912623a7c3d7106af183 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Wed, 8 Jan 2025 17:26:55 -0500 Subject: [PATCH 01/17] Cipher service web changes --- .../exposed-passwords-report.component.ts | 9 +++++- .../inactive-two-factor-report.component.ts | 3 ++ .../reused-passwords-report.component.ts | 9 +++++- .../unsecured-websites-report.component.ts | 3 ++ .../tools/weak-passwords-report.component.ts | 9 +++++- .../settings/change-password.component.ts | 5 +++- .../reports/pages/cipher-report.component.ts | 8 +++-- ...exposed-passwords-report.component.spec.ts | 9 ++++++ .../exposed-passwords-report.component.ts | 3 ++ ...active-two-factor-report.component.spec.ts | 9 ++++++ .../inactive-two-factor-report.component.ts | 3 ++ .../reused-passwords-report.component.spec.ts | 9 ++++++ .../reused-passwords-report.component.ts | 3 ++ ...nsecured-websites-report.component.spec.ts | 9 ++++++ .../unsecured-websites-report.component.ts | 3 ++ .../weak-passwords-report.component.spec.ts | 9 ++++++ .../pages/weak-passwords-report.component.ts | 3 ++ .../vault-item-dialog.component.ts | 13 +++++++-- .../bulk-delete-dialog.component.ts | 12 ++++++-- .../bulk-move-dialog.component.ts | 7 ++++- .../services/vault-filter.service.spec.ts | 2 +- .../services/vault-filter.service.ts | 2 +- .../vault/individual-vault/vault.component.ts | 29 ++++++++++--------- .../vault/individual-vault/view.component.ts | 12 ++++++-- .../app/vault/org-vault/add-edit.component.ts | 7 +++-- .../vault/org-vault/attachments.component.ts | 8 +++-- ...console-cipher-form-config.service.spec.ts | 7 +++-- ...dmin-console-cipher-form-config.service.ts | 8 ++++- .../app/vault/org-vault/vault.component.ts | 23 +++++++++++---- 29 files changed, 192 insertions(+), 44 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts index baa49b8b13d..fa9d13e200a 100644 --- a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts @@ -2,10 +2,12 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -35,6 +37,7 @@ export class ExposedPasswordsReportComponent private route: ActivatedRoute, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -44,16 +47,20 @@ export class ExposedPasswordsReportComponent modalService, passwordRepromptService, i18nService, + accountService, syncService, ); } async ngOnInit() { this.isAdminConsoleActive = true; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(activeUserId); }); } diff --git a/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts index 461e7691faa..5be8d1e3083 100644 --- a/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/inactive-two-factor-report.component.ts @@ -5,6 +5,7 @@ import { ActivatedRoute } from "@angular/router"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -32,6 +33,7 @@ export class InactiveTwoFactorReportComponent passwordRepromptService: PasswordRepromptService, organizationService: OrganizationService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -41,6 +43,7 @@ export class InactiveTwoFactorReportComponent logService, passwordRepromptService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts index 176fad24d24..e58ced30bf8 100644 --- a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts @@ -2,9 +2,11 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -33,6 +35,7 @@ export class ReusedPasswordsReportComponent organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -41,16 +44,20 @@ export class ReusedPasswordsReportComponent modalService, passwordRepromptService, i18nService, + accountService, syncService, ); } async ngOnInit() { this.isAdminConsoleActive = true; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(activeUserId); await super.ngOnInit(); }); } diff --git a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts index 631890a9767..979b1475110 100644 --- a/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/unsecured-websites-report.component.ts @@ -6,6 +6,7 @@ import { ActivatedRoute } from "@angular/router"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -31,6 +32,7 @@ export class UnsecuredWebsitesReportComponent organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, collectionService: CollectionService, ) { @@ -40,6 +42,7 @@ export class UnsecuredWebsitesReportComponent modalService, passwordRepromptService, i18nService, + accountService, syncService, collectionService, ); diff --git a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts index d65682d6623..c6d65849487 100644 --- a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts @@ -2,9 +2,11 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -35,6 +37,7 @@ export class WeakPasswordsReportComponent organizationService: OrganizationService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -44,16 +47,20 @@ export class WeakPasswordsReportComponent modalService, passwordRepromptService, i18nService, + accountService, syncService, ); } async ngOnInit() { this.isAdminConsoleActive = true; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { this.organization = await this.organizationService.get(params.organizationId); - this.manageableCiphers = await this.cipherService.getAll(); + this.manageableCiphers = await this.cipherService.getAll(activeUserId); await super.ngOnInit(); }); } diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index 8f0f195440a..75c299fae5d 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -93,7 +93,10 @@ export class ChangePasswordComponent async rotateUserKeyClicked() { if (this.rotateUserKey) { - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); let hasOldAttachments = false; if (ciphers != null) { for (let i = 0; i < ciphers.length; i++) { diff --git a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts index b1a46bd13a8..9cb64d311cf 100644 --- a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts @@ -1,11 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core"; -import { BehaviorSubject, Observable, Subject, takeUntil } from "rxjs"; +import { BehaviorSubject, Observable, Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -38,6 +39,7 @@ export class CipherReportComponent implements OnDestroy { vaultMsg: string = "vault"; currentFilterStatus: number | string; protected filterOrgStatus$ = new BehaviorSubject(0); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroyed$: Subject = new Subject(); constructor( @@ -46,6 +48,7 @@ export class CipherReportComponent implements OnDestroy { protected passwordRepromptService: PasswordRepromptService, protected organizationService: OrganizationService, protected i18nService: I18nService, + protected accountService: AccountService, private syncService: SyncService, ) { this.organizations$ = this.organizationService.organizations$; @@ -178,7 +181,8 @@ export class CipherReportComponent implements OnDestroy { } protected async getAllCiphers(): Promise { - return await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.activeUserId$); + return await this.cipherService.getAllDecrypted(activeUserId); } protected filterCiphersByOrg(ciphersList: CipherView[]) { diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts index 07dc218bd64..a26e842b13e 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.spec.ts @@ -7,7 +7,10 @@ import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -22,6 +25,8 @@ describe("ExposedPasswordsReportComponent", () => { let organizationService: MockProxy; let syncServiceMock: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("userid-1" as UserId); + beforeEach(() => { syncServiceMock = mock(); auditService = mock(); @@ -60,6 +65,10 @@ describe("ExposedPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts index 13d2804c5e5..ee2dddf8f66 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -28,6 +29,7 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -36,6 +38,7 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple passwordRepromptService, organizationService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.spec.ts index 80760eb5dec..d7af3231216 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.spec.ts @@ -6,8 +6,11 @@ import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -21,6 +24,8 @@ describe("InactiveTwoFactorReportComponent", () => { let organizationService: MockProxy; let syncServiceMock: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("userid-1" as UserId); + beforeEach(() => { organizationService = mock(); organizationService.organizations$ = of([]); @@ -58,6 +63,10 @@ describe("InactiveTwoFactorReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts index 792ad0616f2..b2514882fc8 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts @@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -31,6 +32,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl private logService: LogService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -39,6 +41,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl passwordRepromptService, organizationService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts index 9d16bbb1c62..a0e1ce6bbe6 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.spec.ts @@ -6,7 +6,10 @@ import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -20,6 +23,8 @@ describe("ReusedPasswordsReportComponent", () => { let organizationService: MockProxy; let syncServiceMock: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("userid-1" as UserId); + beforeEach(() => { organizationService = mock(); organizationService.organizations$ = of([]); @@ -53,6 +58,10 @@ describe("ReusedPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts index a8806acea13..73d7e116999 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts @@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -27,6 +28,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -35,6 +37,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem passwordRepromptService, organizationService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts index 5f66814fdf1..476da221a34 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.spec.ts @@ -7,7 +7,10 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -22,6 +25,8 @@ describe("UnsecuredWebsitesReportComponent", () => { let syncServiceMock: MockProxy; let collectionService: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("userid-1" as UserId); + beforeEach(() => { organizationService = mock(); organizationService.organizations$ = of([]); @@ -60,6 +65,10 @@ describe("UnsecuredWebsitesReportComponent", () => { provide: CollectionService, useValue: collectionService, }, + { + provide: AccountService, + useValue: accountService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts index 6a1ba1f6333..de8db5871b6 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; import { CollectionService, Collection } from "@bitwarden/admin-console/common"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -25,6 +26,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, private collectionService: CollectionService, ) { @@ -34,6 +36,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl passwordRepromptService, organizationService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts index bcace60ac0c..f202d7a9cb5 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.spec.ts @@ -6,8 +6,11 @@ import { of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -22,6 +25,8 @@ describe("WeakPasswordsReportComponent", () => { let organizationService: MockProxy; let syncServiceMock: MockProxy; + const accountService: FakeAccountService = mockAccountServiceWith("userid-1" as UserId); + beforeEach(() => { syncServiceMock = mock(); passwordStrengthService = mock(); @@ -60,6 +65,10 @@ describe("WeakPasswordsReportComponent", () => { provide: I18nService, useValue: mock(), }, + { + provide: AccountService, + useValue: accountService, + }, ], schemas: [], }).compileComponents(); diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index f3ad6840c8b..509fe737ece 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -4,6 +4,7 @@ import { Component, OnInit } from "@angular/core"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; @@ -35,6 +36,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, + accountService: AccountService, syncService: SyncService, ) { super( @@ -43,6 +45,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen passwordRepromptService, organizationService, i18nService, + accountService, syncService, ); } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 02dc5ef48bb..e340e84e4b7 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -285,8 +285,11 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.formConfig.mode = "edit"; this.formConfig.initialValues = null; } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); - let cipher = await this.cipherService.get(cipherView.id); + let cipher = await this.cipherService.get(cipherView.id, activeUserId); // When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state) if (this.formConfig.isAdminConsole && (cipher == null || this.formConfig.admin)) { @@ -480,10 +483,14 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { // - The cipher is unassigned const asAdmin = this.organization?.canEditAllCiphers || cipherIsUnassigned; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (this.cipher.isDeleted) { - await this.cipherService.deleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.deleteWithServer(this.cipher.id, activeUserId, asAdmin); } else { - await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.softDeleteWithServer(this.cipher.id, activeUserId, asAdmin); } } diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 913f106004d..037f4ec9477 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -2,10 +2,12 @@ // @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -60,6 +62,7 @@ export class BulkDeleteDialogComponent { private i18nService: I18nService, private apiService: ApiService, private collectionService: CollectionService, + private accountService: AccountService, ) { this.cipherIds = params.cipherIds ?? []; this.permanent = params.permanent; @@ -114,10 +117,15 @@ export class BulkDeleteDialogComponent { private async deleteCiphers(): Promise { const asAdmin = this.organization?.canEditAllCiphers; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (this.permanent) { - await this.cipherService.deleteManyWithServer(this.cipherIds, asAdmin); + await this.cipherService.deleteManyWithServer(this.cipherIds, activeUserId, asAdmin); } else { - await this.cipherService.softDeleteManyWithServer(this.cipherIds, asAdmin); + await this.cipherService.softDeleteManyWithServer(this.cipherIds, activeUserId, asAdmin); } } 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 b7f99fb7b44..745e1e0a582 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 @@ -80,7 +80,12 @@ export class BulkMoveDialogComponent implements OnInit { return; } - await this.cipherService.moveManyWithServer(this.cipherIds, this.formGroup.value.folderId); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.cipherService.moveManyWithServer( + this.cipherIds, + this.formGroup.value.folderId, + activeUserId, + ); this.platformUtilsService.showToast("success", null, this.i18nService.t("movedItems")); this.close(BulkMoveDialogResult.Moved); }; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts index 47003d51cae..afecb88f4d2 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/services/vault-filter.service.spec.ts @@ -71,7 +71,7 @@ describe("vault filter service", () => { policyService.policyAppliesToActiveUser$ .calledWith(PolicyType.SingleOrg) .mockReturnValue(singleOrgPolicy); - cipherService.cipherViews$ = cipherViews; + cipherService.cipherViews$.mockReturnValue(cipherViews); vaultFilterService = new VaultFilterService( organizationService, 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 97b44132e60..f20d060b6a2 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 @@ -64,7 +64,7 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { switchMap((userId) => combineLatest([ this.folderService.folderViews$(userId), - this.cipherService.cipherViews$, + this.cipherService.cipherViews$(userId), this._organizationFilter, ]), ), diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 18a1d8b338a..4626dea70a5 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -182,13 +182,14 @@ export class VaultComponent implements OnInit, OnDestroy { protected selectedCollection: TreeNode | undefined; protected canCreateCollections = false; protected currentSearchText$: Observable; - private activeUserId: UserId; private searchText$ = new Subject(); private refresh$ = new BehaviorSubject(null); private destroy$ = new Subject(); private extensionRefreshEnabled: boolean; private hasSubscription$ = new BehaviorSubject(false); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + private vaultItemDialogRef?: DialogRef | undefined; private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( filter((organizations) => organizations.length === 1), @@ -287,9 +288,7 @@ export class VaultComponent implements OnInit, OnDestroy { : "trashCleanupWarning", ); - this.activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const firstSetup$ = this.route.queryParams.pipe( first(), @@ -353,7 +352,7 @@ export class VaultComponent implements OnInit, OnDestroy { this.currentSearchText$ = this.route.queryParams.pipe(map((queryParams) => queryParams.search)); const ciphers$ = combineLatest([ - this.cipherService.cipherViews$.pipe(filter((c) => c !== null)), + this.cipherService.cipherViews$(activeUserId).pipe(filter((c) => c !== null)), filter$, this.currentSearchText$, ]).pipe( @@ -429,7 +428,7 @@ export class VaultComponent implements OnInit, OnDestroy { switchMap(async (params) => { const cipherId = getCipherIdFromParams(params); if (cipherId) { - if (await this.cipherService.get(cipherId)) { + if (await this.cipherService.get(cipherId, activeUserId)) { let action = params.action; // Default to "view" if extension refresh is enabled if (action == null && this.extensionRefreshEnabled) { @@ -768,7 +767,8 @@ export class VaultComponent implements OnInit, OnDestroy { } async editCipherId(id: string, cloneMode?: boolean) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipher = await this.cipherService.get(id, activeUserId); if ( cipher && @@ -847,7 +847,8 @@ export class VaultComponent implements OnInit, OnDestroy { * @returns Promise */ async viewCipherById(id: string) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipher = await this.cipherService.get(id, activeUserId); // If cipher exists (cipher is null when new) and MP reprompt // is on for this cipher, then show password reprompt. if ( @@ -1040,7 +1041,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.cipherService.restoreWithServer(c.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.cipherService.restoreWithServer(c.id, activeUserId); this.toastService.showToast({ variant: "success", title: null, @@ -1124,7 +1126,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.deleteCipherWithServer(c.id, permanent); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.deleteCipherWithServer(c.id, activeUserId, permanent); this.toastService.showToast({ variant: "success", @@ -1259,10 +1262,10 @@ export class VaultComponent implements OnInit, OnDestroy { } } - protected deleteCipherWithServer(id: string, permanent: boolean) { + protected deleteCipherWithServer(id: string, userId: UserId, permanent: boolean) { return permanent - ? this.cipherService.deleteWithServer(id) - : this.cipherService.softDeleteWithServer(id); + ? this.cipherService.deleteWithServer(id, userId) + : this.cipherService.softDeleteWithServer(id, userId); } protected async repromptCipher(ciphers: CipherView[]) { diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index e9ca2bf8f8c..fdf01a27514 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -3,11 +3,12 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit } from "@angular/core"; -import { Observable } from "rxjs"; +import { Observable, firstValueFrom, map } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -94,6 +95,7 @@ export class ViewComponent implements OnInit { private toastService: ToastService, private organizationService: OrganizationService, private cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) {} /** @@ -153,10 +155,14 @@ export class ViewComponent implements OnInit { */ protected async deleteCipher(): Promise { const asAdmin = this.organization?.canEditAllCiphers; + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (this.cipher.isDeleted) { - await this.cipherService.deleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.deleteWithServer(this.cipher.id, activeUserId, asAdmin); } else { - await this.cipherService.softDeleteWithServer(this.cipher.id, asAdmin); + await this.cipherService.softDeleteWithServer(this.cipher.id, activeUserId, asAdmin); } } diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 135db7f46f4..0ad19637790 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { DatePipe } from "@angular/common"; import { Component } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -93,8 +94,9 @@ export class AddEditComponent extends BaseAddEditComponent { protected async loadCipher() { this.isAdminConsoleAction = true; + const activeUserId = await firstValueFrom(this.activeUserId$); // Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin - const firstCipherCheck = await super.loadCipher(); + const firstCipherCheck = await super.loadCipher(activeUserId); if (!this.organization.canEditAllCiphers && firstCipherCheck != null) { return firstCipherCheck; @@ -118,7 +120,8 @@ export class AddEditComponent extends BaseAddEditComponent { protected async deleteCipher() { if (!this.organization.canEditAllCiphers) { - return super.deleteCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + return super.deleteCipher(activeUserId); } return this.cipher.isDeleted ? this.apiService.deleteCipherAdmin(this.cipherId) diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index c8badffb36f..81f36449368 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -74,7 +75,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On protected async loadCipher() { if (!this.organization.canEditAllCiphers) { - return await super.loadCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + return await super.loadCipher(activeUserId); } const response = await this.apiService.getCipherAdmin(this.cipherId); return new Cipher(new CipherData(response)); @@ -89,9 +91,9 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On ); } - protected deleteCipherAttachment(attachmentId: string) { + protected deleteCipherAttachment(attachmentId: string, userId: UserId) { if (!this.organization.canEditAllCiphers) { - return super.deleteCipherAttachment(attachmentId); + return super.deleteCipherAttachment(attachmentId, userId); } return this.apiService.deleteCipherAttachmentAdmin(this.cipherId, attachmentId); } diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts index 25976c4fb82..98280b2f7ab 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.spec.ts @@ -7,7 +7,9 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; @@ -80,6 +82,7 @@ describe("AdminConsoleCipherFormConfigService", () => { }, { provide: ApiService, useValue: { getCipherAdmin } }, { provide: CipherService, useValue: { get: getCipher } }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }); adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService); @@ -196,7 +199,7 @@ describe("AdminConsoleCipherFormConfigService", () => { await adminConsoleConfigService.buildConfig("edit", cipherId); expect(getCipherAdmin).not.toHaveBeenCalled(); - expect(getCipher).toHaveBeenCalledWith(cipherId); + expect(getCipher).toHaveBeenCalledWith(cipherId, "UserId"); }); }); }); diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts index 0d3db55d3d6..e28ad30589b 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts @@ -9,6 +9,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -31,6 +32,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ private collectionAdminService: CollectionAdminService = inject(CollectionAdminService); private cipherService: CipherService = inject(CipherService); private apiService: ApiService = inject(ApiService); + private accountService: AccountService = inject(AccountService); private allowPersonalOwnership$ = this.policyService .policyAppliesToActiveUser$(PolicyType.PersonalOwnership) @@ -98,7 +100,11 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ return null; } - const localCipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const localCipher = await this.cipherService.get(id, activeUserId); // Fetch from the API because we don't need the permissions in local state OR the cipher was not found (e.g. unassigned) if (organization.canEditAllCiphers || localCipher == null) { diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 462254a4090..9aac35380f0 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -50,6 +50,7 @@ import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; @@ -62,7 +63,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -231,6 +232,8 @@ export class VaultComponent implements OnInit, OnDestroy { ), ); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private route: ActivatedRoute, private organizationService: OrganizationService, @@ -266,6 +269,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected billingApiService: BillingApiServiceAbstraction, private organizationBillingService: OrganizationBillingServiceAbstraction, private resellerWarningService: ResellerWarningService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -1032,8 +1036,9 @@ export class VaultComponent implements OnInit, OnDestroy { // Allow restore of an Unassigned Item try { + const activeUserId = await firstValueFrom(this.activeUserId$); const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned; - await this.cipherService.restoreWithServer(c.id, asAdmin); + await this.cipherService.restoreWithServer(c.id, activeUserId, asAdmin); this.toastService.showToast({ variant: "success", title: null, @@ -1124,7 +1129,8 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - await this.deleteCipherWithServer(c.id, permanent, c.isUnassigned); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.deleteCipherWithServer(c.id, activeUserId, permanent, c.isUnassigned); this.toastService.showToast({ variant: "success", title: null, @@ -1410,11 +1416,16 @@ export class VaultComponent implements OnInit, OnDestroy { }); } - protected deleteCipherWithServer(id: string, permanent: boolean, isUnassigned: boolean) { + protected deleteCipherWithServer( + id: string, + userId: UserId, + permanent: boolean, + isUnassigned: boolean, + ) { const asAdmin = this.organization?.canEditAllCiphers || isUnassigned; return permanent - ? this.cipherService.deleteWithServer(id, asAdmin) - : this.cipherService.softDeleteWithServer(id, asAdmin); + ? this.cipherService.deleteWithServer(id, userId, asAdmin) + : this.cipherService.softDeleteWithServer(id, userId, asAdmin); } protected async repromptCipher(ciphers: CipherView[]) { From bf63c594fc4b3e648c0f7de2a3cb098a72a2b442 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Thu, 9 Jan 2025 22:09:46 -0500 Subject: [PATCH 02/17] Updated browser client to pass user id to cipher service observable changes --- .../notification.background.spec.ts | 13 +++--- .../background/notification.background.ts | 43 ++++++++++++------- .../background/overlay.background.spec.ts | 7 +-- .../autofill/background/overlay.background.ts | 28 +++++++++--- .../background/web-request.background.ts | 12 +++++- .../cipher-context-menu-handler.spec.ts | 21 ++++++--- .../browser/cipher-context-menu-handler.ts | 15 ++++++- .../context-menu-clicked-handler.spec.ts | 16 +++++-- .../browser/context-menu-clicked-handler.ts | 10 +++-- .../overlay.background.deprecated.spec.ts | 3 +- .../overlay.background.deprecated.ts | 26 ++++++++--- .../popup/fido2/fido2-v1.component.ts | 9 ++-- .../autofill/popup/fido2/fido2.component.ts | 9 ++-- .../services/autofill.service.spec.ts | 23 +++++----- .../src/autofill/services/autofill.service.ts | 28 +++++++++--- .../browser/src/background/main.background.ts | 4 ++ .../src/platform/listeners/update-badge.ts | 10 ++++- apps/browser/src/popup/app.component.ts | 2 +- .../add-edit/add-edit-v2.component.spec.ts | 6 ++- .../add-edit/add-edit-v2.component.ts | 12 +++++- .../assign-collections.component.ts | 26 ++++++----- .../open-attachments.component.ts | 2 +- .../vault-list-items-container.component.ts | 9 +++- ...ault-password-history-v2.component.spec.ts | 13 +++--- .../vault-password-history-v2.component.ts | 4 +- .../view-v2/view-v2.component.spec.ts | 2 +- .../vault-v2/view-v2/view-v2.component.ts | 21 +++++---- .../components/vault/add-edit.component.ts | 26 ++++++----- .../components/vault/current-tab.component.ts | 8 +++- .../vault/vault-filter.component.ts | 12 ++++-- .../components/vault/vault-items.component.ts | 14 ++++-- .../vault-popup-items.service.spec.ts | 30 ++++++++++--- .../services/vault-popup-items.service.ts | 24 +++++++---- .../vault-popup-list-filters.service.spec.ts | 2 +- .../vault-popup-list-filters.service.ts | 28 ++++++------ .../trash-list-items-container.component.ts | 13 +++++- 36 files changed, 366 insertions(+), 165 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 37c05a55a3a..70dc6342e77 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -904,10 +904,13 @@ describe("NotificationBackground", () => { expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "editedCipher", }); - expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + expect(setAddEditCipherInfoSpy).toHaveBeenCalledWith( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + "testId", + ); expect(openAddEditVaultItemPopoutSpy).toHaveBeenCalledWith(sender.tab, { cipherId: cipherView.id, }); @@ -945,7 +948,7 @@ describe("NotificationBackground", () => { queueMessage, message.folder, ); - expect(editItemSpy).toHaveBeenCalledWith(cipherView, sender.tab); + expect(editItemSpy).toHaveBeenCalledWith(cipherView, "testId", sender.tab); expect(tabSendMessageSpy).toHaveBeenCalledWith(sender.tab, { command: "closeNotificationBar", }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5c6ff3c2c8c..9cbd2522dc6 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -21,6 +21,7 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -262,7 +263,8 @@ export default class NotificationBackground { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url); + const activeUserId = await firstValueFrom(this.activeUserId$); + const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId); const usernameMatches = ciphers.filter( (c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername, ); @@ -340,7 +342,8 @@ export default class NotificationBackground { } let id: string = null; - const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url); + const activeUserId = await firstValueFrom(this.activeUserId$); + const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); if (changeData.currentPassword != null) { const passwordMatches = ciphers.filter( (c) => c.login.password === changeData.currentPassword, @@ -542,15 +545,20 @@ export default class NotificationBackground { this.notificationQueue.splice(i, 1); + const activeUserId = await firstValueFrom(this.activeUserId$); + if (queueMessage.type === NotificationQueueMessageType.ChangePassword) { - const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId); + const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId); await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab); return; } // If the vault was locked, check if a cipher needs updating instead of creating a new one if (queueMessage.wasVaultLocked) { - const allCiphers = await this.cipherService.getAllDecryptedForUrl(queueMessage.uri); + const allCiphers = await this.cipherService.getAllDecryptedForUrl( + queueMessage.uri, + activeUserId, + ); const existingCipher = allCiphers.find( (c) => c.login.username != null && c.login.username.toLowerCase() === queueMessage.username, @@ -566,13 +574,11 @@ export default class NotificationBackground { const newCipher = this.convertAddLoginQueueMessageToCipherView(queueMessage, folderId); if (edit) { - await this.editItem(newCipher, tab); + await this.editItem(newCipher, activeUserId, tab); await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" }); return; } - const activeUserId = await firstValueFrom(this.activeUserId$); - const cipher = await this.cipherService.encrypt(newCipher, activeUserId); try { await this.cipherService.createWithServer(cipher); @@ -604,14 +610,15 @@ export default class NotificationBackground { ) { cipherView.login.password = newPassword; + const activeUserId = await firstValueFrom(this.activeUserId$); + if (edit) { - await this.editItem(cipherView, tab); + await this.editItem(cipherView, activeUserId, tab); await BrowserApi.tabSendMessage(tab, { command: "closeNotificationBar" }); await BrowserApi.tabSendMessage(tab, { command: "editedCipher" }); return; } - 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 @@ -629,13 +636,17 @@ export default class NotificationBackground { * and opens the add/edit vault item popout. * * @param cipherView - The cipher to edit + * @param userId - The active account user ID * @param senderTab - The tab that the message was sent from */ - private async editItem(cipherView: CipherView, senderTab: chrome.tabs.Tab) { - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + private async editItem(cipherView: CipherView, userId: UserId, senderTab: chrome.tabs.Tab) { + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + userId, + ); await this.openAddEditVaultItemPopout(senderTab, { cipherId: cipherView.id }); } @@ -649,8 +660,8 @@ export default class NotificationBackground { return folders.some((x) => x.id === folderId); } - private async getDecryptedCipherById(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); + private async getDecryptedCipherById(cipherId: string, userId: UserId) { + const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { const activeUserId = await firstValueFrom(this.activeUserId$); diff --git a/apps/browser/src/autofill/background/overlay.background.spec.ts b/apps/browser/src/autofill/background/overlay.background.spec.ts index e7b72b72c9b..53056b0dfc9 100644 --- a/apps/browser/src/autofill/background/overlay.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay.background.spec.ts @@ -202,6 +202,7 @@ describe("OverlayBackground", () => { inlineMenuFieldQualificationService, themeStateService, totpService, + accountService, generatedPasswordCallbackMock, addPasswordCallbackMock, ); @@ -845,7 +846,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [ + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [ CipherType.Card, CipherType.Identity, ]); @@ -868,7 +869,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId); expect(cipherService.sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["inlineMenuCiphers"]).toStrictEqual( new Map([ @@ -887,7 +888,7 @@ describe("OverlayBackground", () => { await flushPromises(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, [ + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId, [ CipherType.Card, CipherType.Identity, ]); diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index 8b577ccccf5..567657d5600 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -13,6 +13,7 @@ import { } from "rxjs"; import { parse } from "tldts"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { @@ -222,6 +223,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { private inlineMenuFieldQualificationService: InlineMenuFieldQualificationService, private themeStateService: ThemeStateService, private totpService: TotpService, + private accountService: AccountService, private generatePasswordCallback: () => Promise, private addPasswordCallback: (password: string) => Promise, ) { @@ -406,9 +408,12 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.getAllCipherTypeViews(currentTab); } - const cipherViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url || "")).sort( - (a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b), + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipherViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId) + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); return this.cardAndIdentityCiphers ? cipherViews.concat(...this.cardAndIdentityCiphers) @@ -426,8 +431,11 @@ export class OverlayBackground implements OverlayBackgroundInterface { } this.cardAndIdentityCiphers.clear(); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const cipherViews = ( - await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", [ + await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId, [ CipherType.Card, CipherType.Identity, ]) @@ -2290,10 +2298,16 @@ export class OverlayBackground implements OverlayBackgroundInterface { try { this.closeInlineMenu(sender); - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a.id)), + ); + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + activeUserId, + ); await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id, diff --git a/apps/browser/src/autofill/background/web-request.background.ts b/apps/browser/src/autofill/background/web-request.background.ts index 2c14358a359..cacc5615654 100644 --- a/apps/browser/src/autofill/background/web-request.background.ts +++ b/apps/browser/src/autofill/background/web-request.background.ts @@ -1,5 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; @@ -14,6 +17,7 @@ export default class WebRequestBackground { platformUtilsService: PlatformUtilsService, private cipherService: CipherService, private authService: AuthService, + private accountService: AccountService, private readonly webRequest: typeof chrome.webRequest, ) { this.isFirefox = platformUtilsService.isFirefox(); @@ -55,7 +59,12 @@ export default class WebRequestBackground { // eslint-disable-next-line private async resolveAuthCredentials(domain: string, success: Function, error: Function) { - if ((await this.authService.getAuthStatus()) < AuthenticationStatus.Unlocked) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId)); + if (authStatus < AuthenticationStatus.Unlocked) { error(); return; } @@ -63,6 +72,7 @@ export default class WebRequestBackground { try { const ciphers = await this.cipherService.getAllDecryptedForUrl( domain, + activeUserId, null, UriMatchStrategy.Host, ); diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts index 4fed9eee5ef..3228aed4688 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.spec.ts @@ -2,6 +2,8 @@ import { mock, MockProxy } from "jest-mock-extended"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; @@ -14,6 +16,9 @@ describe("CipherContextMenuHandler", () => { let authService: MockProxy; let cipherService: MockProxy; + const mockUserId = "UserId" as UserId; + const accountService = mockAccountServiceWith(mockUserId); + let sut: CipherContextMenuHandler; beforeEach(() => { @@ -24,7 +29,12 @@ describe("CipherContextMenuHandler", () => { jest.spyOn(MainContextMenuHandler, "removeAll").mockResolvedValue(); - sut = new CipherContextMenuHandler(mainContextMenuHandler, authService, cipherService); + sut = new CipherContextMenuHandler( + mainContextMenuHandler, + authService, + cipherService, + accountService, + ); }); afterEach(() => jest.resetAllMocks()); @@ -119,10 +129,11 @@ describe("CipherContextMenuHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", [ - CipherType.Card, - CipherType.Identity, - ]); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [CipherType.Card, CipherType.Identity], + ); expect(mainContextMenuHandler.loadOptions).toHaveBeenCalledTimes(3); diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts index b112ff00efe..b2bb22943d6 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts @@ -1,3 +1,6 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -14,6 +17,7 @@ export class CipherContextMenuHandler { private mainContextMenuHandler: MainContextMenuHandler, private authService: AuthService, private cipherService: CipherService, + private accountService: AccountService, ) {} async update(url: string) { @@ -35,7 +39,16 @@ export class CipherContextMenuHandler { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(url, [ + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + if (!activeUserId) { + // Show error be thrown here or is it okay to just return? + return; + } + + const ciphers = await this.cipherService.getAllDecryptedForUrl(url, activeUserId, [ CipherType.Card, CipherType.Identity, ]); diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts index 6ef004f7979..c8cb7e81f72 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.spec.ts @@ -61,6 +61,8 @@ describe("ContextMenuClickedHandler", () => { return cipherView; }; + const mockUserId = "UserId" as UserId; + let copyToClipboard: CopyToClipboardAction; let generatePasswordToClipboard: GeneratePasswordToClipboardAction; let autofill: AutofillAction; @@ -79,7 +81,7 @@ describe("ContextMenuClickedHandler", () => { autofill = jest.fn, [tab: chrome.tabs.Tab, cipher: CipherView]>(); authService = mock(); cipherService = mock(); - accountService = mockAccountServiceWith("userId" as UserId); + accountService = mockAccountServiceWith(mockUserId as UserId); totpService = mock(); eventCollectionService = mock(); @@ -191,7 +193,11 @@ describe("ContextMenuClickedHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [], + ); expect(copyToClipboard).toHaveBeenCalledTimes(1); @@ -215,7 +221,11 @@ describe("ContextMenuClickedHandler", () => { expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledTimes(1); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith("https://test.com", []); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith( + "https://test.com", + mockUserId, + [], + ); }); }); }); diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index 597d75575b0..18ae9fa01ef 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -105,6 +105,10 @@ export class ContextMenuClickedHandler { menuItemId as string, ); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (isCreateCipherAction) { // pass; defer to logic below } else if (menuItemId === NOOP_COMMAND_SUFFIX) { @@ -120,12 +124,13 @@ export class ContextMenuClickedHandler { // in scenarios like unlock on autofill const ciphers = await this.cipherService.getAllDecryptedForUrl( tab.url, + activeUserId, additionalCiphersToGet, ); cipher = ciphers[0]; } else { - const ciphers = await this.cipherService.getAllDecrypted(); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); cipher = ciphers.find(({ id }) => id === menuItemId); } @@ -133,9 +138,6 @@ export class ContextMenuClickedHandler { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); await this.accountService.setAccountActivity(activeUserId, new Date()); switch (info.parentMenuItemId) { case AUTOFILL_ID: diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts index 3adaf9e276c..b7add8414a5 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.spec.ts @@ -106,6 +106,7 @@ describe("OverlayBackground", () => { i18nService, platformUtilsService, themeStateService, + accountService, ); jest @@ -201,7 +202,7 @@ describe("OverlayBackground", () => { await overlayBackground.updateOverlayCiphers(); expect(BrowserApi.getTabFromCurrentWindowId).toHaveBeenCalled(); - expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url); + expect(cipherService.getAllDecryptedForUrl).toHaveBeenCalledWith(url, mockUserId); expect(overlayBackground["cipherService"].sortCiphersByLastUsedThenName).toHaveBeenCalled(); expect(overlayBackground["overlayLoginCiphers"]).toStrictEqual( new Map([ diff --git a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts index 5dfade0f863..d0fad4cd00e 100644 --- a/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/background/overlay.background.deprecated.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { SHOW_AUTOFILL_BUTTON } from "@bitwarden/common/autofill/constants"; @@ -106,6 +107,7 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private themeStateService: ThemeStateService, + private accountService: AccountService, ) {} /** @@ -152,9 +154,13 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { } this.overlayLoginCiphers = new Map(); - const ciphersViews = (await this.cipherService.getAllDecryptedForUrl(currentTab.url)).sort( - (a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b), + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const ciphersViews = ( + await this.cipherService.getAllDecryptedForUrl(currentTab.url, activeUserId) + ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); for (let cipherIndex = 0; cipherIndex < ciphersViews.length; cipherIndex++) { this.overlayLoginCiphers.set(`overlay-cipher-${cipherIndex}`, ciphersViews[cipherIndex]); } @@ -660,10 +666,16 @@ class LegacyOverlayBackground implements OverlayBackgroundInterface { cipherView.type = CipherType.Login; cipherView.login = loginView; - await this.cipherService.setAddEditCipherInfo({ - cipher: cipherView, - collectionIds: cipherView.collectionIds, - }); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.setAddEditCipherInfo( + { + cipher: cipherView, + collectionIds: cipherView.collectionIds, + }, + activeUserId, + ); await this.openAddEditVaultItemPopout(sender.tab, { cipherId: cipherView.id }); await BrowserApi.sendMessage("inlineAutofillMenuRefreshAddEditCipher"); diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts index 8dc603cfe29..cbd39de4cc4 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts @@ -146,7 +146,10 @@ export class Fido2V1Component implements OnInit, OnDestroy { this.domainSettingsService.getUrlEquivalentDomains(this.url), ); - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter( (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, ); this.displayedCiphers = this.ciphers.filter( @@ -168,7 +171,7 @@ export class Fido2V1Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -188,7 +191,7 @@ export class Fido2V1Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 4555d87f249..427c598bbf4 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -186,7 +186,10 @@ export class Fido2Component implements OnInit, OnDestroy { this.domainSettingsService.getUrlEquivalentDomains(this.url), ); - this.ciphers = (await this.cipherService.getAllDecrypted()).filter( + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter( (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, ); @@ -211,7 +214,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -232,7 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { - const cipher = await this.cipherService.get(cipherId); + const cipher = await this.cipherService.get(cipherId, activeUserId); return cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/autofill/services/autofill.service.spec.ts b/apps/browser/src/autofill/services/autofill.service.spec.ts index 91f926440a0..0e6e58afd99 100644 --- a/apps/browser/src/autofill/services/autofill.service.spec.ts +++ b/apps/browser/src/autofill/services/autofill.service.spec.ts @@ -760,7 +760,10 @@ describe("AutofillService", () => { ); expect(autofillService["generateLoginFillScript"]).toHaveBeenCalled(); expect(logService.info).not.toHaveBeenCalled(); - expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith(autofillOptions.cipher.id); + expect(cipherService.updateLastUsedDate).toHaveBeenCalledWith( + autofillOptions.cipher.id, + mockUserId, + ); expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( autofillOptions.pageDetails[0].tab.id, { @@ -1013,8 +1016,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, false); expect(cipherService.getNextCipherForUrl).not.toHaveBeenCalled(); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); }); @@ -1027,7 +1030,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.getLastLaunchedForUrl).not.toHaveBeenCalled(); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); @@ -1057,7 +1060,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.getLastUsedForUrl).not.toHaveBeenCalled(); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ @@ -1087,8 +1090,8 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, true); - expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, true); + expect(cipherService.getLastLaunchedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); + expect(cipherService.getLastUsedForUrl).toHaveBeenCalledWith(tab.url, mockUserId, true); expect(cipherService.updateLastUsedIndexForUrl).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1115,7 +1118,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, fromCommand); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(cipherService.updateLastUsedIndexForUrl).toHaveBeenCalledWith(tab.url); expect(autofillService.doAutoFill).toHaveBeenCalledWith({ tab: tab, @@ -1146,7 +1149,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(userVerificationService.hasMasterPasswordAndMasterKeyHash).toHaveBeenCalled(); expect(autofillService["openVaultItemPasswordRepromptPopout"]).toHaveBeenCalledWith(tab, { cipherId: cipher.id, @@ -1172,7 +1175,7 @@ describe("AutofillService", () => { const result = await autofillService.doAutoFillOnTab(pageDetails, tab, true); - expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url); + expect(cipherService.getNextCipherForUrl).toHaveBeenCalledWith(tab.url, mockUserId); expect(autofillService["openVaultItemPasswordRepromptPopout"]).not.toHaveBeenCalled(); expect(autofillService.doAutoFill).not.toHaveBeenCalled(); expect(result).toBeNull(); diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 093f4bfb638..482db642e70 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs"; -import { pairwise } from "rxjs/operators"; +import { map, pairwise } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -66,6 +66,9 @@ export default class AutofillService implements AutofillServiceInterface { private openPasswordRepromptPopoutDebounce: number | NodeJS.Timeout; private currentlyOpeningPasswordRepromptPopout = false; private autofillScriptPortsSet = new Set(); + + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); constructor( @@ -425,6 +428,8 @@ export default class AutofillService implements AutofillServiceInterface { options.cipher.login.totp = null; } + const activeUserId = await firstValueFrom(this.activeUserId$); + let didAutofill = false; await Promise.all( options.pageDetails.map(async (pd) => { @@ -463,7 +468,7 @@ export default class AutofillService implements AutofillServiceInterface { didAutofill = true; if (!options.skipLastUsed) { - await this.cipherService.updateLastUsedDate(options.cipher.id); + await this.cipherService.updateLastUsedDate(options.cipher.id, activeUserId); } // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. @@ -526,17 +531,24 @@ export default class AutofillService implements AutofillServiceInterface { autoSubmitLogin = false, ): Promise { let cipher: CipherView; + + const activeUserId = await firstValueFrom(this.activeUserId$); + if (fromCommand) { - cipher = await this.cipherService.getNextCipherForUrl(tab.url); + cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId); } else { - const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl(tab.url, true); + const lastLaunchedCipher = await this.cipherService.getLastLaunchedForUrl( + tab.url, + activeUserId, + true, + ); if ( lastLaunchedCipher && Date.now().valueOf() - lastLaunchedCipher.localData?.lastLaunched?.valueOf() < 30000 ) { cipher = lastLaunchedCipher; } else { - cipher = await this.cipherService.getLastUsedForUrl(tab.url, true); + cipher = await this.cipherService.getLastUsedForUrl(tab.url, activeUserId, true); } } @@ -625,12 +637,14 @@ export default class AutofillService implements AutofillServiceInterface { let cipher: CipherView; let cacheKey = ""; + const activeUserId = await firstValueFrom(this.activeUserId$); + if (cipherType === CipherType.Card) { cacheKey = "cardCiphers"; - cipher = await this.cipherService.getNextCardCipher(); + cipher = await this.cipherService.getNextCardCipher(activeUserId); } else { cacheKey = "identityCiphers"; - cipher = await this.cipherService.getNextIdentityCipher(); + cipher = await this.cipherService.getNextIdentityCipher(activeUserId); } if (!cipher || !cacheKey || (cipher.reprompt === CipherRepromptType.Password && !fromCommand)) { diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 555e3a13fa0..c9015084a38 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1230,6 +1230,7 @@ export default class MainBackground { this.mainContextMenuHandler, this.authService, this.cipherService, + this.accountService, ); if (chrome.webRequest != null && chrome.webRequest.onAuthRequired != null) { @@ -1237,6 +1238,7 @@ export default class MainBackground { this.platformUtilsService, this.cipherService, this.authService, + this.accountService, chrome.webRequest, ); } @@ -1611,6 +1613,7 @@ export default class MainBackground { this.i18nService, this.platformUtilsService, this.themeStateService, + this.accountService, ); } else { this.overlayBackground = new OverlayBackground( @@ -1628,6 +1631,7 @@ export default class MainBackground { this.inlineMenuFieldQualificationService, this.themeStateService, this.totpService, + this.accountService, () => this.generatePassword(), (password) => this.addPasswordToHistory(password), ); diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index f67b96c847f..e001dd4f971 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -1,7 +1,8 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; @@ -22,6 +23,7 @@ export class UpdateBadge { private authService: AuthService; private badgeSettingsService: BadgeSettingsServiceAbstraction; private cipherService: CipherService; + private accountService: AccountService; private badgeAction: typeof chrome.action | typeof chrome.browserAction; private sidebarAction: OperaSidebarAction | FirefoxSidebarAction; private win: Window & typeof globalThis; @@ -34,6 +36,7 @@ export class UpdateBadge { this.badgeSettingsService = services.badgeSettingsService; this.authService = services.authService; this.cipherService = services.cipherService; + this.accountService = services.accountService; } async run(opts?: { tabId?: number; windowId?: number }): Promise { @@ -87,7 +90,10 @@ export class UpdateBadge { return; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId); let countText = ciphers.length == 0 ? "" : ciphers.length.toString(); if (ciphers.length > 9) { countText = "9+"; diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 8e51152be2e..a54d0c67a0d 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -167,7 +167,7 @@ export class AppComponent implements OnInit, OnDestroy { await this.clearComponentStates(); } if (url.startsWith("/tabs/")) { - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, this.activeUserId); } (window as any).previousPopupUrl = url; diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts index ebfb1ff765f..b6c52de036d 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -4,10 +4,13 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -57,7 +60,7 @@ describe("AddEditV2Component", () => { addEditCipherInfo$ = new BehaviorSubject(null); cipherServiceMock = mock(); - cipherServiceMock.addEditCipherInfo$ = addEditCipherInfo$.asObservable(); + cipherServiceMock.addEditCipherInfo$.mockReturnValue(addEditCipherInfo$); await TestBed.configureTestingModule({ imports: [AddEditV2Component], @@ -71,6 +74,7 @@ describe("AddEditV2Component", () => { { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: CipherService, useValue: cipherServiceMock }, { provide: EventCollectionService, useValue: { collect } }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideProvider(CipherFormConfigService, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 2d8c4857c1c..b80260f3259 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -9,6 +9,7 @@ import { firstValueFrom, map, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; @@ -165,6 +166,7 @@ export class AddEditV2Component implements OnInit { private router: Router, private cipherService: CipherService, private eventCollectionService: EventCollectionService, + private accountService: AccountService, ) { this.subscribeToParams(); } @@ -266,9 +268,15 @@ export class AddEditV2Component implements OnInit { config.initialValues = this.setInitialValuesFromParams(params); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((account) => account.id)), + ); + // The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form // Attempt to fetch them here and overwrite the initialValues if present - const cachedCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); + const cachedCipherInfo = await firstValueFrom( + this.cipherService.addEditCipherInfo$(activeUserId), + ); if (cachedCipherInfo != null) { // Cached cipher info has priority over queryParams @@ -277,7 +285,7 @@ export class AddEditV2Component implements OnInit { ...mapAddEditCipherInfoToInitialValues(cachedCipherInfo), }; // Be sure to clear the "cached" cipher info, so it doesn't get used again - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, activeUserId); } if (["edit", "partial-edit"].includes(config.mode) && config.originalCipher?.id) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 51ebe9bdb62..27f3b7e5e18 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -5,12 +5,13 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { Observable, combineLatest, first, map, switchMap } from "rxjs"; +import { Observable, combineLatest, filter, first, map, switchMap } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -58,16 +59,19 @@ export class AssignCollections { private accountService: AccountService, route: ActivatedRoute, ) { - const cipher$: Observable = route.queryParams.pipe( - switchMap(({ cipherId }) => this.cipherService.get(cipherId)), - switchMap((cipherDomain) => - this.accountService.activeAccount$.pipe( - map((account) => account?.id), - switchMap((userId) => - this.cipherService - .getKeyForCipherKeyDecryption(cipherDomain, userId) - .then(cipherDomain.decrypt.bind(cipherDomain)), - ), + const cipher$: Observable = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId) => userId != null), + switchMap((userId) => + route.queryParams.pipe( + switchMap(async ({ cipherId }) => { + const cipherDomain = await this.cipherService.get(cipherId, userId); + const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( + cipherDomain, + userId, + ); + return cipherDomain.decrypt(key); + }), ), ), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index ca620531ca8..7088af6be2b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -68,10 +68,10 @@ export class OpenAttachmentsComponent implements OnInit { return; } - const cipherDomain = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 29c9f14e2aa..d736728d125 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -13,9 +13,10 @@ import { signal, } from "@angular/core"; import { Router, RouterLink } from "@angular/router"; -import { map } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -158,6 +159,7 @@ export class VaultListItemsContainerComponent implements AfterViewInit { private cipherService: CipherService, private router: Router, private platformUtilsService: PlatformUtilsService, + private accountService: AccountService, ) {} async ngAfterViewInit() { @@ -186,7 +188,10 @@ export class VaultListItemsContainerComponent implements AfterViewInit { this.viewCipherTimeout = null; } - await this.cipherService.updateLastLaunchedDate(cipher.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); await BrowserApi.createNewTab(cipher.login.launchUri); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts index 9ac17b49386..838ce2e9426 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.spec.ts @@ -1,13 +1,15 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ActivatedRoute } from "@angular/router"; import { mock } from "jest-mock-extended"; -import { BehaviorSubject, Subject } from "rxjs"; +import { Subject } from "rxjs"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -19,6 +21,7 @@ import { PasswordHistoryV2Component } from "./vault-password-history-v2.componen describe("PasswordHistoryV2Component", () => { let fixture: ComponentFixture; const params$ = new Subject(); + const mockUserId = "acct-1" as UserId; const mockCipherView = { id: "111-222-333", @@ -45,9 +48,7 @@ describe("PasswordHistoryV2Component", () => { { provide: CipherService, useValue: mock({ get: getCipher }) }, { provide: AccountService, - useValue: mock({ - activeAccount$: new BehaviorSubject({ id: "acct-1" } as Account), - }), + useValue: mockAccountServiceWith(mockUserId), }, { provide: PopupRouterCacheService, useValue: { back } }, { provide: ActivatedRoute, useValue: { queryParams: params$ } }, @@ -64,7 +65,7 @@ describe("PasswordHistoryV2Component", () => { tick(100); - expect(getCipher).toHaveBeenCalledWith(mockCipherView.id); + expect(getCipher).toHaveBeenCalledWith(mockCipherView.id, mockUserId); })); it("navigates back when a cipherId is not in the params", () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index c8f590ced57..ce9dc0a89cc 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -58,8 +58,6 @@ export class PasswordHistoryV2Component implements OnInit { /** Load the cipher based on the given Id */ private async loadCipher(cipherId: string) { - const cipher = await this.cipherService.get(cipherId); - const activeAccount = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a: { id: string | undefined }) => a)), ); @@ -69,6 +67,8 @@ export class PasswordHistoryV2Component implements OnInit { } const activeUserId = activeAccount.id as UserId; + + const cipher = await this.cipherService.get(cipherId, activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 7ee15aa833b..42d2fcd9de2 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -127,7 +127,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444"); + expect(mockCipherService.get).toHaveBeenCalledWith("122-333-444", mockUserId); expect(component.cipher).toEqual(mockCipher); })); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 55b59c087c5..91d85378bdb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -22,6 +22,7 @@ import { import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { ViewPasswordHistoryService } from "@bitwarden/common/vault/abstractions/view-password-history.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -95,6 +96,8 @@ export class ViewV2Component { loadAction: LoadAction; senderTabId?: number; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private route: ActivatedRoute, private router: Router, @@ -159,10 +162,8 @@ export class ViewV2Component { } async getCipherData(id: string) { - const cipher = await this.cipherService.get(id); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipher = await this.cipherService.get(id, activeUserId); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -192,7 +193,8 @@ export class ViewV2Component { } try { - await this.deleteCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.deleteCipher(activeUserId); } catch (e) { this.logService.error(e); return false; @@ -211,7 +213,8 @@ export class ViewV2Component { restore = async (): Promise => { try { - await this.cipherService.restoreWithServer(this.cipher.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.cipherService.restoreWithServer(this.cipher.id, activeUserId); } catch (e) { this.logService.error(e); } @@ -224,10 +227,10 @@ export class ViewV2Component { }); }; - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id) - : this.cipherService.softDeleteWithServer(this.cipher.id); + ? this.cipherService.deleteWithServer(this.cipher.id, userId) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId); } protected showFooter(): boolean { 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 39414217b0d..3935ba875e4 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 @@ -21,6 +21,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -284,7 +285,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit { async generateUsername(): Promise { const confirmed = await super.generateUsername(); if (confirmed) { - await this.saveCipherState(); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.saveCipherState(activeUserId); // 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.router.navigate(["generator"], { queryParams: { type: "username" } }); @@ -295,7 +297,8 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit { async generatePassword(): Promise { const confirmed = await super.generatePassword(); if (confirmed) { - await this.saveCipherState(); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.saveCipherState(activeUserId); // 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.router.navigate(["generator"], { queryParams: { type: "password" } }); @@ -326,14 +329,17 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit { ); } - private saveCipherState() { - return this.cipherService.setAddEditCipherInfo({ - cipher: this.cipher, - collectionIds: - this.collections == null - ? [] - : this.collections.filter((c) => (c as any).checked).map((c) => c.id), - }); + private saveCipherState(userId: UserId) { + return this.cipherService.setAddEditCipherInfo( + { + cipher: this.cipher, + collectionIds: + this.collections == null + ? [] + : this.collections.filter((c) => (c as any).checked).map((c) => c.id), + }, + userId, + ); } private setFocus() { diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts index 156a708015f..f0ffe2ff88d 100644 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts @@ -3,10 +3,11 @@ import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { Subject, firstValueFrom, from, Subscription } from "rxjs"; -import { debounceTime, switchMap, takeUntil } from "rxjs/operators"; +import { debounceTime, map, switchMap, takeUntil } from "rxjs/operators"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; @@ -73,6 +74,7 @@ export class CurrentTabComponent implements OnInit, OnDestroy { private organizationService: OrganizationService, private vaultFilterService: VaultFilterService, private vaultSettingsService: VaultSettingsService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -280,8 +282,12 @@ export class CurrentTabComponent implements OnInit, OnDestroy { otherTypes.push(CipherType.Identity); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); const ciphers = await this.cipherService.getAllDecryptedForUrl( this.url, + activeUserId, otherTypes.length > 0 ? otherTypes : null, ); diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts index d430568869c..00fb023033e 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts @@ -4,11 +4,12 @@ import { Location } from "@angular/common"; import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; -import { first, switchMap, takeUntil } from "rxjs/operators"; +import { first, map, switchMap, takeUntil } from "rxjs/operators"; import { CollectionView } from "@bitwarden/admin-console/common"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -80,6 +81,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { private _searchText$ = new BehaviorSubject(""); private isSearchable: boolean = false; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + get searchText() { return this._searchText$.value; } @@ -102,6 +105,7 @@ export class VaultFilterComponent implements OnInit, OnDestroy { private vaultFilterService: VaultFilterService, private vaultBrowserStateService: VaultBrowserStateService, private configService: ConfigService, + private accountService: AccountService, ) { this.noFolderListSize = 100; } @@ -208,7 +212,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { } async loadCiphers() { - this.allCiphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.allCiphers = await this.cipherService.getAllDecrypted(activeUserId); if (!this.hasLoadedAllCiphers) { this.hasLoadedAllCiphers = !(await this.searchService.isSearchable(this.searchText)); } @@ -313,7 +318,8 @@ export class VaultFilterComponent implements OnInit, OnDestroy { window.clearTimeout(this.selectedTimeout); } this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); // 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 BrowserApi.createNewTab(cipher.login.launchUri); diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts index 387afcfe217..ed1336b863b 100644 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts +++ b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts @@ -3,13 +3,15 @@ import { Location } from "@angular/common"; import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; +import { firstValueFrom } from "rxjs"; +import { first, map } from "rxjs/operators"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -66,8 +68,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn private platformUtilsService: PlatformUtilsService, cipherService: CipherService, private vaultFilterService: VaultFilterService, + accountService: AccountService, ) { - super(searchService, cipherService); + super(searchService, cipherService, accountService); this.applySavedState = (window as any).previousPopupUrl != null && !(window as any).previousPopupUrl.startsWith("/ciphers"); @@ -233,7 +236,12 @@ export class VaultItemsComponent extends BaseVaultItemsComponent implements OnIn window.clearTimeout(this.selectedTimeout); } this.preventSelected = true; - await this.cipherService.updateLastLaunchedDate(cipher.id); + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); // 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 BrowserApi.createNewTab(cipher.login.launchUri); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts index 5b0eb63998d..3b9186b2c9b 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.spec.ts @@ -6,13 +6,16 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { SyncService } from "@bitwarden/common/platform/sync"; -import { ObservableTracker } from "@bitwarden/common/spec"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { ObservableTracker, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; +import { LocalData } from "@bitwarden/common/vault/models/data/local.data"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { InlineMenuFieldQualificationService } from "../../../../../browser/src/autofill/services/inline-menu-field-qualification.service"; @@ -32,6 +35,9 @@ describe("VaultPopupItemsService", () => { let mockCollections: CollectionView[]; let activeUserLastSync$: BehaviorSubject; + let ciphersSubject: BehaviorSubject>; + let localDataSubject: BehaviorSubject>; + const cipherServiceMock = mock(); const vaultSettingsServiceMock = mock(); const organizationServiceMock = mock(); @@ -56,8 +62,17 @@ describe("VaultPopupItemsService", () => { cipherList[3].favorite = true; cipherServiceMock.getAllDecrypted.mockResolvedValue(cipherList); - cipherServiceMock.ciphers$ = new BehaviorSubject(null); - cipherServiceMock.localData$ = new BehaviorSubject(null); + + ciphersSubject = new BehaviorSubject>({}); + localDataSubject = new BehaviorSubject>({}); + + cipherServiceMock.ciphers$.mockImplementation((userId: UserId) => + ciphersSubject.asObservable(), + ); + cipherServiceMock.localData$.mockImplementation((userId: UserId) => + localDataSubject.asObservable(), + ); + searchService.searchCiphers.mockImplementation(async (_, __, ciphers) => ciphers); cipherServiceMock.filterCiphersForUrl.mockImplementation(async (ciphers) => ciphers.filter((c) => ["0", "1"].includes(c.id)), @@ -112,6 +127,7 @@ describe("VaultPopupItemsService", () => { { provide: CollectionService, useValue: collectionService }, { provide: VaultPopupAutofillService, useValue: vaultAutofillServiceMock }, { provide: SyncService, useValue: syncServiceMock }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, { provide: InlineMenuFieldQualificationService, useValue: inlineMenuFieldQualificationServiceMock, @@ -149,7 +165,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await tracker.expectEmission(); @@ -163,7 +179,7 @@ describe("VaultPopupItemsService", () => { await tracker.expectEmission(); - (cipherServiceMock.localData$ as BehaviorSubject).next(null); + localDataSubject.next({}); await tracker.expectEmission(); @@ -434,7 +450,7 @@ describe("VaultPopupItemsService", () => { it("should cycle when cipherService.ciphers$ emits", async () => { // Restart tracking tracked = new ObservableTracker(service.loading$); - (cipherServiceMock.ciphers$ as BehaviorSubject).next(null); + ciphersSubject.next({}); await trackedCiphers.pauseUntilReceived(2); diff --git a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts index 93aa8cdaba9..3f10b48c559 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-items.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-items.service.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { inject, Injectable, NgZone } from "@angular/core"; +import { Injectable, NgZone } from "@angular/core"; import { BehaviorSubject, combineLatest, @@ -24,6 +24,7 @@ import { import { CollectionService } from "@bitwarden/admin-console/common"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; import { CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; @@ -82,14 +83,17 @@ export class VaultPopupItemsService { * Observable that contains the list of all decrypted ciphers. * @private */ - private _allDecryptedCiphers$: Observable = merge( - this.cipherService.ciphers$, - this.cipherService.localData$, - ).pipe( - runInsideAngular(inject(NgZone)), // Workaround to ensure cipher$ state provider emissions are run inside Angular - tap(() => this._ciphersLoading$.next()), - waitUntilSync(this.syncService), - switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted())), + private _allDecryptedCiphers$: Observable = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + filter((userId) => userId != null), + switchMap((userId) => + merge(this.cipherService.ciphers$(userId), this.cipherService.localData$(userId)).pipe( + runInsideAngular(this.ngZone), + tap(() => this._ciphersLoading$.next()), + waitUntilSync(this.syncService), + switchMap(() => Utils.asyncToObservable(() => this.cipherService.getAllDecrypted(userId))), + ), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); @@ -264,6 +268,8 @@ export class VaultPopupItemsService { private collectionService: CollectionService, private vaultPopupAutofillService: VaultPopupAutofillService, private syncService: SyncService, + private accountService: AccountService, + private ngZone: NgZone, ) {} applyFilter(newSearchText: string) { diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 0eb91c6cbe2..d6c46ccf891 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -39,7 +39,7 @@ describe("VaultPopupListFiltersService", () => { } as unknown as FolderService; const cipherService = { - cipherViews$, + cipherViews$: () => cipherViews$, } as unknown as CipherService; const organizationService = { 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 8455fd587d0..7620aaac7ec 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 @@ -93,16 +93,6 @@ export class VaultPopupListFiltersService { */ private cipherViews: CipherView[] = []; - /** - * Observable of cipher views - */ - private cipherViews$: Observable = this.cipherService.cipherViews$.pipe( - tap((cipherViews) => { - this.cipherViews = Object.values(cipherViews); - }), - map((ciphers) => Object.values(ciphers)), - ); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( @@ -269,8 +259,16 @@ export class VaultPopupListFiltersService { * Folder array structured to be directly passed to `ChipSelectComponent` */ folders$: Observable[]> = this.activeUserId$.pipe( - switchMap((userId) => - combineLatest([ + switchMap((userId) => { + // Observable of cipher views + const cipherViews$ = this.cipherService.cipherViews$(userId).pipe( + tap((cipherViews) => { + this.cipherViews = Object.values(cipherViews); + }), + map((ciphers) => Object.values(ciphers)), + ); + + return combineLatest([ this.filters$.pipe( distinctUntilChanged( (previousFilter, currentFilter) => @@ -279,7 +277,7 @@ export class VaultPopupListFiltersService { ), ), this.folderService.folderViews$(userId), - this.cipherViews$, + cipherViews$, ]).pipe( map(([filters, folders, cipherViews]): [PopupListFilter, FolderView[], CipherView[]] => { if (folders.length === 1 && folders[0].id === null) { @@ -328,8 +326,8 @@ export class VaultPopupListFiltersService { map((folders) => folders.nestedList.map((f) => this.convertToChipSelectOption(f, "bwi-folder")), ), - ), - ), + ); + }), ); /** diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index 9177193d728..ed6e89ce55b 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -3,8 +3,10 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; import { Router } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -55,12 +57,16 @@ export class TrashListItemsContainerComponent { private i18nService: I18nService, private dialogService: DialogService, private passwordRepromptService: PasswordRepromptService, + private accountService: AccountService, private router: Router, ) {} async restore(cipher: CipherView) { try { - await this.cipherService.restoreWithServer(cipher.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.restoreWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ @@ -91,7 +97,10 @@ export class TrashListItemsContainerComponent { } try { - await this.cipherService.deleteWithServer(cipher.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.deleteWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); this.toastService.showToast({ From bfd5eb43b58731b8c40802edd65d4e286be07dc2 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 10 Jan 2025 18:49:31 -0500 Subject: [PATCH 03/17] Cli changes --- .../admin-console/commands/share.command.ts | 11 ++++---- apps/cli/src/commands/edit.command.ts | 19 +++++++++---- apps/cli/src/commands/get.command.ts | 13 +++++---- apps/cli/src/commands/list.command.ts | 16 +++++++++-- apps/cli/src/commands/restore.command.ts | 19 +++++++++++-- apps/cli/src/oss-serve-configurator.ts | 5 +++- apps/cli/src/vault.program.ts | 5 +++- apps/cli/src/vault/create.command.ts | 5 ++-- apps/cli/src/vault/delete.command.ts | 28 +++++++++++++++---- 9 files changed, 91 insertions(+), 30 deletions(-) diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 5b351efe459..145dd5d4e14 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -46,7 +46,11 @@ export class ShareCommand { organizationId = organizationId.toLowerCase(); } - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -54,15 +58,12 @@ export class ShareCommand { return Response.badRequest("This item already belongs to an organization."); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); const cipherView = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); - const updatedCipher = await this.cipherService.get(cipher.id); + const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), ); diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index dd99d03b086..961511922df 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -82,14 +82,14 @@ export class EditCommand { } private async editCipher(id: string, req: CipherExport) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); let cipherView = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -111,7 +111,11 @@ export class EditCommand { } private async editCipherCollections(id: string, req: string[]) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -123,7 +127,10 @@ export class EditCommand { cipher.collectionIds = req; try { - const updatedCipher = await this.cipherService.saveCollectionsWithServer(cipher); + const updatedCipher = await this.cipherService.saveCollectionsWithServer( + cipher, + activeUserId, + ); const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption( updatedCipher, diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 7c3cc7caa9f..5243f77e1f1 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -113,16 +113,16 @@ export class GetCommand extends DownloadCommand { private async getCipherView(id: string): Promise { let decCipher: CipherView = null; + const activeUserId = await firstValueFrom(this.activeUserId$); if (Utils.isGuid(id)) { - const cipher = await this.cipherService.get(id); + const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - const activeUserId = await firstValueFrom(this.activeUserId$); decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); } } else if (id.trim() !== "") { - let ciphers = await this.cipherService.getAllDecrypted(); + let ciphers = await this.cipherService.getAllDecrypted(activeUserId); ciphers = this.searchService.searchCiphersBasic(ciphers, id); if (ciphers.length > 1) { return ciphers; @@ -265,8 +265,10 @@ export class GetCommand extends DownloadCommand { const canAccessPremium = await firstValueFrom( this.accountProfileService.hasPremiumFromAnySource$, ); + if (!canAccessPremium) { - const originalCipher = await this.cipherService.get(cipher.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if ( originalCipher == null || originalCipher.organizationId == null || @@ -351,7 +353,8 @@ export class GetCommand extends DownloadCommand { this.accountProfileService.hasPremiumFromAnySource$, ); if (!canAccessPremium) { - const originalCipher = await this.cipherService.get(cipher.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); } diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 92da86b696a..a1c51dbd22d 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -64,11 +64,19 @@ export class ListCommand { private async listCiphers(options: Options) { let ciphers: CipherView[]; + + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (activeUserId == null) { + return Response.error("No active user id found."); + } + options.trash = options.trash || false; if (options.url != null && options.url.trim() !== "") { - ciphers = await this.cipherService.getAllDecryptedForUrl(options.url); + ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId); } else { - ciphers = await this.cipherService.getAllDecrypted(); + ciphers = await this.cipherService.getAllDecrypted(activeUserId); } if ( @@ -140,6 +148,10 @@ export class ListCommand { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + if (activeUserId == null) { + return Response.error("No active user id found."); + } + let folders = await this.folderService.getAllDecryptedFromState(activeUserId); if (options.search != null && options.search.trim() !== "") { diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 96179e7b687..64c0969680f 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,9 +1,15 @@ +import { firstValueFrom, map } from "rxjs"; + +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../models/response"; export class RestoreCommand { - constructor(private cipherService: CipherService) {} + constructor( + private cipherService: CipherService, + private accountService: AccountService, + ) {} async run(object: string, id: string): Promise { if (id != null) { @@ -19,7 +25,14 @@ export class RestoreCommand { } private async restoreCipher(id: string) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (activeUserId == null) { + return Response.error("No active user id found."); + } + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -28,7 +41,7 @@ export class RestoreCommand { } try { - await this.cipherService.restoreWithServer(id); + await this.cipherService.restoreWithServer(id, activeUserId); return Response.success(); } catch (e) { return Response.error(e); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 9bd3a2bee5f..af5111e23cd 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -124,7 +124,10 @@ export class OssServeConfigurator { this.serviceContainer.encryptService, this.serviceContainer.organizationUserApiService, ); - this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService); + this.restoreCommand = new RestoreCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); this.shareCommand = new ShareCommand( this.serviceContainer.cipherService, this.serviceContainer.accountService, diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 3eb0e68de09..6d68012f245 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -347,7 +347,10 @@ export class VaultProgram extends BaseProgram { } await this.exitIfLocked(); - const command = new RestoreCommand(this.serviceContainer.cipherService); + const command = new RestoreCommand( + this.serviceContainer.cipherService, + this.serviceContainer.accountService, + ); const response = await command.run(object, id); this.processResponse(response); }); diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 47e91cb55ff..7d91c2f2e5b 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -130,8 +130,10 @@ export class CreateCommand { return Response.badRequest("File name not provided."); } + const activeUserId = await firstValueFrom(this.activeUserId$); + const itemId = options.itemId.toLowerCase(); - const cipher = await this.cipherService.get(itemId); + const cipher = await this.cipherService.get(itemId, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -152,7 +154,6 @@ export class CreateCommand { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); const updatedCipher = await this.cipherService.saveAttachmentRawWithServer( cipher, fileName, diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 6b66b8bc7bb..1dd90b1cb39 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -44,7 +44,14 @@ export class DeleteCommand { } private async deleteCipher(id: string, options: Options) { - const cipher = await this.cipherService.get(id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (activeUserId == null) { + return Response.error("No active user id found."); + } + + const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -59,9 +66,9 @@ export class DeleteCommand { try { if (options.permanent) { - await this.cipherService.deleteWithServer(id); + await this.cipherService.deleteWithServer(id, activeUserId); } else { - await this.cipherService.softDeleteWithServer(id); + await this.cipherService.softDeleteWithServer(id, activeUserId); } return Response.success(); } catch (e) { @@ -74,8 +81,15 @@ export class DeleteCommand { return Response.badRequest("`itemid` option is required."); } + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (activeUserId == null) { + return Response.error("No active user id found."); + } + const itemId = options.itemId.toLowerCase(); - const cipher = await this.cipherService.get(itemId); + const cipher = await this.cipherService.get(itemId, activeUserId); if (cipher == null) { return Response.notFound(); } @@ -97,7 +111,11 @@ export class DeleteCommand { } try { - await this.cipherService.deleteAttachmentWithServer(cipher.id, attachments[0].id); + await this.cipherService.deleteAttachmentWithServer( + cipher.id, + attachments[0].id, + activeUserId, + ); return Response.success(); } catch (e) { return Response.error(e); From a7912f63abecfd723fe77edf9f66d401967a5dbb Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 10 Jan 2025 18:49:59 -0500 Subject: [PATCH 04/17] desktop changes --- .../services/desktop-autofill.service.ts | 23 ++++++++++++++----- .../platform/services/ssh-agent.service.ts | 17 ++++++++------ .../encrypted-message-handler.service.ts | 15 ++++++++---- .../vault/app/vault/vault-items.component.ts | 4 +++- 4 files changed, 40 insertions(+), 19 deletions(-) diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 1ce58596b34..3087249a934 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -4,6 +4,7 @@ import { EMPTY, Subject, distinctUntilChanged, + filter, firstValueFrom, map, mergeMap, @@ -27,6 +28,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils"; import { Fido2Utils } from "@bitwarden/common/platform/services/fido2/fido2-utils"; import { guidToRawFormat } from "@bitwarden/common/platform/services/fido2/guid-utils"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -60,7 +62,11 @@ export class DesktopAutofillService implements OnDestroy { return EMPTY; } - return this.cipherService.cipherViews$; + return this.accountService.activeAccount$.pipe( + map((account) => account?.id), + filter((userId): userId is UserId => userId != null), + switchMap((userId) => this.cipherService.cipherViews$(userId)), + ); }), // TODO: This will unset all the autofill credentials on the OS // when the account locks. We should instead explicilty clear the credentials @@ -164,17 +170,22 @@ export class DesktopAutofillService implements OnDestroy { // TODO: For some reason the credentialId is passed as an empty array in the request, so we need to // get it from the cipher. For that we use the recordIdentifier, which is the cipherId. if (request.recordIdentifier && request.credentialId.length === 0) { - const cipher = await this.cipherService.get(request.recordIdentifier); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + if (!activeUserId) { + this.logService.error("listenPasskeyAssertion error", "Active user not found"); + callback(new Error("Active user not found"), null); + return; + } + + const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId); if (!cipher) { this.logService.error("listenPasskeyAssertion error", "Cipher not found"); callback(new Error("Cipher not found"), null); return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const decrypted = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/desktop/src/platform/services/ssh-agent.service.ts b/apps/desktop/src/platform/services/ssh-agent.service.ts index 651e67e9467..43fc9308aca 100644 --- a/apps/desktop/src/platform/services/ssh-agent.service.ts +++ b/apps/desktop/src/platform/services/ssh-agent.service.ts @@ -29,6 +29,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/messaging"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { DialogService, ToastService } from "@bitwarden/components"; @@ -68,14 +69,14 @@ export class SshAgentService implements OnDestroy { this.messageListener .messages$(new CommandDefinition("sshagent.signrequest")) .pipe( - withLatestFrom(this.authService.activeAccountStatus$), + withLatestFrom(this.authService.activeAccountStatus$, this.accountService.activeAccount$), // This switchMap handles unlocking the vault if it is locked: // - If the vault is locked, we will wait for it to be unlocked. // - If the vault is not unlocked within the timeout, we will abort the flow. // - If the vault is unlocked, we will continue with the flow. // switchMap is used here to prevent multiple requests from being processed at the same time, // and will cancel the previous request if a new one is received. - switchMap(([message, status]) => { + switchMap(([message, status, account]) => { if (status !== AuthenticationStatus.Unlocked) { ipc.platform.focusWindow(); this.toastService.showToast({ @@ -109,12 +110,12 @@ export class SshAgentService implements OnDestroy { ); } - return of(message); + return of([message, account.id]); }), // This switchMap handles fetching the ciphers from the vault. - switchMap((message) => - from(this.cipherService.getAllDecrypted()).pipe( - map((ciphers) => [message, ciphers] as const), + switchMap(([message, userId]: [Record, UserId]) => + from(this.cipherService.getAllDecrypted(userId)).pipe( + map((ciphers) => [message as any, ciphers] as const), ), ), // This concatMap handles showing the dialog to approve the request. @@ -197,7 +198,9 @@ export class SshAgentService implements OnDestroy { return; } - const ciphers = await this.cipherService.getAllDecrypted(); + const activeAccount = await firstValueFrom(this.accountService.activeAccount$); + + const ciphers = await this.cipherService.getAllDecrypted(activeAccount.id); if (ciphers == null) { await ipc.platform.sshAgent.lock(); return; diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 09c0fe2a07c..04e650c485a 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -123,7 +123,7 @@ export class EncryptedMessageHandlerService { return { error: "locked" }; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri); + const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri, activeUserId); ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); ciphers.forEach((c) => { @@ -198,13 +198,18 @@ export class EncryptedMessageHandlerService { } try { - const cipher = await this.cipherService.get(credentialUpdatePayload.credentialId); - if (cipher === null) { - return { status: "failure" }; - } const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + + const cipher = await this.cipherService.get( + credentialUpdatePayload.credentialId, + activeUserId, + ); + if (cipher === null) { + return { status: "failure" }; + } + const cipherView = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index 348071729e8..5d7285e570b 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -5,6 +5,7 @@ import { distinctUntilChanged } from "rxjs"; import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -20,8 +21,9 @@ export class VaultItemsComponent extends BaseVaultItemsComponent { searchService: SearchService, searchBarService: SearchBarService, cipherService: CipherService, + accountService: AccountService, ) { - super(searchService, cipherService); + super(searchService, cipherService, accountService); // eslint-disable-next-line rxjs-angular/prefer-takeuntil searchBarService.searchText$.pipe(distinctUntilChanged()).subscribe((searchText) => { From d1cdc5ae64a5a6fba99ba823411a8c15d307bb2c Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 10 Jan 2025 18:51:57 -0500 Subject: [PATCH 05/17] Fixed test --- .../vault-v2/vault-header/vault-header-v2.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 38ec6056d19..c822b71bdf0 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -59,7 +59,9 @@ describe("VaultHeaderV2Component", () => { providers: [ { provide: CipherService, - useValue: mock({ cipherViews$: new BehaviorSubject([]) }), + useValue: mock({ + cipherViews$: jest.fn().mockReturnValue(new BehaviorSubject([])), + }), }, { provide: VaultSettingsService, useValue: mock() }, { provide: FolderService, useValue: mock() }, From 939d5f5297aa3119204c0ec296f857d7dc5f32f5 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 10 Jan 2025 18:52:22 -0500 Subject: [PATCH 06/17] Libs changes --- .../components/collections.component.ts | 20 +- .../angular/src/components/share.component.ts | 14 +- .../vault/components/add-edit.component.ts | 41 +-- .../vault/components/attachments.component.ts | 32 +-- .../components/password-history.component.ts | 2 +- .../vault/components/vault-items.component.ts | 9 +- .../src/vault/components/view.component.ts | 23 +- .../services/vault-filter.service.ts | 3 +- .../fido2/fido2-authenticator.service.ts | 21 +- .../src/platform/sync/core-sync.service.ts | 22 +- libs/common/src/platform/sync/sync.service.ts | 3 +- .../event/event-collection.service.ts | 15 +- .../src/services/notifications.service.ts | 6 +- .../src/vault/abstractions/cipher.service.ts | 73 +++-- .../src/vault/services/cipher.service.spec.ts | 6 +- .../src/vault/services/cipher.service.ts | 269 ++++++++++-------- .../vault/services/folder/folder.service.ts | 2 +- ...ser-asymmetric-key-regeneration.service.ts | 6 +- .../individual-vault-export.service.ts | 4 +- .../src/services/org-vault-export.service.ts | 12 +- .../cipher-attachments.component.ts | 2 +- .../delete-attachment.component.ts | 17 +- .../default-cipher-form-config.service.ts | 8 +- .../services/default-cipher-form.service.ts | 15 +- .../autofill-options-view.component.ts | 8 +- .../assign-collections.component.ts | 26 +- 26 files changed, 387 insertions(+), 272 deletions(-) diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 0b19935985a..63635b4eed1 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -10,6 +10,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -29,6 +30,8 @@ export class CollectionsComponent implements OnInit { protected cipherDomain: Cipher; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, @@ -45,11 +48,9 @@ export class CollectionsComponent implements OnInit { } async load() { - this.cipherDomain = await this.loadCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); @@ -87,7 +88,8 @@ export class CollectionsComponent implements OnInit { } this.cipherDomain.collectionIds = selectedCollectionIds; try { - this.formPromise = this.saveCollections(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.formPromise = this.saveCollections(activeUserId); await this.formPromise; this.onSavedCollections.emit(); this.toastService.showToast({ @@ -106,8 +108,8 @@ export class CollectionsComponent implements OnInit { } } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected loadCipherCollections() { @@ -121,7 +123,7 @@ export class CollectionsComponent implements OnInit { ); } - protected saveCollections() { - return this.cipherService.saveCollectionsWithServer(this.cipherDomain); + protected saveCollections(userId: UserId) { + return this.cipherService.saveCollectionsWithServer(this.cipherDomain, userId); } } diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 37dec53b9c7..21b920a2240 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -29,6 +29,8 @@ export class ShareComponent implements OnInit, OnDestroy { protected writeableCollections: Checkable[] = []; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + private _destroy = new Subject(); constructor( @@ -69,10 +71,8 @@ export class ShareComponent implements OnInit, OnDestroy { } }); - const cipherDomain = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); this.cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); @@ -100,10 +100,8 @@ export class ShareComponent implements OnInit, OnDestroy { return; } - const cipherDomain = await this.cipherService.get(this.cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipherView = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), ); diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 4bd3e9710fb..6601ce7bdff 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -102,7 +102,7 @@ export class AddEditComponent implements OnInit, OnDestroy { private personalOwnershipPolicyAppliesToActiveUser: boolean; private previousCipherId: string; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); @@ -259,12 +259,13 @@ export class AddEditComponent implements OnInit, OnDestroy { this.title = this.i18nService.t("addItem"); } - const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(); - const activeUserId = await firstValueFrom(this.activeUserId$); + + const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(activeUserId); + if (this.cipher == null) { if (this.editMode) { - const cipher = await this.loadCipher(); + const cipher = await this.loadCipher(activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -401,9 +402,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.id = null; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const cipher = await this.encryptCipher(activeUserId); try { this.formPromise = this.saveCipher(cipher); @@ -497,7 +496,8 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - this.deletePromise = this.deleteCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.deletePromise = this.deleteCipher(activeUserId); await this.deletePromise; this.platformUtilsService.showToast( "success", @@ -521,7 +521,8 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - this.restorePromise = this.restoreCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.restorePromise = this.restoreCipher(activeUserId); await this.restorePromise; this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); this.onRestoredCipher.emit(this.cipher); @@ -690,8 +691,8 @@ export class AddEditComponent implements OnInit, OnDestroy { return allCollections.filter((c) => !c.readOnly); } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected encryptCipher(userId: UserId) { @@ -711,14 +712,14 @@ export class AddEditComponent implements OnInit, OnDestroy { : this.cipherService.updateWithServer(cipher, orgAdmin); } - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id, this.asAdmin) - : this.cipherService.softDeleteWithServer(this.cipher.id, this.asAdmin); + ? this.cipherService.deleteWithServer(this.cipher.id, userId, this.asAdmin) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId, this.asAdmin); } - protected restoreCipher() { - return this.cipherService.restoreWithServer(this.cipher.id, this.asAdmin); + protected restoreCipher(userId: UserId) { + return this.cipherService.restoreWithServer(this.cipher.id, userId, this.asAdmin); } /** @@ -738,8 +739,10 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.ownershipOptions[0].value; } - async loadAddEditCipherInfo(): Promise { - const addEditCipherInfo: any = await firstValueFrom(this.cipherService.addEditCipherInfo$); + async loadAddEditCipherInfo(userId: UserId): Promise { + const addEditCipherInfo: any = await firstValueFrom( + this.cipherService.addEditCipherInfo$(userId), + ); const loadedSavedInfo = addEditCipherInfo != null; if (loadedSavedInfo) { @@ -752,7 +755,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } } - await this.cipherService.setAddEditCipherInfo(null); + await this.cipherService.setAddEditCipherInfo(null, userId); return loadedSavedInfo; } diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 521d38a1f47..59b18975afb 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -39,6 +39,8 @@ export class AttachmentsComponent implements OnInit { emergencyAccessId?: string = null; protected componentName = ""; + protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( protected cipherService: CipherService, protected i18nService: I18nService, @@ -83,9 +85,7 @@ export class AttachmentsComponent implements OnInit { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; this.cipher = await this.cipherDomain.decrypt( @@ -120,7 +120,8 @@ export class AttachmentsComponent implements OnInit { } try { - this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id, activeUserId); await this.deletePromises[attachment.id]; this.platformUtilsService.showToast("success", null, this.i18nService.t("deletedAttachment")); const i = this.cipher.attachments.indexOf(attachment); @@ -200,10 +201,8 @@ export class AttachmentsComponent implements OnInit { } protected async init() { - this.cipherDomain = await this.loadCipher(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + this.cipherDomain = await this.loadCipher(activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), ); @@ -254,9 +253,7 @@ export class AttachmentsComponent implements OnInit { ? attachment.key : await this.keyService.getOrgKey(this.cipher.organizationId); const decBuf = await this.encryptService.decryptToBytes(encBuf, key); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, @@ -269,7 +266,10 @@ export class AttachmentsComponent implements OnInit { ); // 3. Delete old - this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id); + this.deletePromises[attachment.id] = this.deleteCipherAttachment( + attachment.id, + activeUserId, + ); await this.deletePromises[attachment.id]; const foundAttachment = this.cipher.attachments.filter((a2) => a2.id === attachment.id); if (foundAttachment.length > 0) { @@ -297,16 +297,16 @@ export class AttachmentsComponent implements OnInit { } } - protected loadCipher() { - return this.cipherService.get(this.cipherId); + protected loadCipher(userId: UserId) { + return this.cipherService.get(this.cipherId, userId); } protected saveCipherAttachment(file: File, userId: UserId) { return this.cipherService.saveAttachmentWithServer(this.cipherDomain, file, userId); } - protected deleteCipherAttachment(attachmentId: string) { - return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId); + protected deleteCipherAttachment(attachmentId: string, userId: UserId) { + return this.cipherService.deleteAttachmentWithServer(this.cipher.id, attachmentId, userId); } protected async reupload(attachment: AttachmentView) { diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 942a34c58bb..461fe8cd17a 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -37,10 +37,10 @@ export class PasswordHistoryComponent implements OnInit { } protected async init() { - const cipher = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + const cipher = await this.cipherService.get(this.cipherId, activeUserId); const decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 052ab95f3bc..e8507b36fc1 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { BehaviorSubject, Subject, from, switchMap, takeUntil } from "rxjs"; +import { BehaviorSubject, Subject, firstValueFrom, from, map, switchMap, takeUntil } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -40,6 +41,7 @@ export class VaultItemsComponent implements OnInit, OnDestroy { constructor( protected searchService: SearchService, protected cipherService: CipherService, + protected accountService: AccountService, ) {} ngOnInit(): void { @@ -117,7 +119,10 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted; protected async doSearch(indexedCiphers?: CipherView[]) { - indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted()); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted(activeUserId)); this.ciphers = await this.searchService.searchCiphers( this.searchText, [this.filter, this.deletedFilter], diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 6bea4cd6150..0cf50a851ba 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -29,7 +29,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { CollectionId } from "@bitwarden/common/types/guid"; +import { CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -142,8 +142,8 @@ export class ViewComponent implements OnDestroy, OnInit { async load() { this.cleanUp(); - const cipher = await this.cipherService.get(this.cipherId); const activeUserId = await firstValueFrom(this.activeUserId$); + const cipher = await this.cipherService.get(this.cipherId, activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -241,7 +241,8 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - await this.deleteCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.deleteCipher(activeUserId); this.platformUtilsService.showToast( "success", null, @@ -261,7 +262,8 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - await this.restoreCipher(); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.restoreCipher(activeUserId); this.platformUtilsService.showToast("success", null, this.i18nService.t("restoredItem")); this.onRestoredCipher.emit(this.cipher); } catch (e) { @@ -361,7 +363,8 @@ export class ViewComponent implements OnDestroy, OnInit { } if (cipherId) { - await this.cipherService.updateLastLaunchedDate(cipherId); + const activeUserId = await firstValueFrom(this.activeUserId$); + await this.cipherService.updateLastLaunchedDate(cipherId, activeUserId); } this.platformUtilsService.launchUri(uri.launchUri); @@ -469,14 +472,14 @@ export class ViewComponent implements OnDestroy, OnInit { a.downloading = false; } - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.cipher.isDeleted - ? this.cipherService.deleteWithServer(this.cipher.id) - : this.cipherService.softDeleteWithServer(this.cipher.id); + ? this.cipherService.deleteWithServer(this.cipher.id, userId) + : this.cipherService.softDeleteWithServer(this.cipher.id, userId); } - protected restoreCipher() { - return this.cipherService.restoreWithServer(this.cipher.id); + protected restoreCipher(userId: UserId) { + return this.cipherService.restoreWithServer(this.cipher.id, userId); } protected async promptPassword() { 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 dd0b49f356a..e3de029cd31 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 @@ -70,8 +70,9 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti if (organizationId == null || organizationId == "MyVault") { folders = storedFolders; } else { + const activeUserId = await firstValueFrom(this.activeUserId$); // Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder - const ciphers = await this.cipherService.getAllDecrypted(); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId); folders = storedFolders.filter( (f) => orgCiphers.some((oc) => oc.folderId == f.id) || f.id == null, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 376f4dcdced..b1910828210 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -46,6 +46,8 @@ const KeyUsages: KeyUsage[] = ["sign"]; export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstraction { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, @@ -145,10 +147,8 @@ export class Fido2AuthenticatorService try { keyPair = await createKeyPair(); pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey); - const encrypted = await this.cipherService.get(cipherId); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); + const encrypted = await this.cipherService.get(cipherId, activeUserId); cipher = await encrypted.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), @@ -308,9 +308,7 @@ export class Fido2AuthenticatorService }; if (selectedFido2Credential.counter > 0) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); await this.cipherService.clearCache(activeUserId); @@ -400,7 +398,8 @@ export class Fido2AuthenticatorService return []; } - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.activeUserId$); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers .filter( (cipher) => @@ -421,7 +420,8 @@ export class Fido2AuthenticatorService return []; } - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.activeUserId$); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => !cipher.isDeleted && @@ -438,7 +438,8 @@ export class Fido2AuthenticatorService } private async findCredentialsByRp(rpId: string): Promise { - const ciphers = await this.cipherService.getAllDecrypted(); + const activeUserId = await firstValueFrom(this.activeUserId$); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => !cipher.isDeleted && diff --git a/libs/common/src/platform/sync/core-sync.service.ts b/libs/common/src/platform/sync/core-sync.service.ts index cfa9030c9de..92a10baf6d2 100644 --- a/libs/common/src/platform/sync/core-sync.service.ts +++ b/libs/common/src/platform/sync/core-sync.service.ts @@ -129,12 +129,18 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncUpsertCipher(notification: SyncCipherNotification, isEdit: boolean): Promise { + async syncUpsertCipher( + notification: SyncCipherNotification, + isEdit: boolean, + userId: UserId, + ): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + if (authStatus >= AuthenticationStatus.Locked) { try { let shouldUpdate = true; - const localCipher = await this.cipherService.get(notification.id); + const localCipher = await this.cipherService.get(notification.id, userId); if (localCipher != null && localCipher.revisionDate >= notification.revisionDate) { shouldUpdate = false; } @@ -182,7 +188,7 @@ export abstract class CoreSyncService implements SyncService { } } catch (e) { if (e != null && e.statusCode === 404 && isEdit) { - await this.cipherService.delete(notification.id); + await this.cipherService.delete(notification.id, userId); this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id }); return this.syncCompleted(true); } @@ -191,10 +197,12 @@ export abstract class CoreSyncService implements SyncService { return this.syncCompleted(false); } - async syncDeleteCipher(notification: SyncCipherNotification): Promise { + async syncDeleteCipher(notification: SyncCipherNotification, userId: UserId): Promise { this.syncStarted(); - if (await this.stateService.getIsAuthenticated()) { - await this.cipherService.delete(notification.id); + + const authStatus = await firstValueFrom(this.authService.authStatusFor$(userId)); + if (authStatus >= AuthenticationStatus.Locked) { + await this.cipherService.delete(notification.id, userId); this.messageSender.send("syncedDeletedCipher", { cipherId: notification.id }); return this.syncCompleted(true); } diff --git a/libs/common/src/platform/sync/sync.service.ts b/libs/common/src/platform/sync/sync.service.ts index 6763e01cab7..967e4db27a5 100644 --- a/libs/common/src/platform/sync/sync.service.ts +++ b/libs/common/src/platform/sync/sync.service.ts @@ -62,8 +62,9 @@ export abstract class SyncService { abstract syncUpsertCipher( notification: SyncCipherNotification, isEdit: boolean, + userId: UserId, ): Promise; - abstract syncDeleteCipher(notification: SyncFolderNotification): Promise; + abstract syncDeleteCipher(notification: SyncFolderNotification, userId: UserId): Promise; abstract syncUpsertSend(notification: SyncSendNotification, isEdit: boolean): Promise; abstract syncDeleteSend(notification: SyncSendNotification): Promise; } diff --git a/libs/common/src/services/event/event-collection.service.ts b/libs/common/src/services/event/event-collection.service.ts index b06985e0ba7..92e9488fea6 100644 --- a/libs/common/src/services/event/event-collection.service.ts +++ b/libs/common/src/services/event/event-collection.service.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { firstValueFrom, map, from, zip, Observable } from "rxjs"; +import { UserId } from "@bitwarden/common/types/guid"; + import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service"; import { EventUploadService } from "../../abstractions/event/event-upload.service"; import { OrganizationService } from "../../admin-console/abstractions/organization/organization.service.abstraction"; @@ -18,6 +20,7 @@ import { EVENT_COLLECTION } from "./key-definitions"; export class EventCollectionService implements EventCollectionServiceAbstraction { private orgIds$: Observable; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); constructor( private cipherService: CipherService, @@ -42,10 +45,10 @@ export class EventCollectionService implements EventCollectionServiceAbstraction ciphers: CipherView[], uploadImmediately = false, ): Promise { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(this.activeUserId$); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); - if (!(await this.shouldUpdate(null, eventType, ciphers))) { + if (!(await this.shouldUpdate(userId, null, eventType, ciphers))) { return; } @@ -86,10 +89,10 @@ export class EventCollectionService implements EventCollectionServiceAbstraction uploadImmediately = false, organizationId: string = null, ): Promise { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(this.activeUserId$); const eventStore = this.stateProvider.getUser(userId, EVENT_COLLECTION); - if (!(await this.shouldUpdate(organizationId, eventType, undefined, cipherId))) { + if (!(await this.shouldUpdate(userId, organizationId, eventType, undefined, cipherId))) { return; } @@ -111,16 +114,18 @@ export class EventCollectionService implements EventCollectionServiceAbstraction } /** Verifies if the event collection should be updated for the provided information + * @param userId the active user's id * @param cipherId the cipher for the event * @param organizationId the organization for the event */ private async shouldUpdate( + userId: UserId, organizationId: string = null, eventType: EventType = null, ciphers: CipherView[] = [], cipherId?: string, ): Promise { - const cipher$ = from(this.cipherService.get(cipherId)); + const cipher$ = from(this.cipherService.get(cipherId, userId)); const [authStatus, orgIds, cipher] = await firstValueFrom( zip(this.authService.activeAccountStatus$, this.orgIds$, cipher$), diff --git a/libs/common/src/services/notifications.service.ts b/libs/common/src/services/notifications.service.ts index 4a14332af8a..bb76dd5db5f 100644 --- a/libs/common/src/services/notifications.service.ts +++ b/libs/common/src/services/notifications.service.ts @@ -157,11 +157,15 @@ export class NotificationsService implements NotificationsServiceAbstraction { await this.syncService.syncUpsertCipher( notification.payload as SyncCipherNotification, notification.type === NotificationType.SyncCipherUpdate, + payloadUserId, ); break; case NotificationType.SyncCipherDelete: case NotificationType.SyncLoginDelete: - await this.syncService.syncDeleteCipher(notification.payload as SyncCipherNotification); + await this.syncService.syncDeleteCipher( + notification.payload as SyncCipherNotification, + payloadUserId, + ); break; case NotificationType.SyncFolderCreate: case NotificationType.SyncFolderUpdate: diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 870cb8b3d73..8d80917d100 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -19,14 +19,14 @@ import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService implements UserKeyRotationDataProvider { - cipherViews$: Observable; - ciphers$: Observable>; - localData$: Observable>; + cipherViews$: (userId: UserId) => Observable; + ciphers$: (userId: UserId) => Observable>; + localData$: (userId: UserId) => Observable>; /** * An observable monitoring the add/edit cipher info saved to memory. */ - addEditCipherInfo$: Observable; - clearCache: (userId?: string) => Promise; + addEditCipherInfo$: (userId: UserId) => Observable; + clearCache: (userId: UserId) => Promise; encrypt: ( model: CipherView, userId: UserId, @@ -36,12 +36,17 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; - get: (id: string) => Promise; - getAll: () => Promise; - getAllDecrypted: () => Promise; - getAllDecryptedForGrouping: (groupingId: string, folder?: boolean) => Promise; + get: (id: string, userId: UserId) => Promise; + getAll: (userId: UserId) => Promise; + getAllDecrypted: (userId: UserId) => Promise; + getAllDecryptedForGrouping: ( + groupingId: string, + userId: UserId, + folder?: boolean, + ) => Promise; getAllDecryptedForUrl: ( url: string, + userId: UserId, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchStrategySetting, ) => Promise; @@ -57,12 +62,20 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - getLastUsedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getLastLaunchedForUrl: (url: string, autofillOnPageLoad: boolean) => Promise; - getNextCipherForUrl: (url: string) => Promise; + getLastUsedForUrl: ( + url: string, + userId: UserId, + autofillOnPageLoad: boolean, + ) => Promise; + getLastLaunchedForUrl: ( + url: string, + userId: UserId, + autofillOnPageLoad: boolean, + ) => Promise; + getNextCipherForUrl: (url: string, userId: UserId) => Promise; updateLastUsedIndexForUrl: (url: string) => void; - updateLastUsedDate: (id: string) => Promise; - updateLastLaunchedDate: (id: string) => Promise; + updateLastUsedDate: (id: string, userId: UserId) => Promise; + updateLastLaunchedDate: (id: string, userId: UserId) => Promise; saveNeverDomain: (domain: string) => Promise; /** * Create a cipher with the server @@ -111,10 +124,11 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + saveCollectionsWithServer: (cipher: Cipher, userId: UserId) => Promise; /** * Save the collections for a cipher with the server as an admin. @@ -125,12 +139,14 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise>; replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise; clear: (userId?: string) => Promise; - moveManyWithServer: (ids: string[], folderId: string) => Promise; - delete: (id: string | string[]) => Promise; - deleteWithServer: (id: string, asAdmin?: boolean) => Promise; - deleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise; - deleteAttachment: (id: string, attachmentId: string) => Promise; - deleteAttachmentWithServer: (id: string, attachmentId: string) => Promise; + moveManyWithServer: (ids: string[], folderId: string, userId: UserId) => Promise; + delete: (id: string | string[], userId: UserId) => Promise; + deleteWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; + deleteManyWithServer: (ids: string[], userId: UserId, asAdmin?: boolean) => Promise; + deleteAttachment: (id: string, attachmentId: string, userId: UserId) => Promise; + deleteAttachmentWithServer: (id: string, attachmentId: string, userId: UserId) => Promise; sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number; sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number; getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; - softDelete: (id: string | string[]) => Promise; - softDeleteWithServer: (id: string, asAdmin?: boolean) => Promise; - softDeleteManyWithServer: (ids: string[], asAdmin?: boolean) => Promise; + softDelete: (id: string | string[], userId: UserId) => Promise; + softDeleteWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; + softDeleteManyWithServer: (ids: string[], userId: UserId, asAdmin?: boolean) => Promise; restore: ( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], + userId: UserId, ) => Promise; - restoreWithServer: (id: string, asAdmin?: boolean) => Promise; + restoreWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; restoreManyWithServer: (ids: string[], orgId?: string) => Promise; getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise; - setAddEditCipherInfo: (value: AddEditCipherInfo) => Promise; + setAddEditCipherInfo: (value: AddEditCipherInfo, userId: UserId) => Promise; /** * Returns user ciphers re-encrypted with the new user key. * @param originalUserKey the original user key @@ -176,6 +193,6 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - getNextCardCipher: () => Promise; - getNextIdentityCipher: () => Promise; + getNextCardCipher: (userId: UserId) => Promise; + getNextIdentityCipher: (userId: UserId) => Promise; } diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 6b225af0d84..40024d3f417 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -384,7 +384,11 @@ describe("Cipher Service", () => { Cipher1: cipher1, Cipher2: cipher2, }); - cipherService.cipherViews$ = decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))); + jest + .spyOn(cipherService, "cipherViews$") + .mockImplementation((userId: UserId) => + decryptedCiphers.pipe(map((ciphers) => Object.values(ciphers))), + ); encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Cipher Key"); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 8711496b374..43541cfe140 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -36,7 +36,7 @@ import Domain from "../../platform/models/domain/domain-base"; import { EncArrayBuffer } from "../../platform/models/domain/enc-array-buffer"; import { EncString } from "../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; -import { ActiveUserState, StateProvider } from "../../platform/state"; +import { StateProvider } from "../../platform/state"; import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid"; import { OrgKey, UserKey } from "../../types/key"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; @@ -95,25 +95,6 @@ export class CipherService implements CipherServiceAbstraction { */ private forceCipherViews$: Subject = new Subject(); - localData$: Observable>; - ciphers$: Observable>; - - /** - * Observable that emits an array of decrypted ciphers for the active user. - * This observable will not emit until the encrypted ciphers have either been loaded from state or after sync. - * - * A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that - * decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete. - * - */ - cipherViews$: Observable; - addEditCipherInfo$: Observable; - - private localDataState: ActiveUserState>; - private encryptedCiphersState: ActiveUserState>; - private decryptedCiphersState: ActiveUserState>; - private addEditCipherInfoState: ActiveUserState; - constructor( private keyService: KeyService, private domainSettingsService: DomainSettingsService, @@ -128,22 +109,36 @@ export class CipherService implements CipherServiceAbstraction { private configService: ConfigService, private stateProvider: StateProvider, private accountService: AccountService, - ) { - this.localDataState = this.stateProvider.getActive(LOCAL_DATA_KEY); - this.encryptedCiphersState = this.stateProvider.getActive(ENCRYPTED_CIPHERS); - this.decryptedCiphersState = this.stateProvider.getActive(DECRYPTED_CIPHERS); - this.addEditCipherInfoState = this.stateProvider.getActive(ADD_EDIT_CIPHER_INFO_KEY); + ) {} - this.localData$ = this.localDataState.state$.pipe(map((data) => data ?? {})); - this.ciphers$ = this.encryptedCiphersState.state$.pipe(map((ciphers) => ciphers ?? {})); + localData$(userId: UserId): Observable> { + return this.localDataState(userId).state$.pipe(map((data) => data ?? {})); + } + + /** + * Observable that emits an object of encrypted ciphers for the active user. + */ + ciphers$(userId: UserId): Observable> { + return this.encryptedCiphersState(userId).state$.pipe(map((ciphers) => ciphers ?? {})); + } - // Decrypted ciphers depend on both ciphers and local data and need to be updated when either changes - this.cipherViews$ = combineLatest([this.encryptedCiphersState.state$, this.localData$]).pipe( + /** + * Observable that emits an array of decrypted ciphers for the active user. + * This observable will not emit until the encrypted ciphers have either been loaded from state or after sync. + * + * A `null` value indicates that the latest encrypted ciphers have not been decrypted yet and that + * decryption is in progress. The latest decrypted ciphers will be emitted once decryption is complete. + */ + cipherViews$(userId: UserId): Observable { + return combineLatest([this.encryptedCiphersState(userId).state$, this.localData$(userId)]).pipe( filter(([ciphers]) => ciphers != null), // Skip if ciphers haven't been loaded yor synced yet - switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted())), + switchMap(() => merge(this.forceCipherViews$, this.getAllDecrypted(userId))), shareReplay({ bufferSize: 1, refCount: true }), ); - this.addEditCipherInfo$ = this.addEditCipherInfoState.state$; + } + + addEditCipherInfo$(userId: UserId): Observable { + return this.addEditCipherInfoState(userId).state$; } async setDecryptedCipherCache(value: CipherView[], userId: UserId) { @@ -190,7 +185,7 @@ export class CipherService implements CipherServiceAbstraction { ): Promise { if (model.id != null) { if (originalCipher == null) { - originalCipher = await this.get(model.id); + originalCipher = await this.get(model.id, userId); } if (originalCipher != null) { await this.updateModelfromExistingCipher(model, originalCipher, userId); @@ -344,22 +339,22 @@ export class CipherService implements CipherServiceAbstraction { return ph; } - async get(id: string): Promise { - const ciphers = await firstValueFrom(this.ciphers$); + async get(id: string, userId: UserId): Promise { + const ciphers = await firstValueFrom(this.ciphers$(userId)); // eslint-disable-next-line if (ciphers == null || !ciphers.hasOwnProperty(id)) { return null; } - const localData = await firstValueFrom(this.localData$); + const localData = await firstValueFrom(this.localData$(userId)); const cipherId = id as CipherId; return new Cipher(ciphers[cipherId], localData ? localData[cipherId] : null); } - async getAll(): Promise { - const localData = await firstValueFrom(this.localData$); - const ciphers = await firstValueFrom(this.ciphers$); + async getAll(userId: UserId): Promise { + const localData = await firstValueFrom(this.localData$(userId)); + const ciphers = await firstValueFrom(this.ciphers$(userId)); const response: Cipher[] = []; for (const id in ciphers) { // eslint-disable-next-line @@ -377,28 +372,22 @@ export class CipherService implements CipherServiceAbstraction { * @deprecated Use `cipherViews$` observable instead */ @sequentialize(() => "getAllDecrypted") - async getAllDecrypted(): Promise { - let decCiphers = await this.getDecryptedCiphers(); + async getAllDecrypted(userId: UserId): Promise { + let decCiphers = await this.getDecryptedCiphers(userId); if (decCiphers != null && decCiphers.length !== 0) { - await this.reindexCiphers(); - return await this.getDecryptedCiphers(); - } - - const activeUserId = await firstValueFrom(this.stateProvider.activeUserId$); - - if (activeUserId == null) { - return []; + await this.reindexCiphers(userId); + return await this.getDecryptedCiphers(userId); } - decCiphers = await this.decryptCiphers(await this.getAll(), activeUserId); + decCiphers = await this.decryptCiphers(await this.getAll(userId), userId); - await this.setDecryptedCipherCache(decCiphers, activeUserId); + await this.setDecryptedCipherCache(decCiphers, userId); return decCiphers; } - private async getDecryptedCiphers() { + private async getDecryptedCiphers(userId: UserId) { return Object.values( - await firstValueFrom(this.decryptedCiphersState.state$.pipe(map((c) => c ?? {}))), + await firstValueFrom(this.decryptedCiphersState(userId).state$.pipe(map((c) => c ?? {}))), ); } @@ -443,18 +432,21 @@ export class CipherService implements CipherServiceAbstraction { return decCiphers; } - private async reindexCiphers() { - const userId = await this.stateService.getUserId(); + private async reindexCiphers(userId: UserId) { const reindexRequired = this.searchService != null && ((await firstValueFrom(this.searchService.indexedEntityId$)) ?? userId) !== userId; if (reindexRequired) { - await this.searchService.indexCiphers(await this.getDecryptedCiphers(), userId); + await this.searchService.indexCiphers(await this.getDecryptedCiphers(userId), userId); } } - async getAllDecryptedForGrouping(groupingId: string, folder = true): Promise { - const ciphers = await this.getAllDecrypted(); + async getAllDecryptedForGrouping( + groupingId: string, + userId: UserId, + folder = true, + ): Promise { + const ciphers = await this.getAllDecrypted(userId); return ciphers.filter((cipher) => { if (cipher.isDeleted) { @@ -476,10 +468,11 @@ export class CipherService implements CipherServiceAbstraction { async getAllDecryptedForUrl( url: string, + userId: UserId, includeOtherTypes?: CipherType[], defaultMatch: UriMatchStrategySetting = null, ): Promise { - const ciphers = await this.getAllDecrypted(); + const ciphers = await this.getAllDecrypted(userId); return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch); } @@ -521,8 +514,11 @@ export class CipherService implements CipherServiceAbstraction { }); } - private async getAllDecryptedCiphersOfType(type: CipherType[]): Promise { - const ciphers = await this.getAllDecrypted(); + private async getAllDecryptedCiphersOfType( + type: CipherType[], + userId: UserId, + ): Promise { + const ciphers = await this.getAllDecrypted(userId); return ciphers .filter((cipher) => cipher.deletedDate == null && type.includes(cipher.type)) .sort((a, b) => this.sortCiphersByLastUsedThenName(a, b)); @@ -565,23 +561,31 @@ export class CipherService implements CipherServiceAbstraction { return decCiphers; } - async getLastUsedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, true, false, autofillOnPageLoad); + async getLastUsedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad = false, + ): Promise { + return this.getCipherForUrl(url, userId, true, false, autofillOnPageLoad); } - async getLastLaunchedForUrl(url: string, autofillOnPageLoad = false): Promise { - return this.getCipherForUrl(url, false, true, autofillOnPageLoad); + async getLastLaunchedForUrl( + url: string, + userId: UserId, + autofillOnPageLoad = false, + ): Promise { + return this.getCipherForUrl(url, userId, false, true, autofillOnPageLoad); } - async getNextCipherForUrl(url: string): Promise { - return this.getCipherForUrl(url, false, false, false); + async getNextCipherForUrl(url: string, userId: UserId): Promise { + return this.getCipherForUrl(url, userId, false, false, false); } - async getNextCardCipher(): Promise { + async getNextCardCipher(userId: UserId): Promise { const cacheKey = "cardCiphers"; if (!this.sortedCiphersCache.isCached(cacheKey)) { - const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card]); + const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Card], userId); if (!ciphers?.length) { return null; } @@ -592,11 +596,11 @@ export class CipherService implements CipherServiceAbstraction { return this.sortedCiphersCache.getNext(cacheKey); } - async getNextIdentityCipher(): Promise { + async getNextIdentityCipher(userId: UserId): Promise { const cacheKey = "identityCiphers"; if (!this.sortedCiphersCache.isCached(cacheKey)) { - const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity]); + const ciphers = await this.getAllDecryptedCiphersOfType([CipherType.Identity], userId); if (!ciphers?.length) { return null; } @@ -611,9 +615,8 @@ export class CipherService implements CipherServiceAbstraction { this.sortedCiphersCache.updateLastUsedIndex(url); } - async updateLastUsedDate(id: string): Promise { - const userId = await firstValueFrom(this.stateProvider.activeUserId$); - let ciphersLocalData = await firstValueFrom(this.localData$); + async updateLastUsedDate(id: string, userId: UserId): Promise { + let ciphersLocalData = await firstValueFrom(this.localData$(userId)); if (!ciphersLocalData) { ciphersLocalData = {}; @@ -628,9 +631,9 @@ export class CipherService implements CipherServiceAbstraction { }; } - await this.localDataState.update(() => ciphersLocalData); + await this.localDataState(userId).update(() => ciphersLocalData); - const decryptedCipherCache = await this.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(userId); if (!decryptedCipherCache) { return; } @@ -645,9 +648,8 @@ export class CipherService implements CipherServiceAbstraction { await this.setDecryptedCiphers(decryptedCipherCache, userId); } - async updateLastLaunchedDate(id: string): Promise { - const userId = await firstValueFrom(this.stateProvider.activeUserId$); - let ciphersLocalData = await firstValueFrom(this.localData$); + async updateLastLaunchedDate(id: string, userId: UserId): Promise { + let ciphersLocalData = await firstValueFrom(this.localData$(userId)); if (!ciphersLocalData) { ciphersLocalData = {}; @@ -659,9 +661,9 @@ export class CipherService implements CipherServiceAbstraction { lastUsedDate: currentTime, }; - await this.localDataState.update(() => ciphersLocalData); + await this.localDataState(userId).update(() => ciphersLocalData); - const decryptedCipherCache = await this.getDecryptedCiphers(); + const decryptedCipherCache = await this.getDecryptedCiphers(userId); if (!decryptedCipherCache) { return; } @@ -865,13 +867,13 @@ export class CipherService implements CipherServiceAbstraction { return new Cipher(cData); } - async saveCollectionsWithServer(cipher: Cipher): Promise { + async saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise { const request = new CipherCollectionsRequest(cipher.collectionIds); const response = await this.apiService.putCipherCollections(cipher.id, request); // The response will now check for an unavailable value. This value determines whether // the user still has Can Manage access to the item after updating. if (response.unavailable) { - await this.delete(cipher.id); + await this.delete(cipher.id, userId); return; } const data = new CipherData(response.cipher); @@ -895,6 +897,7 @@ export class CipherService implements CipherServiceAbstraction { */ async bulkUpdateCollectionsWithServer( orgId: OrganizationId, + userId: UserId, cipherIds: CipherId[], collectionIds: CollectionId[], removeCollections: boolean = false, @@ -909,7 +912,7 @@ export class CipherService implements CipherServiceAbstraction { await this.apiService.send("POST", "/ciphers/bulk-collections", request, true, false); // Update the local state - const ciphers = await firstValueFrom(this.ciphers$); + const ciphers = await firstValueFrom(this.ciphers$(userId)); for (const id of cipherIds) { const cipher = ciphers[id]; @@ -926,7 +929,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } async upsert(cipher: CipherData | CipherData[]): Promise> { @@ -967,10 +970,10 @@ export class CipherService implements CipherServiceAbstraction { await this.clearCache(userId); } - async moveManyWithServer(ids: string[], folderId: string): Promise { + async moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise { await this.apiService.putMoveCiphers(new CipherBulkMoveRequest(ids, folderId)); - let ciphers = await firstValueFrom(this.ciphers$); + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { ciphers = {}; } @@ -983,11 +986,11 @@ export class CipherService implements CipherServiceAbstraction { }); await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } - async delete(id: string | string[]): Promise { - const ciphers = await firstValueFrom(this.ciphers$); + async delete(id: string | string[], userId: UserId): Promise { + const ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1005,31 +1008,31 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => ciphers); + await this.encryptedCiphersState(userId).update(() => ciphers); } - async deleteWithServer(id: string, asAdmin = false): Promise { + async deleteWithServer(id: string, userId: UserId, asAdmin = false): Promise { if (asAdmin) { await this.apiService.deleteCipherAdmin(id); } else { await this.apiService.deleteCipher(id); } - await this.delete(id); + await this.delete(id, userId); } - async deleteManyWithServer(ids: string[], asAdmin = false): Promise { + async deleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise { const request = new CipherBulkDeleteRequest(ids); if (asAdmin) { await this.apiService.deleteManyCiphersAdmin(request); } else { await this.apiService.deleteManyCiphers(request); } - await this.delete(ids); + await this.delete(ids, userId); } - async deleteAttachment(id: string, attachmentId: string): Promise { - let ciphers = await firstValueFrom(this.ciphers$); + async deleteAttachment(id: string, attachmentId: string, userId: UserId): Promise { + let ciphers = await firstValueFrom(this.ciphers$(userId)); const cipherId = id as CipherId; // eslint-disable-next-line if (ciphers == null || !ciphers.hasOwnProperty(id) || ciphers[cipherId].attachments == null) { @@ -1043,7 +1046,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } @@ -1051,13 +1054,17 @@ export class CipherService implements CipherServiceAbstraction { }); } - async deleteAttachmentWithServer(id: string, attachmentId: string): Promise { + async deleteAttachmentWithServer( + id: string, + attachmentId: string, + userId: UserId, + ): Promise { try { await this.apiService.deleteCipherAttachment(id, attachmentId); } catch (e) { return Promise.reject((e as ErrorResponse).getSingleMessage()); } - await this.deleteAttachment(id, attachmentId); + await this.deleteAttachment(id, attachmentId, userId); } sortCiphersByLastUsed(a: CipherView, b: CipherView): number { @@ -1130,8 +1137,8 @@ export class CipherService implements CipherServiceAbstraction { }; } - async softDelete(id: string | string[]): Promise { - let ciphers = await firstValueFrom(this.ciphers$); + async softDelete(id: string | string[], userId: UserId): Promise { + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1150,7 +1157,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } @@ -1158,17 +1165,17 @@ export class CipherService implements CipherServiceAbstraction { }); } - async softDeleteWithServer(id: string, asAdmin = false): Promise { + async softDeleteWithServer(id: string, userId: UserId, asAdmin = false): Promise { if (asAdmin) { await this.apiService.putDeleteCipherAdmin(id); } else { await this.apiService.putDeleteCipher(id); } - await this.softDelete(id); + await this.softDelete(id, userId); } - async softDeleteManyWithServer(ids: string[], asAdmin = false): Promise { + async softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin = false): Promise { const request = new CipherBulkDeleteRequest(ids); if (asAdmin) { await this.apiService.putDeleteManyCiphersAdmin(request); @@ -1176,13 +1183,14 @@ export class CipherService implements CipherServiceAbstraction { await this.apiService.putDeleteManyCiphers(request); } - await this.softDelete(ids); + await this.softDelete(ids, userId); } async restore( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], + userId: UserId, ) { - let ciphers = await firstValueFrom(this.ciphers$); + let ciphers = await firstValueFrom(this.ciphers$(userId)); if (ciphers == null) { return; } @@ -1203,7 +1211,7 @@ export class CipherService implements CipherServiceAbstraction { } await this.clearCache(); - await this.encryptedCiphersState.update(() => { + await this.encryptedCiphersState(userId).update(() => { if (ciphers == null) { ciphers = {}; } @@ -1211,7 +1219,7 @@ export class CipherService implements CipherServiceAbstraction { }); } - async restoreWithServer(id: string, asAdmin = false): Promise { + async restoreWithServer(id: string, userId: UserId, asAdmin = false): Promise { let response; if (asAdmin) { response = await this.apiService.putRestoreCipherAdmin(id); @@ -1219,14 +1227,14 @@ export class CipherService implements CipherServiceAbstraction { response = await this.apiService.putRestoreCipher(id); } - await this.restore({ id: id, revisionDate: response.revisionDate }); + await this.restore({ id: id, revisionDate: response.revisionDate }, userId); } /** * No longer using an asAdmin Param. Org Vault bulkRestore will assess if an item is unassigned or editable * The Org Vault will pass those ids an array as well as the orgId when calling bulkRestore */ - async restoreManyWithServer(ids: string[], orgId: string = null): Promise { + async restoreManyWithServer(ids: string[], userId: UserId, orgId: string = null): Promise { let response; if (orgId) { @@ -1241,7 +1249,7 @@ export class CipherService implements CipherServiceAbstraction { for (const cipher of response.data) { restores.push({ id: cipher.id, revisionDate: cipher.revisionDate }); } - await this.restore(restores); + await this.restore(restores, userId); } async getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise { @@ -1251,8 +1259,8 @@ export class CipherService implements CipherServiceAbstraction { ); } - async setAddEditCipherInfo(value: AddEditCipherInfo) { - await this.addEditCipherInfoState.update(() => value, { + async setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId) { + await this.addEditCipherInfoState(userId).update(() => value, { shouldUpdate: (current) => !(current == null && value == null), }); } @@ -1271,7 +1279,7 @@ export class CipherService implements CipherServiceAbstraction { let encryptedCiphers: CipherWithIdRequest[] = []; - const ciphers = await firstValueFrom(this.cipherViews$); + const ciphers = await firstValueFrom(this.cipherViews$(userId)); if (!ciphers) { return encryptedCiphers; } @@ -1290,6 +1298,34 @@ export class CipherService implements CipherServiceAbstraction { return encryptedCiphers; } + /** + * @returns a SingleUserState + */ + private localDataState(userId: UserId) { + return this.stateProvider.getUser(userId, LOCAL_DATA_KEY); + } + + /** + * @returns a SingleUserState for the encrypted ciphers + */ + private encryptedCiphersState(userId: UserId) { + return this.stateProvider.getUser(userId, ENCRYPTED_CIPHERS); + } + + /** + * @returns a SingleUserState for the decrypted ciphers + */ + private decryptedCiphersState(userId: UserId) { + return this.stateProvider.getUser(userId, DECRYPTED_CIPHERS); + } + + /** + * @returns a SingleUserState for the add/edit cipher info + */ + private addEditCipherInfoState(userId: UserId) { + return this.stateProvider.getUser(userId, ADD_EDIT_CIPHER_INFO_KEY); + } + // Helpers // In the case of a cipher that is being shared with an organization, we want to decrypt the @@ -1593,6 +1629,7 @@ export class CipherService implements CipherServiceAbstraction { private async getCipherForUrl( url: string, + userId: UserId, lastUsed: boolean, lastLaunched: boolean, autofillOnPageLoad: boolean, @@ -1600,7 +1637,7 @@ export class CipherService implements CipherServiceAbstraction { const cacheKey = autofillOnPageLoad ? "autofillOnPageLoad-" + url : url; if (!this.sortedCiphersCache.isCached(cacheKey)) { - let ciphers = await this.getAllDecryptedForUrl(url); + let ciphers = await this.getAllDecryptedForUrl(url, userId); if (!ciphers) { return null; } diff --git a/libs/common/src/vault/services/folder/folder.service.ts b/libs/common/src/vault/services/folder/folder.service.ts index 3aac5374fcb..10785e94efc 100644 --- a/libs/common/src/vault/services/folder/folder.service.ts +++ b/libs/common/src/vault/services/folder/folder.service.ts @@ -190,7 +190,7 @@ export class FolderService implements InternalFolderServiceAbstraction { }); // Items in a deleted folder are re-assigned to "No Folder" - const ciphers = await this.cipherService.getAll(); + const ciphers = await this.cipherService.getAll(userId); if (ciphers != null) { const updates: Cipher[] = []; for (const cId in ciphers) { diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts index ffaa3a82608..f884812574f 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.ts @@ -86,7 +86,7 @@ export class DefaultUserAsymmetricKeysRegenerationService } // The private isn't decryptable, check to see if we can decrypt something with the userKey. - const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey); + const userKeyCanDecrypt = await this.userKeyCanDecrypt(userKey, userId); if (userKeyCanDecrypt) { this.logService.info( "[UserAsymmetricKeyRegeneration] User Asymmetric Key decryption failure detected, attempting regeneration.", @@ -138,8 +138,8 @@ export class DefaultUserAsymmetricKeysRegenerationService ); } - private async userKeyCanDecrypt(userKey: UserKey): Promise { - const ciphers = await this.cipherService.getAll(); + private async userKeyCanDecrypt(userKey: UserKey, userId: UserId): Promise { + const ciphers = await this.cipherService.getAll(userId); const cipher = ciphers.find((cipher) => cipher.organizationId == null); if (cipher != null) { 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 f9df9c7057f..31c8aaa3bc7 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 @@ -72,7 +72,7 @@ export class IndividualVaultExportService ); promises.push( - this.cipherService.getAllDecrypted().then((ciphers) => { + this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => { decCiphers = ciphers.filter((f) => f.deletedDate == null); }), ); @@ -99,7 +99,7 @@ export class IndividualVaultExportService ); promises.push( - this.cipherService.getAll().then((c) => { + this.cipherService.getAll(activeUserId).then((c) => { ciphers = c.filter((f) => f.deletedDate == null); }), ); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 0a4ef86de94..6807d7d458e 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -39,6 +39,8 @@ export class OrganizationVaultExportService extends BaseVaultExportService implements OrganizationVaultExportServiceAbstraction { + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + constructor( private cipherService: CipherService, private apiService: ApiService, @@ -94,9 +96,7 @@ export class OrganizationVaultExportService const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -184,6 +184,7 @@ export class OrganizationVaultExportService let allDecCiphers: CipherView[] = []; let decCollections: CollectionView[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( this.collectionService.getAllDecrypted().then(async (collections) => { @@ -192,7 +193,7 @@ export class OrganizationVaultExportService ); promises.push( - this.cipherService.getAllDecrypted().then((ciphers) => { + this.cipherService.getAllDecrypted(activeUserId).then((ciphers) => { allDecCiphers = ciphers; }), ); @@ -216,6 +217,7 @@ export class OrganizationVaultExportService let allCiphers: Cipher[] = []; let encCollections: Collection[] = []; const promises = []; + const activeUserId = await firstValueFrom(this.activeUserId$); promises.push( this.collectionService.getAll().then((collections) => { @@ -224,7 +226,7 @@ export class OrganizationVaultExportService ); promises.push( - this.cipherService.getAll().then((ciphers) => { + this.cipherService.getAll(activeUserId).then((ciphers) => { allCiphers = ciphers; }), ); diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index e366cdab3fe..ba43a187a3f 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -118,10 +118,10 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { } async ngOnInit(): Promise { - this.cipherDomain = await this.cipherService.get(this.cipherId); this.activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + this.cipherDomain = await this.cipherService.get(this.cipherId, this.activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), ); diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index b1ada907b1d..f7c75d96c08 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -1,7 +1,9 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -36,6 +38,7 @@ export class DeleteAttachmentComponent { private cipherService: CipherService, private logService: LogService, private dialogService: DialogService, + private accountService: AccountService, ) {} delete = async () => { @@ -50,7 +53,19 @@ export class DeleteAttachmentComponent { } try { - await this.cipherService.deleteAttachmentWithServer(this.cipherId, this.attachment.id); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + if (activeUserId == null) { + throw new Error("An active user is expected while deleting an attachment."); + } + + await this.cipherService.deleteAttachmentWithServer( + this.cipherId, + this.attachment.id, + activeUserId, + ); this.toastService.showToast({ variant: "success", 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 93a53345d3a..e361678b0f2 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 @@ -8,7 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { CipherId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -62,7 +62,7 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { ), ), ), - this.getCipher(cipherId), + this.getCipher(activeUserId, cipherId), ]), ); @@ -90,10 +90,10 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { .policyAppliesToActiveUser$(PolicyType.PersonalOwnership) .pipe(map((p) => !p)); - private getCipher(id?: CipherId): Promise { + private getCipher(userId: UserId, id?: CipherId): Promise { if (id == null) { return Promise.resolve(null); } - return this.cipherService.get(id); + return this.cipherService.get(id, userId); } } diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 92a28f9b15f..719a3773c83 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -22,10 +22,10 @@ export class DefaultCipherFormService implements CipherFormService { private accountService: AccountService = inject(AccountService); private apiService: ApiService = inject(ApiService); + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); + async decryptCipher(cipher: Cipher): Promise { - 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), ); @@ -33,9 +33,7 @@ export class DefaultCipherFormService implements CipherFormService { async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { // Passing the original cipher is important here as it is responsible for appending to password history - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(this.activeUserId$); const encryptedCipher = await this.cipherService.encrypt( cipher, activeUserId, @@ -82,7 +80,10 @@ export class DefaultCipherFormService implements CipherFormService { // When using an admin config or the cipher was unassigned, update collections as an admin savedCipher = await this.cipherService.saveCollectionsWithServerAdmin(encryptedCipher); } else { - savedCipher = await this.cipherService.saveCollectionsWithServer(encryptedCipher); + savedCipher = await this.cipherService.saveCollectionsWithServer( + encryptedCipher, + activeUserId, + ); } } diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index e7deb78c868..178dcbcd590 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -2,8 +2,10 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { firstValueFrom, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; @@ -38,10 +40,14 @@ export class AutofillOptionsViewComponent { constructor( private platformUtilsService: PlatformUtilsService, private cipherService: CipherService, + private accountService: AccountService, ) {} async openWebsite(selectedUri: string) { - await this.cipherService.updateLastLaunchedDate(this.cipherId); + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + await this.cipherService.updateLastLaunchedDate(this.cipherId, activeUserId); this.platformUtilsService.launchUri(selectedUri); } } diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 00852ff101c..2260e4a978b 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -172,7 +172,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private get selectedOrgId(): OrganizationId { return this.formGroup.getRawValue().selectedOrg || this.params.organizationId; } - private activeUserId: UserId; + private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroy$ = new Subject(); constructor( @@ -186,10 +186,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ) {} async ngOnInit() { - this.activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const onlyPersonalItems = this.params.ciphers.every((c) => c.organizationId == null); if (this.selectedOrgId === MY_VAULT_ID || onlyPersonalItems) { @@ -246,12 +242,15 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI .filter((i) => i.organizationId) .map((i) => i.id as CipherId); + const activeUserId = await firstValueFrom(this.activeUserId$); + // Move personal items to the organization if (this.personalItemsCount > 0) { await this.moveToOrganization( this.selectedOrgId, this.params.ciphers.filter((c) => c.organizationId == null), this.formGroup.controls.collections.value.map((i) => i.id as CollectionId), + activeUserId, ); } @@ -260,8 +259,8 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI // Update assigned collections for single org cipher or bulk update collections for multiple org ciphers await (isSingleOrgCipher - ? this.updateAssignedCollections(this.editableItems[0]) - : this.bulkUpdateCollections(cipherIds)); + ? this.updateAssignedCollections(this.editableItems[0], activeUserId) + : this.bulkUpdateCollections(cipherIds, activeUserId)); this.toastService.showToast({ variant: "success", @@ -426,12 +425,13 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI organizationId: OrganizationId, shareableCiphers: CipherView[], selectedCollectionIds: CollectionId[], + userId: UserId, ) { await this.cipherService.shareManyWithServer( shareableCiphers, organizationId, selectedCollectionIds, - this.activeUserId, + userId, ); this.toastService.showToast({ @@ -444,10 +444,11 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI }); } - private async bulkUpdateCollections(cipherIds: CipherId[]) { + private async bulkUpdateCollections(cipherIds: CipherId[], userId: UserId) { if (this.formGroup.controls.collections.value.length > 0) { await this.cipherService.bulkUpdateCollectionsWithServer( this.selectedOrgId, + userId, cipherIds, this.formGroup.controls.collections.value.map((i) => i.id as CollectionId), false, @@ -462,6 +463,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI ) { await this.cipherService.bulkUpdateCollectionsWithServer( this.selectedOrgId, + userId, cipherIds, [this.params.activeCollection.id as CollectionId], true, @@ -469,14 +471,14 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI } } - private async updateAssignedCollections(cipherView: CipherView) { + private async updateAssignedCollections(cipherView: CipherView, userId: UserId) { const { collections } = this.formGroup.getRawValue(); cipherView.collectionIds = collections.map((i) => i.id as CollectionId); - const cipher = await this.cipherService.encrypt(cipherView, this.activeUserId); + const cipher = await this.cipherService.encrypt(cipherView, userId); if (this.params.isSingleCipherAdmin) { await this.cipherService.saveCollectionsWithServerAdmin(cipher); } else { - await this.cipherService.saveCollectionsWithServer(cipher); + await this.cipherService.saveCollectionsWithServer(cipher, userId); } } } From 340a847bd9ca65d507a8bf05ebb29ab3e950cf88 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 14:44:09 -0500 Subject: [PATCH 07/17] Fixed merge conflicts --- apps/cli/src/vault/create.command.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 8d044d81b61..84c18ba2623 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -138,8 +138,6 @@ export class CreateCommand { return Response.notFound(); } - const activeUserId = await firstValueFrom(this.activeUserId$); - const canAccessPremium = await firstValueFrom( this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); From d6c59b346cf41dbf76001af839224038bdd05798 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 16:03:32 -0500 Subject: [PATCH 08/17] Fixed merge conflicts --- .../components/vault/add-edit.component.ts | 423 --------------- .../components/vault/current-tab.component.ts | 360 ------------- .../vault/vault-filter.component.ts | 488 ------------------ .../components/vault/vault-items.component.ts | 324 ------------ 4 files changed, 1595 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/vault/add-edit.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/current-tab.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-filter.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault/vault-items.component.ts 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 deleted file mode 100644 index 3935ba875e4..00000000000 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ /dev/null @@ -1,423 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe, Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import qrcodeParser from "qrcode-parser"; -import { firstValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; - -import { CollectionService } from "@bitwarden/admin-console/common"; -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/vault/components/add-edit.component"; -import { AuditService } from "@bitwarden/common/abstractions/audit.service"; -import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; -import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service"; -import { DialogService } from "@bitwarden/components"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { BrowserFido2UserInterfaceSession } from "../../../../autofill/fido2/services/browser-fido2-user-interface.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { PopupCloseWarningService } from "../../../../popup/services/popup-close-warning.service"; -import { Fido2UserVerificationService } from "../../../services/fido2-user-verification.service"; -import { fido2PopoutSessionData$ } from "../../utils/fido2-popout-session-data"; -import { closeAddEditVaultItemPopout, VaultPopoutType } from "../../utils/vault-popout-window"; - -@Component({ - selector: "app-vault-add-edit", - templateUrl: "add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class AddEditComponent extends BaseAddEditComponent implements OnInit { - currentUris: string[]; - showAttachments = true; - openAttachmentsInPopup: boolean; - showAutoFillOnPageLoadOptions: boolean; - - private fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - cipherService: CipherService, - folderService: FolderService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - auditService: AuditService, - accountService: AccountService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - collectionService: CollectionService, - messagingService: MessagingService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - eventCollectionService: EventCollectionService, - policyService: PolicyService, - private popupCloseWarningService: PopupCloseWarningService, - organizationService: OrganizationService, - passwordRepromptService: PasswordRepromptService, - logService: LogService, - dialogService: DialogService, - datePipe: DatePipe, - configService: ConfigService, - private fido2UserVerificationService: Fido2UserVerificationService, - cipherAuthorizationService: CipherAuthorizationService, - ) { - super( - cipherService, - folderService, - i18nService, - platformUtilsService, - auditService, - accountService, - collectionService, - messagingService, - eventCollectionService, - policyService, - logService, - passwordRepromptService, - organizationService, - dialogService, - window, - datePipe, - configService, - cipherAuthorizationService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.cipherId) { - this.cipherId = params.cipherId; - } - if (params.folderId) { - this.folderId = params.folderId; - } - if (params.collectionId) { - this.collectionId = params.collectionId; - const collection = this.writeableCollections.find((c) => c.id === params.collectionId); - if (collection != null) { - this.collectionIds = [collection.id]; - this.organizationId = collection.organizationId; - } - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - this.editMode = !params.cipherId; - - if (params.cloneMode != null) { - this.cloneMode = params.cloneMode === "true"; - } - if (params.selectedVault) { - this.organizationId = params.selectedVault; - } - - await this.load(); - - if (!this.editMode || this.cloneMode) { - // Only allow setting username if there's no existing value - if ( - params.username && - (this.cipher.login.username == null || this.cipher.login.username === "") - ) { - this.cipher.login.username = params.username; - } - - if (params.name && (this.cipher.name == null || this.cipher.name === "")) { - this.cipher.name = params.name; - } - if ( - params.uri && - this.cipher.login.uris[0] && - (this.cipher.login.uris[0].uri == null || this.cipher.login.uris[0].uri === "") - ) { - this.cipher.login.uris[0].uri = params.uri; - } - } - - this.openAttachmentsInPopup = BrowserPopupUtils.inPopup(window); - - if (this.inAddEditPopoutWindow()) { - BrowserApi.messageListener("add-edit-popout", this.handleExtensionMessage.bind(this)); - } - }); - - if (!this.editMode) { - const tabs = await BrowserApi.tabsQuery({ windowType: "normal" }); - this.currentUris = - tabs == null - ? null - : tabs.filter((tab) => tab.url != null && tab.url !== "").map((tab) => tab.url); - } - - this.setFocus(); - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.enable(); - } - } - - async load() { - await super.load(); - this.showAutoFillOnPageLoadOptions = - this.cipher.type === CipherType.Login && - (await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)); - } - - async submit(): Promise { - const fido2SessionData = await firstValueFrom(this.fido2PopoutSessionData$); - const { isFido2Session, sessionId, userVerification } = fido2SessionData; - const inFido2PopoutWindow = BrowserPopupUtils.inPopout(window) && isFido2Session; - - // normalize card expiry year on save - if (this.cipher.type === this.cipherType.Card) { - this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); - } - - // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. - // PM-4577 - https://github.com/bitwarden/clients/pull/8746 - if ( - inFido2PopoutWindow && - !(await this.handleFido2UserVerification(sessionId, userVerification)) - ) { - return false; - } - - const success = await super.submit(); - if (!success) { - return false; - } - - if (BrowserPopupUtils.inPopout(window)) { - this.popupCloseWarningService.disable(); - } - - if (inFido2PopoutWindow) { - BrowserFido2UserInterfaceSession.confirmNewCredentialResponse( - sessionId, - this.cipher.id, - userVerification, - ); - return true; - } - - if (this.inAddEditPopoutWindow()) { - this.messagingService.send("addEditCipherSubmitted"); - await closeAddEditVaultItemPopout(1000); - return true; - } - - if (this.cloneMode) { - // 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.router.navigate(["/tabs/vault"]); - } else { - this.location.back(); - } - return true; - } - - attachments() { - super.attachments(); - - if (this.openAttachmentsInPopup) { - const destinationUrl = this.router - .createUrlTree(["/attachments"], { queryParams: { cipherId: this.cipher.id } }) - .toString(); - const currentBaseUrl = window.location.href.replace(this.router.url, ""); - // 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 - BrowserPopupUtils.openCurrentPagePopout(window, currentBaseUrl + destinationUrl); - } else { - // 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.router.navigate(["/attachments"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - editCollections() { - super.editCollections(); - if (this.cipher.organizationId != null) { - // 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.router.navigate(["/collections"], { queryParams: { cipherId: this.cipher.id } }); - } - } - - async cancel() { - super.cancel(); - - const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); - if (BrowserPopupUtils.inPopout(window) && sessionData.isFido2Session) { - this.popupCloseWarningService.disable(); - BrowserFido2UserInterfaceSession.abortPopout(sessionData.sessionId); - return; - } - - if (this.inAddEditPopoutWindow()) { - // 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 - closeAddEditVaultItemPopout(); - return; - } - - this.location.back(); - } - - async generateUsername(): Promise { - const confirmed = await super.generateUsername(); - if (confirmed) { - const activeUserId = await firstValueFrom(this.activeUserId$); - await this.saveCipherState(activeUserId); - // 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.router.navigate(["generator"], { queryParams: { type: "username" } }); - } - return confirmed; - } - - async generatePassword(): Promise { - const confirmed = await super.generatePassword(); - if (confirmed) { - const activeUserId = await firstValueFrom(this.activeUserId$); - await this.saveCipherState(activeUserId); - // 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.router.navigate(["generator"], { queryParams: { type: "password" } }); - } - return confirmed; - } - - async delete(): Promise { - const confirmed = await super.delete(); - if (confirmed) { - // 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.router.navigate(["/tabs/vault"]); - } - return confirmed; - } - - toggleUriInput(uri: LoginUriView) { - const u = uri as any; - u.showCurrentUris = !u.showCurrentUris; - } - - allowOwnershipOptions(): boolean { - return ( - (!this.editMode || this.cloneMode) && - this.ownershipOptions && - (this.ownershipOptions.length > 1 || !this.allowPersonal) - ); - } - - private saveCipherState(userId: UserId) { - return this.cipherService.setAddEditCipherInfo( - { - cipher: this.cipher, - collectionIds: - this.collections == null - ? [] - : this.collections.filter((c) => (c as any).checked).map((c) => c.id), - }, - userId, - ); - } - - private setFocus() { - window.setTimeout(() => { - if (this.editMode) { - return; - } - - if (this.cipher.name != null && this.cipher.name !== "") { - document.getElementById("loginUsername").focus(); - } else { - document.getElementById("name").focus(); - } - }, 200); - } - - repromptChanged() { - super.repromptChanged(); - - if (!this.showAutoFillOnPageLoadOptions) { - return; - } - - if (this.reprompt) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("passwordRepromptDisabledAutofillOnPageLoad"), - ); - return; - } - - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillOnPageLoadSetToDefault"), - ); - } - - private inAddEditPopoutWindow() { - return BrowserPopupUtils.inSingleActionPopout(window, VaultPopoutType.addEditVaultItem); - } - - async captureTOTPFromTab() { - try { - const screenshot = await BrowserApi.captureVisibleTab(); - const data = await qrcodeParser(screenshot); - const url = new URL(data.toString()); - if (url.protocol == "otpauth:" && url.searchParams.has("secret")) { - this.cipher.login.totp = data.toString(); - this.platformUtilsService.showToast( - "success", - null, - this.i18nService.t("totpCaptureSuccess"), - ); - } - } catch (e) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccurred"), - this.i18nService.t("totpCaptureError"), - ); - } - } - - private handleExtensionMessage(message: { [key: string]: any; command: string }) { - if (message.command === "inlineAutofillMenuRefreshAddEditCipher") { - this.load().catch((error) => this.logService.error(error)); - } - } - - // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. - // Be sure to make the same changes to add-edit-v2.component.ts if applicable - private async handleFido2UserVerification( - sessionId: string, - userVerification: boolean, - ): Promise { - // We are bypassing user verification pending approval for production. - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts b/apps/browser/src/vault/popup/components/vault/current-tab.component.ts deleted file mode 100644 index f0ffe2ff88d..00000000000 --- a/apps/browser/src/vault/popup/components/vault/current-tab.component.ts +++ /dev/null @@ -1,360 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { Subject, firstValueFrom, from, Subscription } from "rxjs"; -import { debounceTime, map, switchMap, takeUntil } from "rxjs/operators"; - -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AutofillOverlayVisibility } from "@bitwarden/common/autofill/constants"; -import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { PasswordRepromptService } from "@bitwarden/vault"; - -import { AutofillService } from "../../../../autofill/services/abstractions/autofill.service"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const BroadcasterSubscriptionId = "CurrentTabComponent"; - -@Component({ - selector: "app-current-tab", - templateUrl: "current-tab.component.html", -}) -export class CurrentTabComponent implements OnInit, OnDestroy { - pageDetails: any[] = []; - tab: chrome.tabs.Tab; - cardCiphers: CipherView[]; - identityCiphers: CipherView[]; - loginCiphers: CipherView[]; - url: string; - hostname: string; - searchText: string; - inSidebar = false; - searchTypeSearch = false; - loaded = false; - isLoading = false; - showOrganizations = false; - showHowToAutofill = false; - autofillCalloutText: string; - protected search$ = new Subject(); - private destroy$ = new Subject(); - private collectPageDetailsSubscription: Subscription; - - private totpCode: string; - private totpTimeout: number; - private loadedTimeout: number; - private searchTimeout: number; - - constructor( - private platformUtilsService: PlatformUtilsService, - private cipherService: CipherService, - private autofillService: AutofillService, - private i18nService: I18nService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private syncService: SyncService, - private searchService: SearchService, - private autofillSettingsService: AutofillSettingsServiceAbstraction, - private passwordRepromptService: PasswordRepromptService, - private organizationService: OrganizationService, - private vaultFilterService: VaultFilterService, - private vaultSettingsService: VaultSettingsService, - private accountService: AccountService, - ) {} - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.inSidebar = BrowserPopupUtils.inSidebar(window); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (this.isLoading) { - window.setTimeout(() => { - // 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.load(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - if (!this.syncService.syncInProgress) { - await this.load(); - await this.setCallout(); - } else { - this.loadedTimeout = window.setTimeout(async () => { - if (!this.isLoading) { - await this.load(); - await this.setCallout(); - } - }, 5000); - } - - this.search$ - .pipe( - debounceTime(500), - switchMap(() => { - return from(this.searchVault()); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - const autofillOnPageLoadOrgPolicy = await firstValueFrom( - this.autofillSettingsService.activateAutofillOnPageLoadFromPolicy$, - ); - const autofillOnPageLoadPolicyToastHasDisplayed = await firstValueFrom( - this.autofillSettingsService.autofillOnPageLoadPolicyToastHasDisplayed$, - ); - - // If the org "autofill on page load" policy is set, set the user setting to match it - // @TODO override user setting instead of overwriting - if (autofillOnPageLoadOrgPolicy === true) { - await this.autofillSettingsService.setAutofillOnPageLoad(true); - - if (!autofillOnPageLoadPolicyToastHasDisplayed) { - this.platformUtilsService.showToast( - "info", - null, - this.i18nService.t("autofillPageLoadPolicyActivated"), - ); - - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(true); - } - } - - // If the org policy is ever disabled after being enabled, reset the toast notification - if (!autofillOnPageLoadOrgPolicy && autofillOnPageLoadPolicyToastHasDisplayed) { - await this.autofillSettingsService.setAutofillOnPageLoadPolicyToastHasDisplayed(false); - } - } - - ngOnDestroy() { - window.clearTimeout(this.loadedTimeout); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - - this.destroy$.next(); - this.destroy$.complete(); - } - - async refresh() { - await this.load(); - } - - addCipher() { - // 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.router.navigate(["/add-cipher"], { - queryParams: { - name: this.hostname, - uri: this.url, - selectedVault: this.vaultFilterService.getVaultFilter().selectedOrganizationId, - }, - }); - } - - viewCipher(cipher: CipherView) { - // 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.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - - async fillCipher(cipher: CipherView, closePopupDelay?: number) { - if ( - cipher.reprompt !== CipherRepromptType.None && - !(await this.passwordRepromptService.showPasswordPrompt()) - ) { - return; - } - - this.totpCode = null; - if (this.totpTimeout != null) { - window.clearTimeout(this.totpTimeout); - } - - if (this.pageDetails == null || this.pageDetails.length === 0) { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - return; - } - - try { - this.totpCode = await this.autofillService.doAutoFill({ - tab: this.tab, - cipher: cipher, - pageDetails: this.pageDetails, - doc: window.document, - fillNewPassword: true, - allowTotpAutofill: true, - }); - if (this.totpCode != null) { - this.platformUtilsService.copyToClipboard(this.totpCode, { window: window }); - } - if (BrowserPopupUtils.inPopup(window)) { - if (!closePopupDelay) { - if (this.platformUtilsService.isFirefox() || this.platformUtilsService.isSafari()) { - BrowserApi.closePopup(window); - } else { - // Slight delay to fix bug in Chromium browsers where popup closes without copying totp to clipboard - setTimeout(() => BrowserApi.closePopup(window), 50); - } - } else { - setTimeout(() => BrowserApi.closePopup(window), closePopupDelay); - } - } - } catch { - this.ngZone.run(() => { - this.platformUtilsService.showToast("error", null, this.i18nService.t("autofillError")); - this.changeDetectorRef.detectChanges(); - }); - } - } - - async searchVault() { - if (!(await this.searchService.isSearchable(this.searchText))) { - return; - } - - await this.router.navigate(["/tabs/vault"], { queryParams: { searchText: this.searchText } }); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - protected async load() { - this.isLoading = false; - this.tab = await BrowserApi.getTabFromCurrentWindow(); - - if (this.tab != null) { - this.url = this.tab.url; - } else { - this.loginCiphers = []; - this.isLoading = this.loaded = true; - return; - } - - this.pageDetails = []; - this.collectPageDetailsSubscription?.unsubscribe(); - this.collectPageDetailsSubscription = this.autofillService - .collectPageDetailsFromTab$(this.tab) - .pipe(takeUntil(this.destroy$)) - .subscribe((pageDetails) => (this.pageDetails = pageDetails)); - - this.hostname = Utils.getHostname(this.url); - const otherTypes: CipherType[] = []; - const dontShowCards = !(await firstValueFrom(this.vaultSettingsService.showCardsCurrentTab$)); - const dontShowIdentities = !(await firstValueFrom( - this.vaultSettingsService.showIdentitiesCurrentTab$, - )); - this.showOrganizations = await this.organizationService.hasOrganizations(); - if (!dontShowCards) { - otherTypes.push(CipherType.Card); - } - if (!dontShowIdentities) { - otherTypes.push(CipherType.Identity); - } - - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - const ciphers = await this.cipherService.getAllDecryptedForUrl( - this.url, - activeUserId, - otherTypes.length > 0 ? otherTypes : null, - ); - - this.loginCiphers = []; - this.cardCiphers = []; - this.identityCiphers = []; - - ciphers.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - switch (c.type) { - case CipherType.Login: - this.loginCiphers.push(c); - break; - case CipherType.Card: - this.cardCiphers.push(c); - break; - case CipherType.Identity: - this.identityCiphers.push(c); - break; - default: - break; - } - } - }); - - if (this.loginCiphers.length) { - this.loginCiphers = this.loginCiphers.sort((a, b) => - this.cipherService.sortCiphersByLastUsedThenName(a, b), - ); - } - - this.isLoading = this.loaded = true; - } - - async goToSettings() { - // 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.router.navigate(["autofill"]); - } - - async dismissCallout() { - await this.autofillSettingsService.setAutofillOnPageLoadCalloutIsDismissed(true); - this.showHowToAutofill = false; - } - - private async setCallout() { - const inlineMenuVisibilityIsOff = - (await firstValueFrom(this.autofillSettingsService.inlineMenuVisibility$)) === - AutofillOverlayVisibility.Off; - - this.showHowToAutofill = - this.loginCiphers.length > 0 && - inlineMenuVisibilityIsOff && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoad$)) && - !(await firstValueFrom(this.autofillSettingsService.autofillOnPageLoadCalloutIsDismissed$)); - - if (this.showHowToAutofill) { - const autofillCommand = await this.platformUtilsService.getAutofillKeyboardShortcut(); - await this.setAutofillCalloutText(autofillCommand); - } - } - - private setAutofillCalloutText(command: string) { - if (command) { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithCommand", command); - } else { - this.autofillCalloutText = this.i18nService.t("autofillSelectInfoWithoutCommand"); - } - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts b/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts deleted file mode 100644 index 00fb023033e..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-filter.component.ts +++ /dev/null @@ -1,488 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { BehaviorSubject, Subject, firstValueFrom, from } from "rxjs"; -import { first, map, switchMap, takeUntil } from "rxjs/operators"; - -import { CollectionView } from "@bitwarden/admin-console/common"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SyncService } from "@bitwarden/common/platform/sync"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserGroupingsComponentState } from "../../../../models/browserGroupingsComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultComponent"; - -@Component({ - selector: "app-vault-filter", - templateUrl: "vault-filter.component.html", -}) -export class VaultFilterComponent implements OnInit, OnDestroy { - get showNoFolderCiphers(): boolean { - return ( - this.noFolderCiphers != null && - this.noFolderCiphers.length < this.noFolderListSize && - this.collections.length === 0 - ); - } - - get folderCount(): number { - return this.nestedFolders.length - (this.showNoFolderCiphers ? 0 : 1); - } - folders: FolderView[]; - nestedFolders: TreeNode[]; - collections: CollectionView[]; - nestedCollections: TreeNode[]; - loaded = false; - cipherType = CipherType; - ciphers: CipherView[]; - favoriteCiphers: CipherView[]; - noFolderCiphers: CipherView[]; - folderCounts = new Map(); - collectionCounts = new Map(); - typeCounts = new Map(); - state: BrowserGroupingsComponentState; - showLeftHeader = true; - searchPending = false; - searchTypeSearch = false; - deletedCount = 0; - vaultFilter: VaultFilter; - selectedOrganization: string = null; - showCollections = true; - - isSshKeysEnabled = false; - - private loadedTimeout: number; - private selectedTimeout: number; - private preventSelected = false; - private noFolderListSize = 100; - private searchTimeout: any = null; - private hasSearched = false; - private hasLoadedAllCiphers = false; - private allCiphers: CipherView[] = null; - private destroy$ = new Subject(); - private _searchText$ = new BehaviorSubject(""); - private isSearchable: boolean = false; - - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - - get searchText() { - return this._searchText$.value; - } - set searchText(value: string) { - this._searchText$.next(value); - } - - constructor( - private i18nService: I18nService, - private cipherService: CipherService, - private router: Router, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private route: ActivatedRoute, - private syncService: SyncService, - private platformUtilsService: PlatformUtilsService, - private searchService: SearchService, - private location: Location, - private vaultFilterService: VaultFilterService, - private vaultBrowserStateService: VaultBrowserStateService, - private configService: ConfigService, - private accountService: AccountService, - ) { - this.noFolderListSize = 100; - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showLeftHeader = !( - BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() - ); - await this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - window.setTimeout(() => { - // 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.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - - const restoredScopeState = await this.restoreState(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } else if (params.searchText) { - this.searchText = params.searchText; - this.location.replaceState("vault"); - } - - if (!this.syncService.syncInProgress) { - // 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.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - // 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.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - }); - - this._searchText$ - .pipe( - switchMap((searchText) => from(this.searchService.isSearchable(searchText))), - takeUntil(this.destroy$), - ) - .subscribe((isSearchable) => { - this.isSearchable = isSearchable; - }); - - this.isSshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem); - } - - ngOnDestroy() { - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - this.destroy$.next(); - this.destroy$.complete(); - } - - async load() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - await this.loadCiphers(); - - if (this.showNoFolderCiphers && this.nestedFolders.length > 0) { - // Remove "No Folder" from folder listing - this.nestedFolders = this.nestedFolders.slice(0, this.nestedFolders.length - 1); - } - - this.loaded = true; - } - - async loadCiphers() { - const activeUserId = await firstValueFrom(this.activeUserId$); - this.allCiphers = await this.cipherService.getAllDecrypted(activeUserId); - if (!this.hasLoadedAllCiphers) { - this.hasLoadedAllCiphers = !(await this.searchService.isSearchable(this.searchText)); - } - await this.search(null); - this.getCounts(); - } - - async loadCollections() { - const allCollections = await this.vaultFilterService.buildCollections( - this.selectedOrganization, - ); - this.collections = allCollections.fullList; - this.nestedCollections = allCollections.nestedList; - } - - async loadFolders() { - const allFolders = await firstValueFrom( - this.vaultFilterService.buildNestedFolders(this.selectedOrganization), - ); - this.folders = allFolders.fullList; - this.nestedFolders = allFolders.nestedList; - } - - async search(timeout: number = null) { - this.searchPending = false; - if (this.searchTimeout != null) { - clearTimeout(this.searchTimeout); - } - const filterDeleted = (c: CipherView) => !c.isDeleted; - if (timeout == null) { - this.hasSearched = this.isSearchable; - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - return; - } - this.searchPending = true; - this.searchTimeout = setTimeout(async () => { - this.hasSearched = this.isSearchable; - if (!this.hasLoadedAllCiphers && !this.hasSearched) { - await this.loadCiphers(); - } else { - this.ciphers = await this.searchService.searchCiphers( - this.searchText, - filterDeleted, - this.allCiphers, - ); - } - this.ciphers = this.ciphers.filter( - (c) => !this.vaultFilterService.filterCipherForSelectedVault(c), - ); - this.searchPending = false; - }, timeout); - } - - async selectType(type: CipherType) { - // 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.router.navigate(["/ciphers"], { queryParams: { type: type } }); - } - - async selectFolder(folder: FolderView) { - // 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.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id || "none" } }); - } - - async selectCollection(collection: CollectionView) { - // 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.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async selectTrash() { - // 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.router.navigate(["/ciphers"], { queryParams: { deleted: true } }); - } - - async selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - // 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.router.navigate(["/view-cipher"], { queryParams: { cipherId: cipher.id } }); - } - this.preventSelected = false; - }, 200); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - const activeUserId = await firstValueFrom(this.activeUserId$); - await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - async addCipher() { - // 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.router.navigate(["/add-cipher"], { - queryParams: { selectedVault: this.vaultFilter.selectedOrganizationId }, - }); - } - - async vaultFilterChanged() { - if (this.showSearching) { - await this.search(); - } - this.updateSelectedOrg(); - await this.loadCollectionsAndFolders(); - this.getCounts(); - } - - updateSelectedOrg() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - if (this.vaultFilter.selectedOrganizationId != null) { - this.selectedOrganization = this.vaultFilter.selectedOrganizationId; - } else { - this.selectedOrganization = null; - } - } - - getCounts() { - let favoriteCiphers: CipherView[] = null; - let noFolderCiphers: CipherView[] = null; - const folderCounts = new Map(); - const collectionCounts = new Map(); - const typeCounts = new Map(); - - this.deletedCount = this.allCiphers.filter( - (c) => c.isDeleted && !this.vaultFilterService.filterCipherForSelectedVault(c), - ).length; - - this.ciphers?.forEach((c) => { - if (!this.vaultFilterService.filterCipherForSelectedVault(c)) { - if (c.isDeleted) { - return; - } - if (c.favorite) { - if (favoriteCiphers == null) { - favoriteCiphers = []; - } - favoriteCiphers.push(c); - } - - if (c.folderId == null) { - if (noFolderCiphers == null) { - noFolderCiphers = []; - } - noFolderCiphers.push(c); - } - - if (typeCounts.has(c.type)) { - typeCounts.set(c.type, typeCounts.get(c.type) + 1); - } else { - typeCounts.set(c.type, 1); - } - - if (folderCounts.has(c.folderId)) { - folderCounts.set(c.folderId, folderCounts.get(c.folderId) + 1); - } else { - folderCounts.set(c.folderId, 1); - } - - if (c.collectionIds != null) { - c.collectionIds.forEach((colId) => { - if (collectionCounts.has(colId)) { - collectionCounts.set(colId, collectionCounts.get(colId) + 1); - } else { - collectionCounts.set(colId, 1); - } - }); - } - } - }); - - this.favoriteCiphers = favoriteCiphers; - this.noFolderCiphers = noFolderCiphers; - this.typeCounts = typeCounts; - this.folderCounts = folderCounts; - this.collectionCounts = collectionCounts; - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.isSearchable); - } - - closeOnEsc(e: KeyboardEvent) { - // If input not empty, use browser default behavior of clearing input instead - if (e.key === "Escape" && (this.searchText == null || this.searchText === "")) { - BrowserApi.closePopup(window); - } - } - - private async loadCollectionsAndFolders() { - this.showCollections = !this.vaultFilter.myVaultOnly; - await this.loadFolders(); - await this.loadCollections(); - } - - private async saveState() { - this.state = Object.assign(new BrowserGroupingsComponentState(), { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - favoriteCiphers: this.favoriteCiphers, - noFolderCiphers: this.noFolderCiphers, - ciphers: this.ciphers, - collectionCounts: this.collectionCounts, - folderCounts: this.folderCounts, - typeCounts: this.typeCounts, - folders: this.folders, - collections: this.collections, - deletedCount: this.deletedCount, - }); - await this.vaultBrowserStateService.setBrowserGroupingsComponentState(this.state); - } - - private async restoreState(): Promise { - this.state = await this.vaultBrowserStateService.getBrowserGroupingsComponentState(); - if (this.state == null) { - return false; - } - - if (this.state.favoriteCiphers != null) { - this.favoriteCiphers = this.state.favoriteCiphers; - } - if (this.state.noFolderCiphers != null) { - this.noFolderCiphers = this.state.noFolderCiphers; - } - if (this.state.ciphers != null) { - this.ciphers = this.state.ciphers; - } - if (this.state.collectionCounts != null) { - this.collectionCounts = this.state.collectionCounts; - } - if (this.state.folderCounts != null) { - this.folderCounts = this.state.folderCounts; - } - if (this.state.typeCounts != null) { - this.typeCounts = this.state.typeCounts; - } - if (this.state.folders != null) { - this.folders = this.state.folders; - } - if (this.state.collections != null) { - this.collections = this.state.collections; - } - if (this.state.deletedCount != null) { - this.deletedCount = this.state.deletedCount; - } - - return true; - } -} diff --git a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts b/apps/browser/src/vault/popup/components/vault/vault-items.component.ts deleted file mode 100644 index ed1336b863b..00000000000 --- a/apps/browser/src/vault/popup/components/vault/vault-items.component.ts +++ /dev/null @@ -1,324 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { first, map } from "rxjs/operators"; - -import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { VaultItemsComponent as BaseVaultItemsComponent } from "@bitwarden/angular/vault/components/vault-items.component"; -import { VaultFilter } from "@bitwarden/angular/vault/vault-filter/models/vault-filter.model"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType } from "@bitwarden/common/vault/enums"; -import { TreeNode } from "@bitwarden/common/vault/models/domain/tree-node"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; - -import { BrowserComponentState } from "../../../../models/browserComponentState"; -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import BrowserPopupUtils from "../../../../platform/popup/browser-popup-utils"; -import { VaultBrowserStateService } from "../../../services/vault-browser-state.service"; -import { VaultFilterService } from "../../../services/vault-filter.service"; - -const ComponentId = "VaultItemsComponent"; - -@Component({ - selector: "app-vault-items", - templateUrl: "vault-items.component.html", -}) -export class VaultItemsComponent extends BaseVaultItemsComponent implements OnInit, OnDestroy { - groupingTitle: string; - state: BrowserComponentState; - folderId: string = null; - collectionId: string = null; - type: CipherType = null; - nestedFolders: TreeNode[]; - nestedCollections: TreeNode[]; - searchTypeSearch = false; - showOrganizations = false; - vaultFilter: VaultFilter; - deleted = true; - noneFolder = false; - showVaultFilter = false; - - private selectedTimeout: number; - private preventSelected = false; - private applySavedState = true; - private scrollingContainer = "cdk-virtual-scroll-viewport"; - - constructor( - searchService: SearchService, - private organizationService: OrganizationService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - private ngZone: NgZone, - private broadcasterService: BroadcasterService, - private changeDetectorRef: ChangeDetectorRef, - private stateService: VaultBrowserStateService, - private i18nService: I18nService, - private collectionService: CollectionService, - private platformUtilsService: PlatformUtilsService, - cipherService: CipherService, - private vaultFilterService: VaultFilterService, - accountService: AccountService, - ) { - super(searchService, cipherService, accountService); - this.applySavedState = - (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith("/ciphers"); - } - - async ngOnInit() { - this.searchTypeSearch = !this.platformUtilsService.isSafari(); - this.showOrganizations = await this.organizationService.hasOrganizations(); - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (this.applySavedState) { - this.state = await this.stateService.getBrowserVaultItemsComponentState(); - if (this.state?.searchText) { - this.searchText = this.state.searchText; - } - } - - if (params.deleted) { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("trash"); - this.searchPlaceholder = this.i18nService.t("searchTrash"); - await this.load(this.buildFilter(), true); - } else if (params.type) { - this.showVaultFilter = true; - this.searchPlaceholder = this.i18nService.t("searchType"); - this.type = parseInt(params.type, null); - switch (this.type) { - case CipherType.Login: - this.groupingTitle = this.i18nService.t("logins"); - break; - case CipherType.Card: - this.groupingTitle = this.i18nService.t("cards"); - break; - case CipherType.Identity: - this.groupingTitle = this.i18nService.t("identities"); - break; - case CipherType.SecureNote: - this.groupingTitle = this.i18nService.t("secureNotes"); - break; - case CipherType.SshKey: - this.groupingTitle = this.i18nService.t("sshKeys"); - break; - default: - break; - } - await this.load(this.buildFilter()); - } else if (params.folderId) { - this.showVaultFilter = true; - this.folderId = params.folderId === "none" ? null : params.folderId; - this.searchPlaceholder = this.i18nService.t("searchFolder"); - if (this.folderId != null) { - this.showOrganizations = false; - const folderNode = await this.vaultFilterService.getFolderNested(this.folderId); - if (folderNode != null && folderNode.node != null) { - this.groupingTitle = folderNode.node.name; - this.nestedFolders = - folderNode.children != null && folderNode.children.length > 0 - ? folderNode.children - : null; - } - } else { - this.noneFolder = true; - this.groupingTitle = this.i18nService.t("noneFolder"); - } - await this.load(this.buildFilter()); - } else if (params.collectionId) { - this.showVaultFilter = false; - this.collectionId = params.collectionId; - this.searchPlaceholder = this.i18nService.t("searchCollection"); - const collectionNode = await this.collectionService.getNested(this.collectionId); - if (collectionNode != null && collectionNode.node != null) { - this.groupingTitle = collectionNode.node.name; - this.nestedCollections = - collectionNode.children != null && collectionNode.children.length > 0 - ? collectionNode.children - : null; - } - await this.load( - (c) => c.collectionIds != null && c.collectionIds.indexOf(this.collectionId) > -1, - ); - } else { - this.showVaultFilter = true; - this.groupingTitle = this.i18nService.t("allItems"); - await this.load(this.buildFilter()); - } - - if (this.applySavedState && this.state != null) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state.scrollY, { - delay: 0, - containerSelector: this.scrollingContainer, - }); - } - await this.stateService.setBrowserVaultItemsComponentState(null); - }); - - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - window.setTimeout(() => { - // 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.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // 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.saveState(); - this.broadcasterService.unsubscribe(ComponentId); - } - - selectCipher(cipher: CipherView) { - this.selectedTimeout = window.setTimeout(() => { - if (!this.preventSelected) { - super.selectCipher(cipher); - // 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.router.navigate(["/view-cipher"], { - queryParams: { cipherId: cipher.id, collectionId: this.collectionId }, - }); - } - this.preventSelected = false; - }, 200); - } - - selectFolder(folder: FolderView) { - if (folder.id != null) { - // 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.router.navigate(["/ciphers"], { queryParams: { folderId: folder.id } }); - } - } - - selectCollection(collection: CollectionView) { - // 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.router.navigate(["/ciphers"], { queryParams: { collectionId: collection.id } }); - } - - async launchCipher(cipher: CipherView) { - if (cipher.type !== CipherType.Login || !cipher.login.canLaunch) { - return; - } - - if (this.selectedTimeout != null) { - window.clearTimeout(this.selectedTimeout); - } - this.preventSelected = true; - - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); - // 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 - BrowserApi.createNewTab(cipher.login.launchUri); - if (BrowserPopupUtils.inPopup(window)) { - BrowserApi.closePopup(window); - } - } - - addCipher() { - if (this.deleted) { - return false; - } - super.addCipher(); - // 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.router.navigate(["/add-cipher"], { - queryParams: { - folderId: this.folderId, - type: this.type, - collectionId: this.collectionId, - selectedVault: this.vaultFilter.selectedOrganizationId, - }, - }); - } - - back() { - (window as any).routeDirection = "b"; - this.location.back(); - } - - showGroupings() { - return ( - !this.isSearching() && - ((this.nestedFolders && this.nestedFolders.length) || - (this.nestedCollections && this.nestedCollections.length)) - ); - } - - async changeVaultSelection() { - this.vaultFilter = this.vaultFilterService.getVaultFilter(); - await this.load(this.buildFilter(), this.deleted); - } - - private buildFilter(): (cipher: CipherView) => boolean { - return (cipher) => { - let cipherPassesFilter = true; - if (this.deleted && cipherPassesFilter) { - cipherPassesFilter = cipher.isDeleted; - } - if (this.type != null && cipherPassesFilter) { - cipherPassesFilter = cipher.type === this.type; - } - if (this.folderId != null && this.folderId != "none" && cipherPassesFilter) { - cipherPassesFilter = cipher.folderId === this.folderId; - } - if (this.noneFolder) { - cipherPassesFilter = cipher.folderId == null; - } - if (this.collectionId != null && cipherPassesFilter) { - cipherPassesFilter = - cipher.collectionIds != null && cipher.collectionIds.indexOf(this.collectionId) > -1; - } - if (this.vaultFilter.selectedOrganizationId != null && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === this.vaultFilter.selectedOrganizationId; - } - if (this.vaultFilter.myVaultOnly && cipherPassesFilter) { - cipherPassesFilter = cipher.organizationId === null; - } - return cipherPassesFilter; - }; - } - - private async saveState() { - this.state = { - scrollY: BrowserPopupUtils.getContentScrollY(window, this.scrollingContainer), - searchText: this.searchText, - }; - await this.stateService.setBrowserVaultItemsComponentState(this.state); - } -} From bd1dd188b4085ba8309c998db321e23b8e5128d8 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 17:03:46 -0500 Subject: [PATCH 09/17] removed duplicate reference fixed conflict --- .../vault-list-items-container.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 1763d336e9d..c3795ea8f3b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -16,7 +16,7 @@ import { ViewChild, } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, Observable, firstValueFrom, map } from "rxjs"; +import { firstValueFrom, Observable, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; From 27a13db85609c6aa53c5d80c1003f912bd2d85fa Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 17:17:51 -0500 Subject: [PATCH 10/17] Fixed test --- .../services/fido2/fido2-authenticator.service.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 226f4c2cfe9..3ea86a1f504 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -3,7 +3,8 @@ import { TextEncoder } from "util"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; -import { Account, AccountService } from "../../../auth/abstractions/account.service"; +import { mockAccountServiceWith } from "../../../../spec"; +import { Account } from "../../../auth/abstractions/account.service"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; @@ -46,7 +47,6 @@ describe("FidoAuthenticatorService", () => { let userInterface!: MockProxy>; let userInterfaceSession!: MockProxy; let syncService!: MockProxy; - let accountService!: MockProxy; let authenticator!: Fido2AuthenticatorService; let windowReference!: ParentWindowReference; @@ -58,7 +58,7 @@ describe("FidoAuthenticatorService", () => { syncService = mock({ activeUserLastSync$: () => of(new Date()), }); - accountService = mock(); + const accountService = mockAccountServiceWith("testId" as UserId); authenticator = new Fido2AuthenticatorService( cipherService, userInterface, From 423fcb41bbc51cf0d56995079c83c8123f7a2edd Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 17:25:17 -0500 Subject: [PATCH 11/17] Fixed test --- .../components/attachments/cipher-attachments.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 1ee9a985f5a..ce12ca95e1e 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -110,7 +110,7 @@ describe("CipherAttachmentsComponent", () => { it("fetches cipherView using `cipherId`", async () => { await component.ngOnInit(); - expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333"); + expect(cipherServiceGet).toHaveBeenCalledWith("5555-444-3333", mockUserId); expect(component.cipher).toEqual(cipherView); }); From a911da14e54677408f5d7730c39155e7aaf8c3f7 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 17:44:41 -0500 Subject: [PATCH 12/17] Fixed test --- .../delete-attachment.component.spec.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts index 8e0d4f7a665..4442fa6e75d 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.spec.ts @@ -2,12 +2,16 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { mock } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { DialogService, ToastService } from "@bitwarden/components"; +import { mockAccountServiceWith } from "../../../../../../common/spec"; + import { DeleteAttachmentComponent } from "./delete-attachment.component"; describe("DeleteAttachmentComponent", () => { @@ -42,6 +46,7 @@ describe("DeleteAttachmentComponent", () => { }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: LogService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountServiceWith("UserId" as UserId) }, ], }) .overrideProvider(DialogService, { @@ -90,7 +95,11 @@ describe("DeleteAttachmentComponent", () => { }); // Called with cipher id and attachment id - expect(deleteAttachmentWithServer).toHaveBeenCalledWith("5555-444-3333", "222-3333-4444"); + expect(deleteAttachmentWithServer).toHaveBeenCalledWith( + "5555-444-3333", + "222-3333-4444", + "UserId", + ); }); it("shows toast message on successful deletion", async () => { From 41db3db877a4f897e4c41066a27d94b2dfaa80e9 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Mon, 13 Jan 2025 18:38:03 -0500 Subject: [PATCH 13/17] Fixed desturcturing issue on failed to decrypt ciphers cipher service --- libs/common/src/vault/services/cipher.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 448e852995c..ed01ab9c569 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -152,7 +152,7 @@ export class CipherService implements CipherServiceAbstraction { */ failedToDecryptCiphers$(userId: UserId): Observable { return this.failedToDecryptCiphersState(userId).state$.pipe( - filter(([ciphers]) => ciphers != null), + filter((ciphers) => ciphers != null), switchMap((ciphers) => merge(this.forceCipherViews$, of(ciphers))), shareReplay({ bufferSize: 1, refCount: true }), ); From be3bb91781b58bb93fb5345f6c61702a5c5c92ba Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Thu, 16 Jan 2025 18:00:44 -0500 Subject: [PATCH 14/17] Updated abstraction to use method syntax --- .../src/vault/abstractions/cipher.service.ts | 146 +++++++++--------- 1 file changed, 77 insertions(+), 69 deletions(-) diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index 966b92898ee..fc4f959ec57 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -19,70 +19,70 @@ import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; export abstract class CipherService implements UserKeyRotationDataProvider { - cipherViews$: (userId: UserId) => Observable; - ciphers$: (userId: UserId) => Observable>; - localData$: (userId: UserId) => Observable>; + abstract cipherViews$(userId: UserId): Observable; + abstract ciphers$(userId: UserId): Observable>; + abstract localData$(userId: UserId): Observable>; /** * An observable monitoring the add/edit cipher info saved to memory. */ - addEditCipherInfo$: (userId: UserId) => Observable; + abstract addEditCipherInfo$(userId: UserId): Observable; /** * Observable that emits an array of cipherViews that failed to decrypt. Does not emit until decryption has completed. * * An empty array indicates that all ciphers were successfully decrypted. */ - failedToDecryptCiphers$: (userId: UserId) => Observable; - clearCache: (userId: UserId) => Promise; - encrypt: ( + abstract failedToDecryptCiphers$(userId: UserId): Observable; + abstract clearCache(userId: UserId): Promise; + abstract encrypt( model: CipherView, userId: UserId, keyForEncryption?: SymmetricCryptoKey, keyForCipherKeyDecryption?: SymmetricCryptoKey, originalCipher?: Cipher, - ) => Promise; - encryptFields: (fieldsModel: FieldView[], key: SymmetricCryptoKey) => Promise; - encryptField: (fieldModel: FieldView, key: SymmetricCryptoKey) => Promise; - get: (id: string, userId: UserId) => Promise; - getAll: (userId: UserId) => Promise; - getAllDecrypted: (userId: UserId) => Promise; - getAllDecryptedForGrouping: ( + ): Promise; + abstract encryptFields(fieldsModel: FieldView[], key: SymmetricCryptoKey): Promise; + abstract encryptField(fieldModel: FieldView, key: SymmetricCryptoKey): Promise; + abstract get(id: string, userId: UserId): Promise; + abstract getAll(userId: UserId): Promise; + abstract getAllDecrypted(userId: UserId): Promise; + abstract getAllDecryptedForGrouping( groupingId: string, userId: UserId, folder?: boolean, - ) => Promise; - getAllDecryptedForUrl: ( + ): Promise; + abstract getAllDecryptedForUrl( url: string, userId: UserId, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchStrategySetting, - ) => Promise; - filterCiphersForUrl: ( + ): Promise; + abstract filterCiphersForUrl( ciphers: CipherView[], url: string, includeOtherTypes?: CipherType[], defaultMatch?: UriMatchStrategySetting, - ) => Promise; - getAllFromApiForOrganization: (organizationId: string) => Promise; + ): Promise; + abstract getAllFromApiForOrganization(organizationId: string): Promise; /** * Gets ciphers belonging to the specified organization that the user has explicit collection level access to. * Ciphers that are not assigned to any collections are only included for users with admin access. */ - getManyFromApiForOrganization: (organizationId: string) => Promise; - getLastUsedForUrl: ( + abstract getManyFromApiForOrganization(organizationId: string): Promise; + abstract getLastUsedForUrl( url: string, userId: UserId, autofillOnPageLoad: boolean, - ) => Promise; - getLastLaunchedForUrl: ( + ): Promise; + abstract getLastLaunchedForUrl( url: string, userId: UserId, autofillOnPageLoad: boolean, - ) => Promise; - getNextCipherForUrl: (url: string, userId: UserId) => Promise; - updateLastUsedIndexForUrl: (url: string) => void; - updateLastUsedDate: (id: string, userId: UserId) => Promise; - updateLastLaunchedDate: (id: string, userId: UserId) => Promise; - saveNeverDomain: (domain: string) => Promise; + ): Promise; + abstract getNextCipherForUrl(url: string, userId: UserId): Promise; + abstract updateLastUsedIndexForUrl(url: string): void; + abstract updateLastUsedDate(id: string, userId: UserId): Promise; + abstract updateLastLaunchedDate(id: string, userId: UserId): Promise; + abstract saveNeverDomain(domain: string): Promise; /** * Create a cipher with the server * @@ -91,7 +91,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + abstract createWithServer(cipher: Cipher, orgAdmin?: boolean): Promise; /** * Update a cipher with the server * @param cipher The cipher to update @@ -100,32 +100,36 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - shareWithServer: ( + abstract updateWithServer( + cipher: Cipher, + orgAdmin?: boolean, + isNotClone?: boolean, + ): Promise; + abstract shareWithServer( cipher: CipherView, organizationId: string, collectionIds: string[], userId: UserId, - ) => Promise; - shareManyWithServer: ( + ): Promise; + abstract shareManyWithServer( ciphers: CipherView[], organizationId: string, collectionIds: string[], userId: UserId, - ) => Promise; - saveAttachmentWithServer: ( + ): Promise; + abstract saveAttachmentWithServer( cipher: Cipher, unencryptedFile: any, userId: UserId, admin?: boolean, - ) => Promise; - saveAttachmentRawWithServer: ( + ): Promise; + abstract saveAttachmentRawWithServer( cipher: Cipher, filename: string, data: ArrayBuffer, userId: UserId, admin?: boolean, - ) => Promise; + ): Promise; /** * Save the collections for a cipher with the server * @@ -134,14 +138,14 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + abstract saveCollectionsWithServer(cipher: Cipher, userId: UserId): Promise; /** * Save the collections for a cipher with the server as an admin. * Used for Unassigned ciphers or when the user only has admin access to the cipher (not assigned normally). * @param cipher */ - saveCollectionsWithServerAdmin: (cipher: Cipher) => Promise; + abstract saveCollectionsWithServerAdmin(cipher: Cipher): Promise; /** * Bulk update collections for many ciphers with the server * @param orgId @@ -150,42 +154,46 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; + ): Promise; /** * Update the local store of CipherData with the provided data. Values are upserted into the existing store. * * @param cipher The cipher data to upsert. Can be a single CipherData object or an array of CipherData objects. * @returns A promise that resolves to a record of updated cipher store, keyed by their cipher ID. Returns all ciphers, not just those updated */ - upsert: (cipher: CipherData | CipherData[]) => Promise>; - replace: (ciphers: { [id: string]: CipherData }, userId: UserId) => Promise; - clear: (userId?: string) => Promise; - moveManyWithServer: (ids: string[], folderId: string, userId: UserId) => Promise; - delete: (id: string | string[], userId: UserId) => Promise; - deleteWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; - deleteManyWithServer: (ids: string[], userId: UserId, asAdmin?: boolean) => Promise; - deleteAttachment: (id: string, attachmentId: string, userId: UserId) => Promise; - deleteAttachmentWithServer: (id: string, attachmentId: string, userId: UserId) => Promise; - sortCiphersByLastUsed: (a: CipherView, b: CipherView) => number; - sortCiphersByLastUsedThenName: (a: CipherView, b: CipherView) => number; - getLocaleSortingFunction: () => (a: CipherView, b: CipherView) => number; - softDelete: (id: string | string[], userId: UserId) => Promise; - softDeleteWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; - softDeleteManyWithServer: (ids: string[], userId: UserId, asAdmin?: boolean) => Promise; - restore: ( + abstract upsert(cipher: CipherData | CipherData[]): Promise>; + abstract replace(ciphers: { [id: string]: CipherData }, userId: UserId): Promise; + abstract clear(userId?: string): Promise; + abstract moveManyWithServer(ids: string[], folderId: string, userId: UserId): Promise; + abstract delete(id: string | string[], userId: UserId): Promise; + abstract deleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract deleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise; + abstract deleteAttachment(id: string, attachmentId: string, userId: UserId): Promise; + abstract deleteAttachmentWithServer( + id: string, + attachmentId: string, + userId: UserId, + ): Promise; + abstract sortCiphersByLastUsed(a: CipherView, b: CipherView): number; + abstract sortCiphersByLastUsedThenName(a: CipherView, b: CipherView): number; + abstract getLocaleSortingFunction(): (a: CipherView, b: CipherView) => number; + abstract softDelete(id: string | string[], userId: UserId): Promise; + abstract softDeleteWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract softDeleteManyWithServer(ids: string[], userId: UserId, asAdmin?: boolean): Promise; + abstract restore( cipher: { id: string; revisionDate: string } | { id: string; revisionDate: string }[], userId: UserId, - ) => Promise; - restoreWithServer: (id: string, userId: UserId, asAdmin?: boolean) => Promise; - restoreManyWithServer: (ids: string[], orgId?: string) => Promise; - getKeyForCipherKeyDecryption: (cipher: Cipher, userId: UserId) => Promise; - setAddEditCipherInfo: (value: AddEditCipherInfo, userId: UserId) => Promise; + ): Promise; + abstract restoreWithServer(id: string, userId: UserId, asAdmin?: boolean): Promise; + abstract restoreManyWithServer(ids: string[], orgId?: string): Promise; + abstract getKeyForCipherKeyDecryption(cipher: Cipher, userId: UserId): Promise; + abstract setAddEditCipherInfo(value: AddEditCipherInfo, userId: UserId): Promise; /** * Returns user ciphers re-encrypted with the new user key. * @param originalUserKey the original user key @@ -194,11 +202,11 @@ export abstract class CipherService implements UserKeyRotationDataProvider Promise; - getNextCardCipher: (userId: UserId) => Promise; - getNextIdentityCipher: (userId: UserId) => Promise; + ): Promise; + abstract getNextCardCipher(userId: UserId): Promise; + abstract getNextIdentityCipher(userId: UserId): Promise; } From 35375ecf4714cf7d41387ba1dbbc87ea72627b31 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Thu, 23 Jan 2025 23:42:32 -0500 Subject: [PATCH 15/17] Fixed conflicts --- .../tools/reports/pages/exposed-passwords-report.component.ts | 2 -- .../tools/reports/pages/inactive-two-factor-report.component.ts | 2 -- .../tools/reports/pages/reused-passwords-report.component.ts | 2 -- .../tools/reports/pages/unsecured-websites-report.component.ts | 2 -- .../app/tools/reports/pages/weak-passwords-report.component.ts | 2 -- 5 files changed, 10 deletions(-) diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts index 5acd25b219a..1a0d4043b74 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.ts @@ -30,7 +30,6 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, - accountService: AccountService, syncService: SyncService, ) { super( @@ -40,7 +39,6 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple organizationService, accountService, i18nService, - accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts index acd279ad9a9..52c52041c9d 100644 --- a/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/inactive-two-factor-report.component.ts @@ -33,7 +33,6 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl private logService: LogService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, - accountService: AccountService, syncService: SyncService, ) { super( @@ -43,7 +42,6 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl organizationService, accountService, i18nService, - accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts index b1ff07ea261..a5c1c65560b 100644 --- a/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/reused-passwords-report.component.ts @@ -29,7 +29,6 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, - accountService: AccountService, syncService: SyncService, ) { super( @@ -39,7 +38,6 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem organizationService, accountService, i18nService, - accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts index 5e84fb16838..350e5c03980 100644 --- a/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/unsecured-websites-report.component.ts @@ -27,7 +27,6 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, - accountService: AccountService, syncService: SyncService, private collectionService: CollectionService, ) { @@ -38,7 +37,6 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl organizationService, accountService, i18nService, - accountService, syncService, ); } diff --git a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts index a3f41a81d8f..c374ecd0e4a 100644 --- a/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/weak-passwords-report.component.ts @@ -37,7 +37,6 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, - accountService: AccountService, syncService: SyncService, ) { super( @@ -47,7 +46,6 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen organizationService, accountService, i18nService, - accountService, syncService, ); } From 11e477147d5e01ffbf0ab9c012347853c9401b06 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 24 Jan 2025 00:05:33 -0500 Subject: [PATCH 16/17] Fixed test on add edit v2 Passed active userId to delete function --- .../add-edit/add-edit-v2.component.spec.ts | 10 +++++----- .../vault-v2/add-edit/add-edit-v2.component.ts | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts index e1de8820477..6974e6f7359 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from "@angular/core/testing"; import { ActivatedRoute, Router } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject, Observable } from "rxjs"; +import { BehaviorSubject } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -60,10 +60,10 @@ describe("AddEditV2Component", () => { back.mockClear(); collect.mockClear(); - cipherServiceMock = mock(); - cipherServiceMock.addEditCipherInfo$.mockReturnValue( - addEditCipherInfo$ as Observable, - ); + addEditCipherInfo$ = new BehaviorSubject(null); + cipherServiceMock = mock({ + addEditCipherInfo$: jest.fn().mockReturnValue(addEditCipherInfo$), + }); await TestBed.configureTestingModule({ imports: [AddEditV2Component], diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index 7c1cf848ef0..ac1ffd13eb3 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -10,10 +10,11 @@ import { firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -283,9 +284,7 @@ export class AddEditV2Component implements OnInit { config.initialValues = this.setInitialValuesFromParams(params); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((account) => account.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); // The browser notification bar and overlay use addEditCipherInfo$ to pass modified cipher details to the form // Attempt to fetch them here and overwrite the initialValues if present @@ -379,7 +378,8 @@ export class AddEditV2Component implements OnInit { } try { - await this.deleteCipher(); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.deleteCipher(activeUserId); } catch (e) { this.logService.error(e); return false; @@ -396,10 +396,10 @@ export class AddEditV2Component implements OnInit { return true; }; - protected deleteCipher() { + protected deleteCipher(userId: UserId) { return this.config.originalCipher.deletedDate - ? this.cipherService.deleteWithServer(this.config.originalCipher.id) - : this.cipherService.softDeleteWithServer(this.config.originalCipher.id); + ? this.cipherService.deleteWithServer(this.config.originalCipher.id, userId) + : this.cipherService.softDeleteWithServer(this.config.originalCipher.id, userId); } } From c6431768ed5f3066e296a61bd81d5da2a875f430 Mon Sep 17 00:00:00 2001 From: gbubemismith Date: Fri, 24 Jan 2025 13:38:47 -0500 Subject: [PATCH 17/17] Used getUserId utility function --- .../background/notification.background.ts | 19 +++++++-------- .../autofill/background/overlay.background.ts | 13 ++++------ .../background/web-request.background.ts | 7 +++--- .../browser/cipher-context-menu-handler.ts | 12 +++------- .../browser/context-menu-clicked-handler.ts | 7 +++--- .../autofill/popup/fido2/fido2.component.ts | 3 ++- .../src/autofill/services/autofill.service.ts | 11 ++++----- .../src/platform/listeners/update-badge.ts | 7 +++--- .../vault-list-items-container.component.ts | 5 ++-- .../components/vault-v2/vault-v2.component.ts | 10 +++----- .../vault-v2/view-v2/view-v2.component.ts | 11 ++++----- .../trash-list-items-container.component.ts | 11 ++++----- .../admin-console/commands/share.command.ts | 7 +++--- apps/cli/src/commands/edit.command.ts | 17 +++++-------- apps/cli/src/commands/get.command.ts | 12 ++++------ apps/cli/src/commands/list.command.ts | 14 ++--------- apps/cli/src/commands/restore.command.ts | 10 +++----- apps/cli/src/vault/create.command.ts | 9 ++++--- apps/cli/src/vault/delete.command.ts | 24 +++++-------------- .../services/desktop-autofill.service.ts | 10 ++------ .../encrypted-message-handler.service.ts | 23 ++++++------------ .../exposed-passwords-report.component.ts | 7 +++--- .../reused-passwords-report.component.ts | 7 +++--- .../tools/weak-passwords-report.component.ts | 8 +++---- .../settings/change-password.component.ts | 5 ++-- .../reports/pages/cipher-report.component.ts | 16 ++++--------- .../vault-item-dialog.component.ts | 10 +++----- .../bulk-delete-dialog.component.ts | 8 +++---- .../bulk-move-dialog.component.ts | 9 ++++--- .../vault/individual-vault/vault.component.ts | 13 +++++----- .../app/vault/org-vault/add-edit.component.ts | 5 ++-- .../vault/org-vault/attachments.component.ts | 3 ++- ...dmin-console-cipher-form-config.service.ts | 5 ++-- .../app/vault/org-vault/vault.component.ts | 7 +++--- .../components/collections.component.ts | 7 +++--- .../angular/src/components/share.component.ts | 7 +++--- .../vault/components/add-edit.component.ts | 11 ++++----- .../vault/components/attachments.component.ts | 13 +++++----- .../components/password-history.component.ts | 7 +++--- .../vault/components/vault-items.component.ts | 7 +++--- .../src/vault/components/view.component.ts | 13 +++++----- .../services/vault-filter.service.ts | 8 +++---- .../fido2/fido2-authenticator.service.ts | 15 ++++++------ .../event/event-collection.service.ts | 1 - .../individual-vault-export.service.ts | 11 ++++----- .../src/services/org-vault-export.service.ts | 11 ++++----- .../cipher-attachments.component.ts | 7 +++--- .../delete-attachment.component.ts | 7 +++--- .../default-cipher-form-config.service.ts | 5 ++-- .../services/default-cipher-form.service.ts | 9 ++++--- .../autofill-options-view.component.ts | 7 +++--- .../assign-collections.component.ts | 3 +-- 52 files changed, 191 insertions(+), 303 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index b4adeed41b9..5c7cac2445a 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ExtensionCommand, ExtensionCommandType, @@ -83,8 +84,6 @@ export default class NotificationBackground { getWebVaultUrlForNotification: () => this.getWebVaultUrl(), }; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private autofillService: AutofillService, private cipherService: CipherService, @@ -257,7 +256,7 @@ export default class NotificationBackground { return; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecryptedForUrl(loginInfo.url, activeUserId); const usernameMatches = ciphers.filter( (c) => c.login.username != null && c.login.username.toLowerCase() === normalizedUsername, @@ -336,7 +335,7 @@ export default class NotificationBackground { } let id: string = null; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); if (changeData.currentPassword != null) { const passwordMatches = ciphers.filter( @@ -490,7 +489,7 @@ export default class NotificationBackground { this.notificationQueue.splice(i, 1); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (queueMessage.type === NotificationQueueMessageType.ChangePassword) { const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId); @@ -555,7 +554,7 @@ export default class NotificationBackground { ) { cipherView.login.password = newPassword; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (edit) { await this.editItem(cipherView, activeUserId, tab); @@ -600,7 +599,7 @@ export default class NotificationBackground { if (Utils.isNullOrWhitespace(folderId) || folderId === "null") { return false; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const folders = await firstValueFrom(this.folderService.folderViews$(activeUserId)); return folders.some((x) => x.id === folderId); } @@ -608,7 +607,7 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string, userId: UserId) { const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -648,7 +647,7 @@ export default class NotificationBackground { * Returns the first value found from the folder service's folderViews$ observable. */ private async getFolderData() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await firstValueFrom(this.folderService.folderViews$(activeUserId)); } diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index cbfba8d4858..2a4bac197ed 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -16,6 +16,7 @@ import { parse } from "tldts"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, SHOW_AUTOFILL_BUTTON, @@ -411,9 +412,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { return this.getAllCipherTypeViews(currentTab); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipherViews = ( await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId) ).sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b)); @@ -434,9 +433,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { } this.cardAndIdentityCiphers.clear(); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipherViews = ( await this.cipherService.getAllDecryptedForUrl(currentTab.url || "", activeUserId, [ CipherType.Card, @@ -2407,9 +2404,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { try { this.closeInlineMenu(sender); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.setAddEditCipherInfo( { cipher: cipherView, diff --git a/apps/browser/src/autofill/background/web-request.background.ts b/apps/browser/src/autofill/background/web-request.background.ts index cacc5615654..5609d056381 100644 --- a/apps/browser/src/autofill/background/web-request.background.ts +++ b/apps/browser/src/autofill/background/web-request.background.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -59,9 +60,7 @@ export default class WebRequestBackground { // eslint-disable-next-line private async resolveAuthCredentials(domain: string, success: Function, error: Function) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const authStatus = await firstValueFrom(this.authService.authStatusFor$(activeUserId)); if (authStatus < AuthenticationStatus.Unlocked) { diff --git a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts index b2bb22943d6..240a5ad3955 100644 --- a/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts +++ b/apps/browser/src/autofill/browser/cipher-context-menu-handler.ts @@ -1,8 +1,9 @@ -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -39,14 +40,7 @@ export class CipherContextMenuHandler { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - - if (!activeUserId) { - // Show error be thrown here or is it okay to just return? - return; - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecryptedForUrl(url, activeUserId, [ CipherType.Card, diff --git a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts index 18ae9fa01ef..5ea15eac4f4 100644 --- a/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts +++ b/apps/browser/src/autofill/browser/context-menu-clicked-handler.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AUTOFILL_CARD_ID, AUTOFILL_ID, @@ -105,9 +106,7 @@ export class ContextMenuClickedHandler { menuItemId as string, ); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (isCreateCipherAction) { // pass; defer to logic below diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 427c598bbf4..2a423bc52fb 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -20,6 +20,7 @@ import { import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -187,7 +188,7 @@ export class Fido2Component implements OnInit, OnDestroy { ); const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), + getUserId(this.accountService.activeAccount$), ); this.ciphers = (await this.cipherService.getAllDecrypted(activeUserId)).filter( (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 669cfb5c3c3..4c1255ebd03 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -1,13 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { filter, firstValueFrom, merge, Observable, ReplaySubject, scan, startWith } from "rxjs"; -import { map, pairwise } from "rxjs/operators"; +import { pairwise } from "rxjs/operators"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AutofillOverlayVisibility, CardExpiryDateDelimiters, @@ -67,8 +68,6 @@ export default class AutofillService implements AutofillServiceInterface { private currentlyOpeningPasswordRepromptPopout = false; private autofillScriptPortsSet = new Set(); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - static searchFieldNamesSet = new Set(AutoFillConstants.SearchFieldNames); constructor( @@ -429,7 +428,7 @@ export default class AutofillService implements AutofillServiceInterface { options.cipher.login.totp = null; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); let didAutofill = false; await Promise.all( @@ -533,7 +532,7 @@ export default class AutofillService implements AutofillServiceInterface { ): Promise { let cipher: CipherView; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (fromCommand) { cipher = await this.cipherService.getNextCipherForUrl(tab.url, activeUserId); @@ -638,7 +637,7 @@ export default class AutofillService implements AutofillServiceInterface { let cipher: CipherView; let cacheKey = ""; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (cipherType === CipherType.Card) { cacheKey = "cardCiphers"; diff --git a/apps/browser/src/platform/listeners/update-badge.ts b/apps/browser/src/platform/listeners/update-badge.ts index e001dd4f971..73fad4b3d10 100644 --- a/apps/browser/src/platform/listeners/update-badge.ts +++ b/apps/browser/src/platform/listeners/update-badge.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BadgeSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/badge-settings.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -90,9 +91,7 @@ export class UpdateBadge { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecryptedForUrl(opts?.tab?.url, activeUserId); let countText = ciphers.length == 0 ? "" : ciphers.length.toString(); if (ciphers.length > 9) { diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index c3795ea8f3b..51fdaaea862 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -20,6 +20,7 @@ import { firstValueFrom, Observable, map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherId } from "@bitwarden/common/types/guid"; @@ -268,9 +269,7 @@ export class VaultListItemsContainerComponent implements OnInit, AfterViewInit { this.viewCipherTimeout = null; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.updateLastLaunchedDate(cipher.id, activeUserId); await BrowserApi.createNewTab(cipher.login.launchUri); diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index dfa919b9d12..9871cd338c1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -8,7 +8,8 @@ import { filter, map, take } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { CipherId, CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { CipherId, CollectionId, OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { ButtonModule, DialogService, Icons, NoItemsModule } from "@bitwarden/components"; @@ -122,12 +123,7 @@ export class VaultV2Component implements OnInit, OnDestroy { } async ngOnInit() { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe( - map((a) => a?.id), - filter((userId): userId is UserId => userId != null), - ), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.cipherService .failedToDecryptCiphers$(activeUserId) diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 7c8c739869b..72847c49fb5 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -5,13 +5,14 @@ import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, map, Observable, switchMap } from "rxjs"; +import { firstValueFrom, Observable, switchMap } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AUTOFILL_ID, COPY_PASSWORD_ID, @@ -100,8 +101,6 @@ export class ViewV2Component { loadAction: LoadAction; senderTabId?: number; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private route: ActivatedRoute, private router: Router, @@ -166,7 +165,7 @@ export class ViewV2Component { } async getCipherData(id: string) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -197,7 +196,7 @@ export class ViewV2Component { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.deleteCipher(activeUserId); } catch (e) { this.logService.error(e); @@ -217,7 +216,7 @@ export class ViewV2Component { restore = async (): Promise => { try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.restoreWithServer(this.cipher.id, activeUserId); } catch (e) { this.logService.error(e); diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index 780c0cc322c..19fa567c34d 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -3,10 +3,11 @@ import { CommonModule } from "@angular/common"; import { ChangeDetectionStrategy, Component, Input } from "@angular/core"; import { Router } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherId } from "@bitwarden/common/types/guid"; @@ -84,9 +85,7 @@ export class TrashListItemsContainerComponent { async restore(cipher: CipherView) { try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.restoreWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); @@ -118,9 +117,7 @@ export class TrashListItemsContainerComponent { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.deleteWithServer(cipher.id, activeUserId); await this.router.navigate(["/trash"]); diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index d3e3f1b8f84..9159f2c63af 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../../models/response"; @@ -48,9 +49,7 @@ export class ShareCommand { organizationId = organizationId.toLowerCase(); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index d1325a7e994..2d1adaefa87 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -1,11 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { CollectionRequest } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; import { FolderExport } from "@bitwarden/common/models/export/folder.export"; @@ -25,8 +26,6 @@ import { CipherResponse } from "../vault/models/cipher.response"; import { FolderResponse } from "../vault/models/folder.response"; export class EditCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -85,9 +84,7 @@ export class EditCommand { } private async editCipher(id: string, req: CipherExport) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { return Response.notFound(); @@ -114,9 +111,7 @@ export class EditCommand { } private async editCipherCollections(id: string, req: string[]) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { @@ -137,7 +132,7 @@ export class EditCommand { const decCipher = await updatedCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption( updatedCipher, - await firstValueFrom(this.activeUserId$), + await firstValueFrom(getUserId(this.accountService.activeAccount$)), ), ); const res = new CipherResponse(decCipher); @@ -148,7 +143,7 @@ export class EditCommand { } private async editFolder(id: string, req: FolderExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 338f176be45..a2b2854e77c 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -52,8 +52,6 @@ import { FolderResponse } from "../vault/models/folder.response"; import { DownloadCommand } from "./download.command"; export class GetCommand extends DownloadCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -114,7 +112,7 @@ export class GetCommand extends DownloadCommand { private async getCipherView(id: string): Promise { let decCipher: CipherView = null; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { @@ -267,7 +265,7 @@ export class GetCommand extends DownloadCommand { ); if (!canAccessPremium) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if ( originalCipher == null || @@ -354,7 +352,7 @@ export class GetCommand extends DownloadCommand { this.accountProfileService.hasPremiumFromAnySource$(account.id), ); if (!canAccessPremium) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const originalCipher = await this.cipherService.get(cipher.id, activeUserId); if (originalCipher == null || originalCipher.organizationId == null) { return Response.error("Premium status is required to use this feature."); @@ -387,7 +385,7 @@ export class GetCommand extends DownloadCommand { private async getFolder(id: string) { let decFolder: FolderView = null; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (Utils.isGuid(id)) { const folder = await this.folderService.getFromState(id, activeUserId); if (folder != null) { @@ -564,7 +562,7 @@ export class GetCommand extends DownloadCommand { private async getFingerprint(id: string) { let fingerprint: string[] = null; if (id === "me") { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const publicKey = await firstValueFrom(this.keyService.userPublicKey$(activeUserId)); fingerprint = await this.keyService.getFingerprint(activeUserId, publicKey); } else if (Utils.isGuid(id)) { diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 4a371eda91d..b85a8f8dcfd 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -66,12 +66,7 @@ export class ListCommand { private async listCiphers(options: Options) { let ciphers: CipherView[]; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (activeUserId == null) { - return Response.error("No active user id found."); - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); options.trash = options.trash || false; if (options.url != null && options.url.trim() !== "") { @@ -146,12 +141,7 @@ export class ListCommand { } private async listFolders(options: Options) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (activeUserId == null) { - return Response.error("No active user id found."); - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); let folders = await this.folderService.getAllDecryptedFromState(activeUserId); diff --git a/apps/cli/src/commands/restore.command.ts b/apps/cli/src/commands/restore.command.ts index 64c0969680f..691969099df 100644 --- a/apps/cli/src/commands/restore.command.ts +++ b/apps/cli/src/commands/restore.command.ts @@ -1,6 +1,7 @@ -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Response } from "../models/response"; @@ -25,12 +26,7 @@ export class RestoreCommand { } private async restoreCipher(id: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (activeUserId == null) { - return Response.error("No active user id found."); - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index b64d37af9a5..19ecad3a204 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -10,6 +10,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { CipherExport } from "@bitwarden/common/models/export/cipher.export"; import { CollectionExport } from "@bitwarden/common/models/export/collection.export"; @@ -30,8 +31,6 @@ import { CipherResponse } from "./models/cipher.response"; import { FolderResponse } from "./models/folder.response"; export class CreateCommand { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private folderService: FolderService, @@ -90,7 +89,7 @@ export class CreateCommand { } private async createCipher(req: CipherExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); @@ -132,7 +131,7 @@ export class CreateCommand { return Response.badRequest("File name not provided."); } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const itemId = options.itemId.toLowerCase(); const cipher = await this.cipherService.get(itemId, activeUserId); @@ -173,7 +172,7 @@ export class CreateCommand { } private async createFolder(req: FolderExport) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const userKey = await this.keyService.getUserKeyWithLegacySupport(activeUserId); const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey); try { diff --git a/apps/cli/src/vault/delete.command.ts b/apps/cli/src/vault/delete.command.ts index 1ffc7eae6d1..304c7e18105 100644 --- a/apps/cli/src/vault/delete.command.ts +++ b/apps/cli/src/vault/delete.command.ts @@ -1,7 +1,8 @@ -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -44,12 +45,7 @@ export class DeleteCommand { } private async deleteCipher(id: string, options: Options) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (activeUserId == null) { - return Response.error("No active user id found."); - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if (cipher == null) { @@ -81,12 +77,7 @@ export class DeleteCommand { return Response.badRequest("`itemid` option is required."); } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (activeUserId == null) { - return Response.error("No active user id found."); - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const itemId = options.itemId.toLowerCase(); const cipher = await this.cipherService.get(itemId, activeUserId); @@ -103,9 +94,8 @@ export class DeleteCommand { return Response.error("Attachment `" + id + "` was not found."); } - const account = await firstValueFrom(this.accountService.activeAccount$); const canAccessPremium = await firstValueFrom( - this.accountProfileService.hasPremiumFromAnySource$(account.id), + this.accountProfileService.hasPremiumFromAnySource$(activeUserId), ); if (cipher.organizationId == null && !canAccessPremium) { return Response.error("Premium status is required to use this feature."); @@ -124,9 +114,7 @@ export class DeleteCommand { } private async deleteFolder(id: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const folder = await this.folderService.getFromState(id, activeUserId); if (folder == null) { return Response.notFound(); diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index 3087249a934..e5c1ac2984e 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -13,6 +13,7 @@ import { } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -170,14 +171,7 @@ export class DesktopAutofillService implements OnDestroy { // TODO: For some reason the credentialId is passed as an empty array in the request, so we need to // get it from the cipher. For that we use the recordIdentifier, which is the cipherId. if (request.recordIdentifier && request.credentialId.length === 0) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - if (!activeUserId) { - this.logService.error("listenPasskeyAssertion error", "Active user not found"); - callback(new Error("Active user not found"), null); - return; - } + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(request.recordIdentifier, activeUserId); if (!cipher) { diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 0c470a52d5a..a8a1e738644 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -65,9 +66,7 @@ export class EncryptedMessageHandlerService { } private async checkUserStatus(userId: string): Promise { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (userId !== activeUserId) { return "not-active-user"; @@ -83,9 +82,7 @@ export class EncryptedMessageHandlerService { private async statusCommandHandler(): Promise { const accounts = await firstValueFrom(this.accountService.accounts$); - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (!accounts || !Object.keys(accounts)) { return []; @@ -114,9 +111,7 @@ export class EncryptedMessageHandlerService { } const ciphersResponse: CipherResponse[] = []; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const authStatus = await this.authService.getAuthStatus(activeUserId); if (authStatus !== AuthenticationStatus.Unlocked) { @@ -166,9 +161,7 @@ export class EncryptedMessageHandlerService { cipherView.login.uris[0].uri = credentialCreatePayload.uri; try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const encrypted = await this.cipherService.encrypt(cipherView, activeUserId); await this.cipherService.createWithServer(encrypted); @@ -200,9 +193,7 @@ export class EncryptedMessageHandlerService { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get( credentialUpdatePayload.credentialId, diff --git a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts index c233b1c7602..2bac020853e 100644 --- a/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/exposed-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; @@ -11,6 +11,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -59,9 +60,7 @@ export class ExposedPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.organization = await firstValueFrom( this.organizationService .organizations$(userId) diff --git a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts index e6b6637478d..81b097a67a0 100644 --- a/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/reused-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -10,6 +10,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; @@ -56,9 +57,7 @@ export class ReusedPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.organization = await firstValueFrom( this.organizationService .organizations$(userId) diff --git a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts index eab1e131936..e676aecf76b 100644 --- a/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts +++ b/apps/web/src/app/admin-console/organizations/tools/weak-passwords-report.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -10,6 +10,7 @@ import { OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -59,9 +60,8 @@ export class WeakPasswordsReportComponent this.isAdminConsoleActive = true; // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { - const userId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organization = await firstValueFrom( this.organizationService .organizations$(userId) diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index a9e919e88dc..b63e1a2ed74 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -94,9 +94,8 @@ export class ChangePasswordComponent async rotateUserKeyClicked() { if (this.rotateUserKey) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const ciphers = await this.cipherService.getAllDecrypted(activeUserId); let hasOldAttachments = false; if (ciphers != null) { diff --git a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts index c7408e99632..69689c41b56 100644 --- a/apps/web/src/app/tools/reports/pages/cipher-report.component.ts +++ b/apps/web/src/app/tools/reports/pages/cipher-report.component.ts @@ -1,20 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, ViewChild, ViewContainerRef, OnDestroy } from "@angular/core"; -import { - BehaviorSubject, - Observable, - Subject, - firstValueFrom, - map, - switchMap, - takeUntil, -} from "rxjs"; +import { BehaviorSubject, Observable, Subject, firstValueFrom, switchMap, takeUntil } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -47,7 +40,6 @@ export class CipherReportComponent implements OnDestroy { vaultMsg: string = "vault"; currentFilterStatus: number | string; protected filterOrgStatus$ = new BehaviorSubject(0); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroyed$: Subject = new Subject(); constructor( @@ -59,7 +51,7 @@ export class CipherReportComponent implements OnDestroy { protected i18nService: I18nService, private syncService: SyncService, ) { - this.organizations$ = this.activeUserId$.pipe( + this.organizations$ = getUserId(this.accountService.activeAccount$).pipe( switchMap((userId) => this.organizationService.organizations$(userId)), ); this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => { @@ -191,7 +183,7 @@ export class CipherReportComponent implements OnDestroy { } protected async getAllCiphers(): Promise { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await this.cipherService.getAllDecrypted(activeUserId); } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index e70b95502d6..7197bf077dd 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -12,6 +12,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions"; import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -342,10 +343,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { this.formConfig.mode = "edit"; this.formConfig.initialValues = null; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); let cipher = await this.cipherService.get(cipherView.id, activeUserId); // When the form config is used within the Admin Console, retrieve the cipher from the admin endpoint (if not found in local state) @@ -461,9 +459,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { if (config.originalCipher == null) { return; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await config.originalCipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId), ); diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 74e06600d75..cc09cacd72f 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -2,12 +2,13 @@ // @ts-strict-ignore import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -119,10 +120,7 @@ export class BulkDeleteDialogComponent { private async deleteCiphers(): Promise { const asAdmin = this.organization?.canEditAllCiphers; - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); - + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (this.permanent) { await this.cipherService.deleteManyWithServer(this.cipherIds, activeUserId, asAdmin); } else { 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 31160ba969c..c6e04c67576 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 @@ -3,9 +3,10 @@ import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -48,8 +49,6 @@ export class BulkMoveDialogComponent implements OnInit { }); folders$: Observable; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( @Inject(DIALOG_DATA) params: BulkMoveDialogParams, private dialogRef: DialogRef, @@ -65,7 +64,7 @@ export class BulkMoveDialogComponent implements OnInit { } async ngOnInit() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.folders$ = this.folderService.folderViews$(activeUserId); this.formGroup.patchValue({ folderId: (await firstValueFrom(this.folders$))[0].id, @@ -81,7 +80,7 @@ export class BulkMoveDialogComponent implements OnInit { return; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.moveManyWithServer( this.cipherIds, this.formGroup.value.folderId, diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 4e6a6032bb2..4201db38ffe 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -53,6 +53,7 @@ import { } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; @@ -194,8 +195,6 @@ export class VaultComponent implements OnInit, OnDestroy { private extensionRefreshEnabled: boolean; private hasSubscription$ = new BehaviorSubject(false); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - private vaultItemDialogRef?: DialogRef | undefined; private organizations$ = this.accountService.activeAccount$ .pipe(map((a) => a?.id)) @@ -297,7 +296,7 @@ export class VaultComponent implements OnInit, OnDestroy { : "trashCleanupWarning", ); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const firstSetup$ = this.route.queryParams.pipe( first(), @@ -820,7 +819,7 @@ export class VaultComponent implements OnInit, OnDestroy { } async editCipherId(id: string, cloneMode?: boolean) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); if ( @@ -900,7 +899,7 @@ export class VaultComponent implements OnInit, OnDestroy { * @returns Promise */ async viewCipherById(id: string) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(id, activeUserId); // If cipher exists (cipher is null when new) and MP reprompt // is on for this cipher, then show password reprompt. @@ -1096,7 +1095,7 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.restoreWithServer(c.id, activeUserId); this.toastService.showToast({ variant: "success", @@ -1179,7 +1178,7 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.deleteCipherWithServer(c.id, activeUserId, permanent); this.toastService.showToast({ diff --git a/apps/web/src/app/vault/org-vault/add-edit.component.ts b/apps/web/src/app/vault/org-vault/add-edit.component.ts index 9eea8a82bc9..941a2a593ec 100644 --- a/apps/web/src/app/vault/org-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/org-vault/add-edit.component.ts @@ -11,6 +11,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -99,7 +100,7 @@ export class AddEditComponent extends BaseAddEditComponent { protected async loadCipher() { this.isAdminConsoleAction = true; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); // Calling loadCipher first to assess if the cipher is unassigned. If null use apiService getCipherAdmin const firstCipherCheck = await super.loadCipher(activeUserId); @@ -125,7 +126,7 @@ export class AddEditComponent extends BaseAddEditComponent { protected async deleteCipher() { if (!this.organization.canEditAllCiphers) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return super.deleteCipher(activeUserId); } return this.cipher.isDeleted diff --git a/apps/web/src/app/vault/org-vault/attachments.component.ts b/apps/web/src/app/vault/org-vault/attachments.component.ts index 81f36449368..50d3b3d4500 100644 --- a/apps/web/src/app/vault/org-vault/attachments.component.ts +++ b/apps/web/src/app/vault/org-vault/attachments.component.ts @@ -6,6 +6,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -75,7 +76,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent implements On protected async loadCipher() { if (!this.organization.canEditAllCiphers) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await super.loadCipher(activeUserId); } const response = await this.apiService.getCipherAdmin(this.cipherId); diff --git a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts index 7e2629477d0..45069e1543a 100644 --- a/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts +++ b/apps/web/src/app/vault/org-vault/services/admin-console-cipher-form-config.service.ts @@ -10,6 +10,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -104,9 +105,7 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ return null; } - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const localCipher = await this.cipherService.get(id, activeUserId); diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 213331b7bb9..022817785ac 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -50,6 +50,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationBillingServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { EventType } from "@bitwarden/common/enums"; @@ -239,8 +240,6 @@ export class VaultComponent implements OnInit, OnDestroy { ), ); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private route: ActivatedRoute, private organizationService: OrganizationService, @@ -1072,7 +1071,7 @@ export class VaultComponent implements OnInit, OnDestroy { // Allow restore of an Unassigned Item try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const asAdmin = this.organization?.canEditAnyCollection || c.isUnassigned; await this.cipherService.restoreWithServer(c.id, activeUserId, asAdmin); this.toastService.showToast({ @@ -1165,7 +1164,7 @@ export class VaultComponent implements OnInit, OnDestroy { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.deleteCipherWithServer(c.id, activeUserId, permanent, c.isUnassigned); this.toastService.showToast({ variant: "success", diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index ef38d4e3d6d..32aee4087be 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -7,6 +7,7 @@ import { CollectionService, CollectionView } from "@bitwarden/admin-console/comm import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -30,8 +31,6 @@ export class CollectionsComponent implements OnInit { protected cipherDomain: Cipher; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( protected collectionService: CollectionService, protected platformUtilsService: PlatformUtilsService, @@ -48,7 +47,7 @@ export class CollectionsComponent implements OnInit { } async load() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); this.cipher = await this.cipherDomain.decrypt( @@ -96,7 +95,7 @@ export class CollectionsComponent implements OnInit { } this.cipherDomain.collectionIds = selectedCollectionIds; try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCollections(activeUserId); await this.formPromise; this.onSavedCollections.emit(); diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index 62acd0a2ecc..31aabe17d53 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -29,8 +30,6 @@ export class ShareComponent implements OnInit, OnDestroy { protected writeableCollections: Checkable[] = []; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - private _destroy = new Subject(); constructor( @@ -75,7 +74,7 @@ export class ShareComponent implements OnInit, OnDestroy { } }); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); this.cipher = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), @@ -104,7 +103,7 @@ export class ShareComponent implements OnInit, OnDestroy { return; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); const cipherView = await cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index bae6a63b125..37c30e735f1 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -12,6 +12,7 @@ import { PolicyService } from "@bitwarden/common/admin-console/abstractions/poli import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils"; import { EventType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -101,8 +102,6 @@ export class AddEditComponent implements OnInit, OnDestroy { private personalOwnershipPolicyAppliesToActiveUser: boolean; private previousCipherId: string; - protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -263,7 +262,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.title = this.i18nService.t("addItem"); } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const loadedAddEditCipherInfo = await this.loadAddEditCipherInfo(activeUserId); @@ -421,7 +420,7 @@ export class AddEditComponent implements OnInit, OnDestroy { this.cipher.id = null; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.encryptCipher(activeUserId); try { this.formPromise = this.saveCipher(cipher); @@ -515,7 +514,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.deletePromise = this.deleteCipher(activeUserId); await this.deletePromise; this.toastService.showToast({ @@ -542,7 +541,7 @@ export class AddEditComponent implements OnInit, OnDestroy { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.restorePromise = this.restoreCipher(activeUserId); await this.restorePromise; this.toastService.showToast({ diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 3765184811d..6b4b4190d4e 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -39,8 +40,6 @@ export class AttachmentsComponent implements OnInit { emergencyAccessId?: string = null; protected componentName = ""; - protected activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( protected cipherService: CipherService, protected i18nService: I18nService, @@ -85,7 +84,7 @@ export class AttachmentsComponent implements OnInit { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; this.cipher = await this.cipherDomain.decrypt( @@ -124,7 +123,7 @@ export class AttachmentsComponent implements OnInit { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.deletePromises[attachment.id] = this.deleteCipherAttachment(attachment.id, activeUserId); await this.deletePromises[attachment.id]; this.toastService.showToast({ @@ -219,7 +218,7 @@ export class AttachmentsComponent implements OnInit { } protected async init() { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.cipherDomain = await this.loadCipher(activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), @@ -275,7 +274,7 @@ export class AttachmentsComponent implements OnInit { ? attachment.key : await this.keyService.getOrgKey(this.cipher.organizationId); const decBuf = await this.encryptService.decryptToBytes(encBuf, key); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index b1a45e06bcd..4d17a5f7380 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -1,9 +1,10 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, OnInit } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -39,9 +40,7 @@ export class PasswordHistoryComponent implements OnInit { } protected async init() { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(this.cipherId, activeUserId); const decCipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), diff --git a/libs/angular/src/vault/components/vault-items.component.ts b/libs/angular/src/vault/components/vault-items.component.ts index 688393c0e23..6746b5c77bf 100644 --- a/libs/angular/src/vault/components/vault-items.component.ts +++ b/libs/angular/src/vault/components/vault-items.component.ts @@ -1,11 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; -import { BehaviorSubject, firstValueFrom, from, Subject, map, switchMap, takeUntil } from "rxjs"; +import { BehaviorSubject, firstValueFrom, from, Subject, switchMap, takeUntil } from "rxjs"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -119,9 +120,7 @@ export class VaultItemsComponent implements OnInit, OnDestroy { protected deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted; protected async doSearch(indexedCiphers?: CipherView[]) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); indexedCiphers = indexedCiphers ?? (await this.cipherService.getAllDecrypted(activeUserId)); const failedCiphers = await firstValueFrom( diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 17c470f99be..e447979543a 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -11,13 +11,14 @@ import { OnInit, Output, } from "@angular/core"; -import { firstValueFrom, map, Observable } from "rxjs"; +import { firstValueFrom, Observable } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EventType } from "@bitwarden/common/enums"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -80,8 +81,6 @@ export class ViewComponent implements OnDestroy, OnInit { private previousCipherId: string; private passwordReprompted = false; - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - get fido2CredentialCreationDateValue(): string { const dateCreated = this.i18nService.t("dateCreated"); const creationDate = this.datePipe.transform( @@ -144,7 +143,7 @@ export class ViewComponent implements OnDestroy, OnInit { async load() { this.cleanUp(); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const cipher = await this.cipherService.get(this.cipherId, activeUserId); this.cipher = await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), @@ -246,7 +245,7 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.deleteCipher(activeUserId); this.toastService.showToast({ variant: "success", @@ -269,7 +268,7 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.restoreCipher(activeUserId); this.toastService.showToast({ variant: "success", @@ -378,7 +377,7 @@ export class ViewComponent implements OnDestroy, OnInit { } if (cipherId) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.updateLastLaunchedDate(cipherId, activeUserId); } 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 1009c131810..93967fcf59c 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 @@ -33,8 +33,6 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti private readonly collapsedGroupings$: Observable> = this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c))); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( protected organizationService: OrganizationService, protected folderService: FolderService, @@ -73,7 +71,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti if (organizationId == null || organizationId == "MyVault") { folders = storedFolders; } else { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); // Otherwise, show only folders that have ciphers from the selected org and the "no folder" folder const ciphers = await this.cipherService.getAllDecrypted(activeUserId); const orgCiphers = ciphers.filter((c) => c.organizationId == organizationId); @@ -89,7 +87,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti }); }; - return this.activeUserId$.pipe( + return getUserId(this.accountService.activeAccount$).pipe( switchMap((userId) => this.folderService.folderViews$(userId)), mergeMap((folders) => from(transformation(folders))), ); @@ -135,7 +133,7 @@ export class VaultFilterService implements DeprecatedVaultFilterServiceAbstracti } async getFolderNested(id: string): Promise> { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const folders = await this.getAllFoldersNested( await firstValueFrom(this.folderService.folderViews$(activeUserId)), ); diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index b1910828210..ecc7f36c8fe 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -1,8 +1,9 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { AccountService } from "../../../auth/abstractions/account.service"; +import { getUserId } from "../../../auth/services/account.service"; import { CipherService } from "../../../vault/abstractions/cipher.service"; import { SyncService } from "../../../vault/abstractions/sync/sync.service.abstraction"; import { CipherRepromptType } from "../../../vault/enums/cipher-reprompt-type"; @@ -46,8 +47,6 @@ const KeyUsages: KeyUsage[] = ["sign"]; export class Fido2AuthenticatorService implements Fido2AuthenticatorServiceAbstraction { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private userInterface: Fido2UserInterfaceService, @@ -147,7 +146,7 @@ export class Fido2AuthenticatorService try { keyPair = await createKeyPair(); pubKeyDer = await crypto.subtle.exportKey("spki", keyPair.publicKey); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const encrypted = await this.cipherService.get(cipherId, activeUserId); cipher = await encrypted.decrypt( @@ -308,7 +307,7 @@ export class Fido2AuthenticatorService }; if (selectedFido2Credential.counter > 0) { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const encrypted = await this.cipherService.encrypt(selectedCipher, activeUserId); await this.cipherService.updateWithServer(encrypted); await this.cipherService.clearCache(activeUserId); @@ -398,7 +397,7 @@ export class Fido2AuthenticatorService return []; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers .filter( @@ -420,7 +419,7 @@ export class Fido2AuthenticatorService return []; } - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => @@ -438,7 +437,7 @@ export class Fido2AuthenticatorService } private async findCredentialsByRp(rpId: string): Promise { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const ciphers = await this.cipherService.getAllDecrypted(activeUserId); return ciphers.filter( (cipher) => diff --git a/libs/common/src/services/event/event-collection.service.ts b/libs/common/src/services/event/event-collection.service.ts index e52d06973d1..b37ec0de271 100644 --- a/libs/common/src/services/event/event-collection.service.ts +++ b/libs/common/src/services/event/event-collection.service.ts @@ -6,7 +6,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; - import { UserId } from "@bitwarden/common/types/guid"; import { EventCollectionService as EventCollectionServiceAbstraction } from "../../abstractions/event/event-collection.service"; 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 31c8aaa3bc7..f6d12784b01 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 @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as papa from "papaparse"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -32,8 +33,6 @@ export class IndividualVaultExportService extends BaseVaultExportService implements IndividualVaultExportServiceAbstraction { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private folderService: FolderService, private cipherService: CipherService, @@ -63,7 +62,7 @@ export class IndividualVaultExportService let decFolders: FolderView[] = []; let decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); promises.push( firstValueFrom(this.folderService.folderViews$(activeUserId)).then((folders) => { @@ -90,7 +89,7 @@ export class IndividualVaultExportService let folders: Folder[] = []; let ciphers: Cipher[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); promises.push( firstValueFrom(this.folderService.folders$(activeUserId)).then((f) => { @@ -107,7 +106,7 @@ export class IndividualVaultExportService await Promise.all(promises); const userKey = await this.keyService.getUserKeyWithLegacySupport( - await firstValueFrom(this.activeUserId$), + await firstValueFrom(getUserId(this.accountService.activeAccount$)), ); const encKeyValidation = await this.encryptService.encrypt(Utils.newGuid(), userKey); diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index 6807d7d458e..2f1d5e33e7e 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -1,7 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import * as papa from "papaparse"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { CollectionService, @@ -13,6 +13,7 @@ import { import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherWithIdExport, CollectionWithIdExport } from "@bitwarden/common/models/export"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; @@ -39,8 +40,6 @@ export class OrganizationVaultExportService extends BaseVaultExportService implements OrganizationVaultExportServiceAbstraction { - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - constructor( private cipherService: CipherService, private apiService: ApiService, @@ -96,7 +95,7 @@ export class OrganizationVaultExportService const decCollections: CollectionView[] = []; const decCiphers: CipherView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); promises.push( this.apiService.getOrganizationExport(organizationId).then((exportData) => { @@ -184,7 +183,7 @@ export class OrganizationVaultExportService let allDecCiphers: CipherView[] = []; let decCollections: CollectionView[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); promises.push( this.collectionService.getAllDecrypted().then(async (collections) => { @@ -217,7 +216,7 @@ export class OrganizationVaultExportService let allCiphers: Cipher[] = []; let encCollections: Collection[] = []; const promises = []; - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); promises.push( this.collectionService.getAll().then((collections) => { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index ba43a187a3f..e49a05aba97 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -21,10 +21,11 @@ import { ReactiveFormsModule, Validators, } from "@angular/forms"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; @@ -118,9 +119,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { } async ngOnInit(): Promise { - this.activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + this.activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.cipherDomain = await this.cipherService.get(this.cipherId, this.activeUserId); this.cipher = await this.cipherDomain.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), diff --git a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts index f7c75d96c08..a7406e1c5cb 100644 --- a/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/delete-attachment/delete-attachment.component.ts @@ -1,9 +1,10 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -53,9 +54,7 @@ export class DeleteAttachmentComponent { } try { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); if (activeUserId == null) { throw new Error("An active user is expected while deleting an attachment."); 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 69b38f54fb8..cdd714e1250 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 @@ -8,6 +8,7 @@ import { OrganizationService } from "@bitwarden/common/admin-console/abstraction import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; @@ -34,14 +35,12 @@ export class DefaultCipherFormConfigService implements CipherFormConfigService { private collectionService: CollectionService = inject(CollectionService); private accountService = inject(AccountService); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - async buildConfig( mode: CipherFormMode, cipherId?: CipherId, cipherType?: CipherType, ): Promise { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const [organizations, collections, allowPersonalOwnership, folders, cipher] = await firstValueFrom( diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 719a3773c83..0145fd970df 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -1,10 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { inject, Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -22,10 +23,8 @@ export class DefaultCipherFormService implements CipherFormService { private accountService: AccountService = inject(AccountService); private apiService: ApiService = inject(ApiService); - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); - async decryptCipher(cipher: Cipher): Promise { - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); return await cipher.decrypt( await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), ); @@ -33,7 +32,7 @@ export class DefaultCipherFormService implements CipherFormService { async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { // Passing the original cipher is important here as it is responsible for appending to password history - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const encryptedCipher = await this.cipherService.encrypt( cipher, activeUserId, diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts index 178dcbcd590..3aca8682032 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.ts @@ -2,10 +2,11 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; @@ -44,9 +45,7 @@ export class AutofillOptionsViewComponent { ) {} async openWebsite(selectedUri: string) { - const activeUserId = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.id)), - ); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); await this.cipherService.updateLastLaunchedDate(this.cipherId, activeUserId); this.platformUtilsService.launchUri(selectedUri); } diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 22e0ac7017e..96eb44e317b 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -179,7 +179,6 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI private get selectedOrgId(): OrganizationId { return this.formGroup.getRawValue().selectedOrg || this.params.organizationId; } - private activeUserId$ = this.accountService.activeAccount$.pipe(map((a) => a?.id)); private destroy$ = new Subject(); constructor( @@ -249,7 +248,7 @@ export class AssignCollectionsComponent implements OnInit, OnDestroy, AfterViewI .filter((i) => i.organizationId) .map((i) => i.id as CipherId); - const activeUserId = await firstValueFrom(this.activeUserId$); + const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); // Move personal items to the organization if (this.personalItemsCount > 0) {