From 1ba6d90d9d07142b1acc4d2cf65458e0d39823c3 Mon Sep 17 00:00:00 2001 From: Brandon Date: Fri, 10 Jan 2025 15:48:35 -0500 Subject: [PATCH] wire up vNextOrganizationService in web client --- .../guards/is-enterprise-org.guard.spec.ts | 27 ++++-- .../guards/is-enterprise-org.guard.ts | 17 +++- .../guards/is-paid-org.guard.spec.ts | 25 ++++-- .../organizations/guards/is-paid-org.guard.ts | 17 +++- .../guards/org-permissions.guard.spec.ts | 31 ++++--- .../guards/org-permissions.guard.ts | 20 +++-- .../guards/org-redirect.guard.spec.ts | 27 ++++-- .../guards/org-redirect.guard.ts | 29 ++++-- .../integrations/integrations.component.ts | 19 +++- .../layouts/organization-layout.component.ts | 12 ++- .../organizations/manage/events.component.ts | 19 +++- .../manage/group-add-edit.component.ts | 19 ++-- .../member-dialog/member-dialog.component.ts | 18 ++-- .../members/members.component.ts | 19 +++- ...zation-user-reset-password.service.spec.ts | 14 +-- ...rganization-user-reset-password.service.ts | 7 +- .../policies/master-password.component.ts | 18 +++- .../policies/policies.component.ts | 22 +++-- .../policies/reset-password.component.ts | 28 +++++- .../reporting/reports-home.component.ts | 20 ++++- .../settings/account.component.ts | 28 +++++- .../delete-organization-dialog.component.ts | 17 +++- .../settings/org-import.component.ts | 20 +++-- .../settings/two-factor-setup.component.ts | 16 +++- ...families-for-enterprise-setup.component.ts | 27 +++--- .../exposed-passwords-report.component.ts | 20 ++++- .../inactive-two-factor-report.component.ts | 20 ++++- .../reused-passwords-report.component.ts | 20 ++++- .../unsecured-websites-report.component.ts | 20 ++++- .../tools/weak-passwords-report.component.ts | 20 ++++- apps/web/src/app/app.component.ts | 14 +-- .../settings/account/account.component.ts | 19 ++-- .../settings/account/profile.component.ts | 20 +++-- .../settings/change-password.component.ts | 3 +- .../emergency-access.component.ts | 10 ++- .../emergency-add-edit-cipher.component.ts | 4 +- .../emergency-view-dialog.component.spec.ts | 11 ++- .../guards/organization-is-unmanaged.guard.ts | 26 +++++- .../adjust-subscription.component.ts | 21 +++-- .../change-plan-dialog.component.ts | 20 ++++- .../organization-plans.component.ts | 22 +++-- ...ganization-subscription-cloud.component.ts | 17 +++- ...ization-subscription-selfhost.component.ts | 17 +++- .../organization-payment-method.component.ts | 20 ++++- .../sm-adjust-subscription.component.ts | 21 +++-- .../sm-subscribe-standalone.component.ts | 11 ++- .../services/free-families-policy.service.ts | 34 +++++-- .../settings/sponsored-families.component.ts | 21 +++-- .../shared/add-credit-dialog.component.ts | 16 +++- .../shared/payment-method.component.ts | 20 ++++- .../org-switcher/org-switcher.component.ts | 25 ++++-- .../navigation-switcher.stories.ts | 27 ++++-- .../product-switcher.stories.ts | 32 +++++-- .../shared/product-switcher.service.spec.ts | 88 +++++++++++-------- .../shared/product-switcher.service.ts | 32 ++++--- .../request-sm-access.component.ts | 11 ++- .../sm-landing.component.ts | 15 +++- .../reports/pages/cipher-report.component.ts | 12 ++- ...exposed-passwords-report.component.spec.ts | 21 +++-- .../exposed-passwords-report.component.ts | 7 +- ...active-two-factor-report.component.spec.ts | 21 +++-- .../inactive-two-factor-report.component.ts | 7 +- .../reused-passwords-report.component.spec.ts | 22 +++-- .../reused-passwords-report.component.ts | 7 +- ...nsecured-websites-report.component.spec.ts | 21 +++-- .../unsecured-websites-report.component.ts | 7 +- .../weak-passwords-report.component.spec.ts | 21 +++-- .../pages/weak-passwords-report.component.ts | 7 +- .../collection-dialog.component.ts | 20 ++++- .../add-edit-v2.component.spec.ts | 10 +-- .../individual-vault/add-edit.component.ts | 4 +- .../bulk-share-dialog.component.ts | 8 +- .../organization-options.component.ts | 31 +++++-- .../services/vault-filter.service.spec.ts | 10 ++- .../services/vault-filter.service.ts | 12 ++- .../vault/individual-vault/vault.component.ts | 30 ++++--- .../individual-vault/view.component.spec.ts | 17 +++- .../vault/individual-vault/view.component.ts | 20 ++++- .../app/vault/org-vault/add-edit.component.ts | 4 +- .../bulk-collections-dialog.component.ts | 17 +++- ...console-cipher-form-config.service.spec.ts | 13 ++- ...dmin-console-cipher-form-config.service.ts | 22 +++-- .../vault-filter/vault-filter.service.ts | 7 +- .../vault-header/vault-header.component.ts | 3 - .../app/vault/org-vault/vault.component.ts | 54 ++++++++---- 85 files changed, 1195 insertions(+), 433 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index 5d61f7cf25a..531e2dcc1e8 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -5,11 +5,16 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, any, mock } from "jest-mock-extended"; +import { of } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserType } 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 { ProductTierType } from "@bitwarden/common/billing/enums"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @@ -41,18 +46,22 @@ const orgFactory = (props: Partial = {}) => ); describe("Is Enterprise Org Guard", () => { - let organizationService: MockProxy; + let organizationService: MockProxy; let dialogService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { - organizationService = mock(); + organizationService = mock(); dialogService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ - { provide: OrganizationService, useValue: organizationService }, + { provide: vNextOrganizationService, useValue: organizationService }, { provide: DialogService, useValue: dialogService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -82,7 +91,7 @@ describe("Is Enterprise Org Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", @@ -101,7 +110,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.User, productTierType: productTierType, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(dialogService.openSimpleDialog).toHaveBeenCalled(); expect( @@ -115,7 +124,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.Owner, productTierType: ProductTierType.Teams, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( @@ -133,7 +142,7 @@ describe("Is Enterprise Org Guard", () => { type: OrganizationUserType.User, productTierType: productTierType, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnlyNoError`); expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); expect( @@ -143,7 +152,7 @@ describe("Is Enterprise Org Guard", () => { it("proceeds with navigation if the organization in question is a enterprise organization", async () => { const org = orgFactory({ productTierType: ProductTierType.Enterprise }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/enterpriseOrgsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This component can only be accessed by a enterprise organization!", diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts index e1f3a1b8259..9e52bdecd64 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.ts @@ -7,8 +7,13 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { DialogService } from "@bitwarden/components"; @@ -22,10 +27,16 @@ import { DialogService } from "@bitwarden/components"; export function isEnterpriseOrgGuard(showError: boolean = true): CanActivateFn { return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { const router = inject(Router); - const organizationService = inject(OrganizationService); + const organizationService = inject(vNextOrganizationService); + const accountService = inject(AccountService); const dialogService = inject(DialogService); - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(getOrganizationById(route.params.organizationId)), + ); if (org == null) { return router.createUrlTree(["/"]); diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 6572b8aabd6..c4ec4ae032c 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, any, mock } from "jest-mock-extended"; +import { of } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserType } 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 { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { DialogService } from "@bitwarden/components"; import { isPaidOrgGuard } from "./is-paid-org.guard"; @@ -40,18 +45,22 @@ const orgFactory = (props: Partial = {}) => ); describe("Is Paid Org Guard", () => { - let organizationService: MockProxy; + let organizationService: MockProxy; let dialogService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { - organizationService = mock(); + organizationService = mock(); dialogService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ - { provide: OrganizationService, useValue: organizationService }, + { provide: vNextOrganizationService, useValue: organizationService }, { provide: DialogService, useValue: dialogService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -75,7 +84,7 @@ describe("Is Paid Org Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", @@ -86,7 +95,7 @@ describe("Is Paid Org Guard", () => { // `useTotp` is the current indicator of a free org, it is the baseline // feature offered above the free organization level. const org = orgFactory({ type: OrganizationUserType.User, useTotp: false }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(dialogService.openSimpleDialog).toHaveBeenCalled(); expect( @@ -98,7 +107,7 @@ describe("Is Paid Org Guard", () => { // `useTotp` is the current indicator of a free org, it is the baseline // feature offered above the free organization level. const org = orgFactory({ type: OrganizationUserType.Owner, useTotp: false }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); dialogService.openSimpleDialog.calledWith(any()).mockResolvedValue(true); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( @@ -108,7 +117,7 @@ describe("Is Paid Org Guard", () => { it("proceeds with navigation if the organization in question is a paid organization", async () => { const org = orgFactory({ useTotp: true }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/paidOrganizationsOnly`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This component can only be accessed by a paid organization!", diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts index 0fe95e3d3a0..9e6ac5348c9 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts @@ -7,8 +7,13 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DialogService } from "@bitwarden/components"; /** @@ -21,10 +26,16 @@ import { DialogService } from "@bitwarden/components"; export function isPaidOrgGuard(): CanActivateFn { return async (route: ActivatedRouteSnapshot, _state: RouterStateSnapshot) => { const router = inject(Router); - const organizationService = inject(OrganizationService); + const organizationService = inject(vNextOrganizationService); + const accountService = inject(AccountService); const dialogService = inject(DialogService); - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(getOrganizationById(route.params.organizationId)), + ); if (org == null) { return router.createUrlTree(["/"]); diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts index 39f5dc429bc..dd94181b9f1 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.spec.ts @@ -8,11 +8,16 @@ import { RouterStateSnapshot, } from "@angular/router"; import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserType } 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; @@ -31,13 +36,16 @@ const orgFactory = (props: Partial = {}) => describe("Organization Permissions Guard", () => { let router: MockProxy; - let organizationService: MockProxy; + let organizationService: MockProxy; let state: MockProxy; let route: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { router = mock(); - organizationService = mock(); + organizationService = mock(); + accountService = mockAccountServiceWith(userId); state = mock(); route = mock({ params: { @@ -48,7 +56,8 @@ describe("Organization Permissions Guard", () => { TestBed.configureTestingModule({ providers: [ { provide: Router, useValue: router }, - { provide: OrganizationService, useValue: organizationService }, + { provide: AccountService, useValue: accountService }, + { provide: vNextOrganizationService, useValue: organizationService }, { provide: ToastService, useValue: mock() }, { provide: I18nService, useValue: mock() }, { provide: SyncService, useValue: mock() }, @@ -57,7 +66,7 @@ describe("Organization Permissions Guard", () => { }); it("blocks navigation if organization does not exist", async () => { - organizationService.get.mockReturnValue(null); + organizationService.organizations$.mockReturnValue(of([])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), @@ -68,7 +77,7 @@ describe("Organization Permissions Guard", () => { it("permits navigation if no permissions are specified", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext(async () => organizationPermissionsGuard()(route, state), @@ -81,7 +90,7 @@ describe("Organization Permissions Guard", () => { const permissionsCallback = jest.fn(); permissionsCallback.mockImplementation((_org) => true); const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard(permissionsCallback)(route, state), @@ -103,7 +112,7 @@ describe("Organization Permissions Guard", () => { }); const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard(permissionsCallback)(route, state), @@ -122,7 +131,7 @@ describe("Organization Permissions Guard", () => { }), }); const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard((_org: Organization) => false)(route, state), @@ -141,7 +150,7 @@ describe("Organization Permissions Guard", () => { type: OrganizationUserType.Admin, enabled: false, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), @@ -155,7 +164,7 @@ describe("Organization Permissions Guard", () => { type: OrganizationUserType.Owner, enabled: false, }); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); const actual = await TestBed.runInInjectionContext( async () => await organizationPermissionsGuard()(route, state), diff --git a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts index 6bb881762c8..fca786d2ca6 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-permissions.guard.ts @@ -7,12 +7,12 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { - canAccessOrgAdmin, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { canAccessOrgAdmin } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { ToastService } from "@bitwarden/components"; @@ -42,17 +42,25 @@ export function organizationPermissionsGuard( ): CanActivateFn { return async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const router = inject(Router); - const organizationService = inject(OrganizationService); + const organizationService = inject(vNextOrganizationService); const toastService = inject(ToastService); const i18nService = inject(I18nService); const syncService = inject(SyncService); + const accountService = inject(AccountService); // TODO: We need to fix issue once and for all. if ((await syncService.getLastSync()) == null) { await syncService.fullSync(false); } - const org = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((org) => route.params.organizationId))), + ); + if (org == null) { return router.createUrlTree(["/"]); } diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index 264f2c6d4a8..64e83bf600d 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -5,10 +5,15 @@ import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { RouterTestingHarness } from "@angular/router/testing"; import { MockProxy, mock } from "jest-mock-extended"; +import { of } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserType } 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 { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { organizationRedirectGuard } from "./org-redirect.guard"; @@ -39,15 +44,19 @@ const orgFactory = (props: Partial = {}) => ); describe("Organization Redirect Guard", () => { - let organizationService: MockProxy; + let organizationService: MockProxy; let routerHarness: RouterTestingHarness; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(async () => { - organizationService = mock(); + organizationService = mock(); + accountService = mockAccountServiceWith(userId); TestBed.configureTestingModule({ providers: [ - { provide: OrganizationService, useValue: organizationService }, + { provide: vNextOrganizationService, useValue: organizationService }, + { provide: AccountService, useValue: accountService }, provideRouter([ { path: "", @@ -89,16 +98,16 @@ describe("Organization Redirect Guard", () => { it("redirects to `/` if the organization id provided is not found", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(null); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([])); await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the home screen!", ); }); - it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin onsole", async () => { + it("redirects to `/organizations/{id}` if no custom redirect is supplied but the user can access the admin console", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/noCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is the admin console!", @@ -107,7 +116,7 @@ describe("Organization Redirect Guard", () => { it("redirects properly when the redirect callback returns a single string", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/stringCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is a subroute of the admin console!", @@ -116,7 +125,7 @@ describe("Organization Redirect Guard", () => { it("redirects properly when the redirect callback returns an array of strings", async () => { const org = orgFactory(); - organizationService.get.calledWith(org.id).mockResolvedValue(org); + organizationService.organizations$.calledWith(userId).mockReturnValue(of([org])); await routerHarness.navigateByUrl(`organizations/${org.id}/arrayCallback`); expect(routerHarness.routeNativeElement?.querySelector("h1")?.textContent?.trim() ?? "").toBe( "This is a subroute of the admin console!", diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts index 1ab73195e4d..af71b52f89e 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.ts @@ -5,12 +5,12 @@ import { Router, RouterStateSnapshot, } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { - canAccessOrgAdmin, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { canAccessOrgAdmin } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; /** * @@ -24,9 +24,26 @@ export function organizationRedirectGuard( ): CanActivateFn { return async (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { const router = inject(Router); - const organizationService = inject(OrganizationService); + const organizationService = inject(vNextOrganizationService); + const accountService = inject(AccountService); + + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + + if (!userId) { + return router.createUrlTree(["/"]); + } + + const org = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.find((o) => o.id === route.params.organizationId)), + ), + ); - const org = await organizationService.get(route.params.organizationId); + if (!org) { + return router.createUrlTree(["/"]); + } if (customRedirect != null) { let redirectPath = customRedirect(org); diff --git a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts index d7ab6a6f617..d2227a2a1e4 100644 --- a/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts +++ b/apps/web/src/app/admin-console/organizations/integrations/integrations.component.ts @@ -4,8 +4,12 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { Observable, switchMap } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { IntegrationType } from "@bitwarden/common/enums"; import { HeaderModule } from "../../../layouts/header/header.module"; @@ -32,13 +36,22 @@ export class AdminConsoleIntegrationsComponent implements OnInit { ngOnInit(): void { this.organization$ = this.route.params.pipe( - switchMap((params) => this.organizationService.get$(params.organizationId)), + switchMap((params) => + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), ); } constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, ) { this.integrationsList = [ { diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 0b024817edc..836651bf1c6 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -3,7 +3,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, RouterModule } from "@angular/router"; -import { combineLatest, filter, map, Observable, switchMap } from "rxjs"; +import { combineLatest, filter, firstValueFrom, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { @@ -14,12 +14,14 @@ import { canAccessReportingTab, canAccessSettingsTab, canAccessVaultTab, - OrganizationService, } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { PolicyType, ProviderStatusType } 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -61,19 +63,21 @@ export class OrganizationLayoutComponent implements OnInit { constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private platformUtilsService: PlatformUtilsService, private configService: ConfigService, private policyService: PolicyService, private providerService: ProviderService, + private accountService: AccountService, ) {} async ngOnInit() { document.body.classList.remove("layout_frontend"); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.organization$ = this.route.params.pipe( map((p) => p.organizationId), - switchMap((id) => this.organizationService.organizations$.pipe(getById(id))), + switchMap((id) => this.organizationService.organizations$(userId).pipe(getById(id))), filter((org) => org != null), ); diff --git a/apps/web/src/app/admin-console/organizations/manage/events.component.ts b/apps/web/src/app/admin-console/organizations/manage/events.component.ts index c9fb1cb08f0..307c8a48baa 100644 --- a/apps/web/src/app/admin-console/organizations/manage/events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/events.component.ts @@ -2,14 +2,19 @@ // @ts-strict-ignore import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { concatMap, Subject, takeUntil } from "rxjs"; +import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs"; import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.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 { EventSystemUser } from "@bitwarden/common/enums"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; @@ -50,11 +55,12 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe private router: Router, logService: LogService, private userNamePipe: UserNamePipe, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private organizationUserApiService: OrganizationUserApiService, private providerService: ProviderService, fileDownloadService: FileDownloadService, toastService: ToastService, + private accountService: AccountService, ) { super( eventService, @@ -68,11 +74,16 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( concatMap(async (params) => { this.organizationId = params.organizationId; - this.organization = await this.organizationService.get(this.organizationId); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); if (this.organization == null || !this.organization.useEvents) { await this.router.navigate(["/organizations", this.organizationId]); return; diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index bbb9af67e3a..d6960ea3197 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -13,6 +13,7 @@ import { of, shareReplay, Subject, + switchMap, takeUntil, } from "rxjs"; @@ -22,7 +23,10 @@ import { OrganizationUserApiService, } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; @@ -97,9 +101,14 @@ export const openGroupAddEditDialog = ( templateUrl: "group-add-edit.component.html", }) export class GroupAddEditComponent implements OnInit, OnDestroy { - private organization$ = this.organizationService - .get$(this.organizationId) - .pipe(shareReplay({ refCount: true })); + private organization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.organizationId)) + .pipe(shareReplay({ refCount: true })), + ), + ); protected PermissionMode = PermissionMode; protected ResultType = GroupAddEditDialogResultType; @@ -218,7 +227,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { private formBuilder: FormBuilder, private changeDetectorRef: ChangeDetectorRef, private dialogService: DialogService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private accountService: AccountService, private collectionAdminService: CollectionAdminService, private toastService: ToastService, diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index f4e2fbccbeb..b38489bd288 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -22,7 +22,10 @@ import { OrganizationUserApiService, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserStatusType, OrganizationUserType, @@ -151,13 +154,18 @@ export class MemberDialogComponent implements OnDestroy { private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private accountService: AccountService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, private toastService: ToastService, private configService: ConfigService, ) { - this.organization$ = organizationService - .get$(this.params.organizationId) - .pipe(shareReplay({ refCount: true, bufferSize: 1 })); + this.organization$ = accountService.activeAccount$.pipe( + switchMap((account) => + organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.params.organizationId)) + .pipe(shareReplay({ refCount: true, bufferSize: 1 })), + ), + ); this.editMode = this.params.organizationUserId != null; this.tabIndex = this.params.initialTab ?? MemberDialogTab.Role; diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3d10a4a07b3..cde52a57171 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -28,7 +28,10 @@ import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; import { PolicyApiServiceAbstraction as PolicyApiService } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; @@ -40,6 +43,7 @@ import { import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { isNotSelfUpgradable, ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -121,7 +125,8 @@ export class MembersComponent extends BaseMembersComponent private policyApiService: PolicyApiService, private route: ActivatedRoute, private syncService: SyncService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private organizationUserApiService: OrganizationUserApiService, private router: Router, @@ -144,7 +149,15 @@ export class MembersComponent extends BaseMembersComponent ); const organization$ = this.route.params.pipe( - concatMap((params) => this.organizationService.get$(params.organizationId)), + concatMap((params) => + this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(params.organizationId)), + ), + ), + ), shareReplay({ refCount: true, bufferSize: 1 }), ); diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts index da71b61892d..50bb2bd5163 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.spec.ts @@ -1,12 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; import { OrganizationUserApiService, OrganizationUserResetPasswordDetailsResponse, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; @@ -27,7 +28,7 @@ describe("OrganizationUserResetPasswordService", () => { let keyService: MockProxy; let encryptService: MockProxy; - let organizationService: MockProxy; + let organizationService: MockProxy; let organizationUserApiService: MockProxy; let organizationApiService: MockProxy; let i18nService: MockProxy; @@ -35,7 +36,7 @@ describe("OrganizationUserResetPasswordService", () => { beforeAll(() => { keyService = mock(); encryptService = mock(); - organizationService = mock(); + organizationService = mock(); organizationUserApiService = mock(); organizationApiService = mock(); i18nService = mock(); @@ -164,10 +165,9 @@ describe("OrganizationUserResetPasswordService", () => { describe("getRotatedData", () => { beforeEach(() => { - organizationService.getAll.mockResolvedValue([ - createOrganization("1", "org1"), - createOrganization("2", "org2"), - ]); + organizationService.organizations$.mockReturnValue( + of([createOrganization("1", "org1"), createOrganization("2", "org2")]), + ); organizationApiService.getKeys.mockResolvedValue( new OrganizationKeysResponse({ privateKey: "test-private-key", diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 77d5ad29ccd..21b6cc19eb4 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Injectable } from "@angular/core"; +import { firstValueFrom } from "rxjs"; import { OrganizationUserApiService, @@ -8,7 +9,7 @@ import { OrganizationUserResetPasswordWithIdRequest, } from "@bitwarden/admin-console/common"; 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -34,7 +35,7 @@ export class OrganizationUserResetPasswordService constructor( private keyService: KeyService, private encryptService: EncryptService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private organizationUserApiService: OrganizationUserApiService, private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, @@ -154,7 +155,7 @@ export class OrganizationUserResetPasswordService throw new Error("New user key is required for rotation."); } - const allOrgs = await this.organizationService.getAll(); + const allOrgs = await firstValueFrom(this.organizationService.organizations$(userId)); if (!allOrgs) { return; diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index a0001060688..69003697430 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -2,11 +2,17 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { ControlsOf } from "@bitwarden/angular/types/controls-of"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; +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 { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -42,7 +48,8 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement constructor( private formBuilder: FormBuilder, i18nService: I18nService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, ) { super(); @@ -58,7 +65,12 @@ export class MasterPasswordPolicyComponent extends BasePolicyComponent implement async ngOnInit() { super.ngOnInit(); - const organization = await this.organizationService.get(this.policyResponse.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.policyResponse.organizationId)), + ); this.showKeyConnectorInfo = organization.keyConnectorEnabled; } } diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 9e96215a6c2..09571de712f 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -2,14 +2,18 @@ // @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { lastValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; +import { firstValueFrom, lastValueFrom } from "rxjs"; +import { first, map } from "rxjs/operators"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DialogService } from "@bitwarden/components"; import { PolicyListService } from "../../core/policy-list.service"; @@ -36,7 +40,8 @@ export class PoliciesComponent implements OnInit { constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private policyApiService: PolicyApiServiceAbstraction, private policyListService: PolicyListService, private dialogService: DialogService, @@ -46,7 +51,14 @@ export class PoliciesComponent implements OnInit { // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe this.route.parent.parent.params.subscribe(async (params) => { this.organizationId = params.organizationId; - this.organization = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); this.policies = this.policyListService.getPolicies(); await this.load(); diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 6307ee13fa4..36257a59bd2 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -1,9 +1,15 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { 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 { BasePolicy, BasePolicyComponent } from "./base-policy.component"; @@ -30,14 +36,30 @@ export class ResetPasswordPolicyComponent extends BasePolicyComponent implements constructor( private formBuilder: FormBuilder, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, ) { super(); } async ngOnInit() { super.ngOnInit(); - const organization = await this.organizationService.get(this.policyResponse.organizationId); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + + if (!userId) { + throw new Error("No user found."); + } + + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.policyResponse.organizationId)), + ); + + if (!organization) { + throw new Error("No organization found."); + } this.showKeyConnectorInfo = organization.keyConnectorEnabled; } } diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 2b2e1f7049b..c58ecc474f2 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -2,9 +2,14 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; -import { filter, map, Observable, startWith, concatMap } from "rxjs"; +import { filter, map, Observable, startWith, concatMap, firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { ReportVariant, reports, ReportType, ReportEntry } from "../../../tools/reports"; @@ -19,7 +24,8 @@ export class ReportsHomeComponent implements OnInit { constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private router: Router, ) {} @@ -30,8 +36,14 @@ export class ReportsHomeComponent implements OnInit { startWith(this.isReportsHomepageRouteUrl(this.router.url)), ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.reports$ = this.route.params.pipe( - concatMap((params) => this.organizationService.get$(params.organizationId)), + concatMap((params) => + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ), map((org) => this.buildReports(org.productTierType)), ); } diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index 0cf8c24d468..1e82842fd78 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -3,15 +3,29 @@ import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { combineLatest, from, lastValueFrom, of, Subject, switchMap, takeUntil } from "rxjs"; +import { + combineLatest, + firstValueFrom, + from, + lastValueFrom, + of, + Subject, + switchMap, + takeUntil, +} from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationCollectionManagementUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-collection-management-update.request"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "@bitwarden/common/admin-console/models/request/organization-update.request"; import { OrganizationResponse } from "@bitwarden/common/admin-console/models/response/organization.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/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"; @@ -77,7 +91,8 @@ export class AccountComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private keyService: KeyService, private router: Router, - private organizationService: OrganizationService, + private accountService: AccountService, + private organizationService: vNextOrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, private dialogService: DialogService, private formBuilder: FormBuilder, @@ -88,9 +103,14 @@ export class AccountComponent implements OnInit, OnDestroy { async ngOnInit() { this.selfHosted = this.platformUtilsService.isSelfHost(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( - switchMap((params) => this.organizationService.get$(params.organizationId)), + switchMap((params) => + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ), switchMap((organization) => { return combineLatest([ of(organization), diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index 680fb014b47..8f44edeefe9 100644 --- a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -3,12 +3,17 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; -import { combineLatest, Subject, takeUntil } from "rxjs"; +import { combineLatest, firstValueFrom, Subject, takeUntil } from "rxjs"; 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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { Verification } from "@bitwarden/common/auth/types/verification"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -93,7 +98,8 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private userVerificationService: UserVerificationService, private cipherService: CipherService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private formBuilder: FormBuilder, private toastService: ToastService, @@ -106,9 +112,12 @@ export class DeleteOrganizationDialogComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { this.deleteOrganizationRequestType = this.params.requestType; + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); combineLatest([ - this.organizationService.get$(this.params.organizationId), + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.params.organizationId)), this.cipherService.getAllFromApiForOrganization(this.params.organizationId), ]) .pipe(takeUntil(this.destroy$)) diff --git a/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts b/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts index 49ec7465da5..4d69a20d360 100644 --- a/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/org-import.component.ts @@ -2,13 +2,13 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; +import { firstValueFrom, map } from "rxjs"; import { CollectionAdminService } from "@bitwarden/admin-console/common"; -import { - canAccessVaultTab, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { canAccessVaultTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ImportCollectionServiceAbstraction } from "@bitwarden/importer/core"; import { ImportComponent } from "@bitwarden/importer/ui"; @@ -34,8 +34,9 @@ export class OrgImportComponent implements OnInit { constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private router: Router, + private accountService: AccountService, ) {} ngOnInit(): void { @@ -46,7 +47,12 @@ export class OrgImportComponent implements OnInit { * Callback that is called after a successful import. */ protected async onSuccessfulImport(organizationId: string): Promise { - const organization = await firstValueFrom(this.organizationService.get$(organizationId)); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(map((organizations) => organizations.find((o) => o.id === organizationId))), + ); if (organization == null) { return; } diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 48a844caa22..6105546a494 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -3,15 +3,20 @@ import { DialogRef } from "@angular/cdk/dialog"; import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { concatMap, takeUntil, map, lastValueFrom } from "rxjs"; +import { concatMap, takeUntil, map, lastValueFrom, firstValueFrom } from "rxjs"; import { first, tap } from "rxjs/operators"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorDuoResponse } from "@bitwarden/common/auth/models/response/two-factor-duo.response"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { AuthResponse } from "@bitwarden/common/auth/types/auth-response"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -35,7 +40,8 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme messagingService: MessagingService, policyService: PolicyService, private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, billingAccountProfileStateService: BillingAccountProfileStateService, ) { super( @@ -49,11 +55,13 @@ export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent impleme } async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.route.params .pipe( concatMap((params) => this.organizationService - .get$(params.organizationId) + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)) .pipe(map((organization) => ({ params, organization }))), ), tap(async (mapResponse) => { diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index b3c046d1fef..6651c10d116 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -3,15 +3,17 @@ import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { lastValueFrom, Observable, Subject } from "rxjs"; +import { firstValueFrom, lastValueFrom, Observable, Subject } from "rxjs"; import { first, map, takeUntil } from "rxjs/operators"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-console/models/request/organization/organization-sponsorship-redeem.request"; import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/pre-validate-sponsorship.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -69,7 +71,8 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { private apiService: ApiService, private syncService: SyncService, private validationService: ValidationService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private dialogService: DialogService, private formBuilder: FormBuilder, private toastService: ToastService, @@ -116,14 +119,18 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { this.loading = false; }); - this.existingFamilyOrganizations$ = this.organizationService.organizations$.pipe( - map((orgs) => - orgs.filter( - (o) => - o.productTierType === ProductTierType.Families && o.type === OrganizationUserType.Owner, + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.existingFamilyOrganizations$ = this.organizationService + .organizations$(userId) + .pipe( + map((orgs) => + orgs.filter( + (o) => + o.productTierType === ProductTierType.Families && + o.type === OrganizationUserType.Owner, + ), ), - ), - ); + ); this.existingFamilyOrganizations$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { if (orgs.length === 0) { 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..d133beb04d0 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,15 @@ // @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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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,7 +36,8 @@ export class ExposedPasswordsReportComponent cipherService: CipherService, auditService: AuditService, modalService: ModalService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, + protected accountService: AccountService, private route: ActivatedRoute, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, @@ -41,6 +47,7 @@ export class ExposedPasswordsReportComponent cipherService, auditService, organizationService, + accountService, modalService, passwordRepromptService, i18nService, @@ -52,7 +59,14 @@ 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) => { - this.organization = await this.organizationService.get(params.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); this.manageableCiphers = await this.cipherService.getAll(); }); } 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..1880b0c09be 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 @@ -2,9 +2,14 @@ // @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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -30,13 +35,15 @@ export class InactiveTwoFactorReportComponent private route: ActivatedRoute, logService: LogService, passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, + accountService: AccountService, i18nService: I18nService, syncService: SyncService, ) { super( cipherService, organizationService, + accountService, modalService, logService, passwordRepromptService, @@ -49,7 +56,14 @@ export class InactiveTwoFactorReportComponent this.isAdminConsoleActive = true; // 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); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); await super.ngOnInit(); }); } 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..3a75f348cab 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,14 @@ // @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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -30,7 +35,8 @@ export class ReusedPasswordsReportComponent cipherService: CipherService, modalService: ModalService, private route: ActivatedRoute, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, + protected accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, @@ -38,6 +44,7 @@ export class ReusedPasswordsReportComponent super( cipherService, organizationService, + accountService, modalService, passwordRepromptService, i18nService, @@ -49,7 +56,14 @@ 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) => { - this.organization = await this.organizationService.get(params.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); this.manageableCiphers = await this.cipherService.getAll(); 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..ad955799dcf 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 @@ -2,10 +2,15 @@ // @ts-strict-ignore import { Component, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; 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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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,7 +33,8 @@ export class UnsecuredWebsitesReportComponent cipherService: CipherService, modalService: ModalService, private route: ActivatedRoute, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, + protected accountService: AccountService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, @@ -37,6 +43,7 @@ export class UnsecuredWebsitesReportComponent super( cipherService, organizationService, + accountService, modalService, passwordRepromptService, i18nService, @@ -49,7 +56,14 @@ export class UnsecuredWebsitesReportComponent this.isAdminConsoleActive = true; // 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); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); await super.ngOnInit(); }); } 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..6d396a2a4f7 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,14 @@ // @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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -32,15 +37,17 @@ export class WeakPasswordsReportComponent passwordStrengthService: PasswordStrengthServiceAbstraction, modalService: ModalService, private route: ActivatedRoute, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, syncService: SyncService, + protected accountService: AccountService, ) { super( cipherService, passwordStrengthService, organizationService, + accountService, modalService, passwordRepromptService, i18nService, @@ -52,7 +59,14 @@ 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) => { - this.organization = await this.organizationService.get(params.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(params.organizationId)), + ); this.manageableCiphers = await this.cipherService.getAll(); await super.ngOnInit(); }); diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 16c783f3a5a..57022153202 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -11,12 +11,13 @@ import { EventUploadService } from "@bitwarden/common/abstractions/event/event-u import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextInternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -86,7 +87,7 @@ export class AppComponent implements OnDestroy, OnInit { private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, - private organizationService: InternalOrganizationServiceAbstraction, + private organizationService: vNextInternalOrganizationServiceAbstraction, private accountService: AccountService, private processReloadService: ProcessReloadServiceAbstraction, ) {} @@ -219,7 +220,10 @@ export class AppComponent implements OnDestroy, OnInit { break; case "syncOrganizationStatusChanged": { const { organizationId, enabled } = message; - const organizations = await firstValueFrom(this.organizationService.organizations$); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organizations = await firstValueFrom( + this.organizationService.organizations$(userId), + ); const organization = organizations.find((org) => org.id === organizationId); if (organization) { @@ -227,7 +231,7 @@ export class AppComponent implements OnDestroy, OnInit { ...organization, enabled: enabled, }; - await this.organizationService.upsert(updatedOrganization); + await this.organizationService.upsert(updatedOrganization, userId); } break; } @@ -277,7 +281,7 @@ export class AppComponent implements OnDestroy, OnInit { // will prevent any toasts from being displayed long enough to be read await this.eventUploadService.uploadEvents(); - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const logoutPromise = firstValueFrom( this.authService.authStatusFor$(userId).pipe( diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index 7e1be937a22..36bcf1be50d 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { combineLatest, from, lastValueFrom, map, Observable } from "rxjs"; +import { combineLatest, firstValueFrom, from, lastValueFrom, map, Observable } from "rxjs"; import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { DialogService } from "@bitwarden/components"; @@ -32,17 +34,22 @@ export class AccountComponent implements OnInit { private dialogService: DialogService, private userVerificationService: UserVerificationService, private configService: ConfigService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, ) {} async ngOnInit() { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const isAccountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.AccountDeprovisioning, ); - const userIsManagedByOrganization$ = this.organizationService.organizations$.pipe( - map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)), - ); + const userIsManagedByOrganization$ = this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.some((o) => o.userIsManagedByOrganization === true)), + ); const hasMasterPassword$ = from(this.userVerificationService.hasMasterPassword()); diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index 4f4920270f0..32c78cb5623 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -5,10 +5,11 @@ import { FormControl, FormGroup } from "@angular/forms"; import { firstValueFrom, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UpdateProfileRequest } from "@bitwarden/common/auth/models/request/update-profile.request"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProfileResponse } from "@bitwarden/common/models/response/profile.response"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -40,7 +41,7 @@ export class ProfileComponent implements OnInit, OnDestroy { private dialogService: DialogService, private toastService: ToastService, private configService: ConfigService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, ) {} async ngOnInit() { @@ -49,16 +50,21 @@ export class ProfileComponent implements OnInit, OnDestroy { this.fingerprintMaterial = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.managingOrganization$ = this.configService .getFeatureFlag$(FeatureFlag.AccountDeprovisioning) .pipe( switchMap((isAccountDeprovisioningEnabled) => isAccountDeprovisioningEnabled - ? this.organizationService.organizations$.pipe( - map((organizations) => - organizations.find((o) => o.userIsManagedByOrganization === true), - ), - ) + ? this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => + organizations.find((o) => o.userIsManagedByOrganization === true), + ), + ) : of(null), ), ); 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..ebbc762e5b3 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -12,6 +12,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -188,7 +189,7 @@ export class ChangePasswordComponent await this.kdfConfigService.getKdfConfig(), ); - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const newLocalKeyHash = await this.keyService.hashMasterKey( this.masterPassword, newMasterKey, diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index 316be3ed65c..5db07019744 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -4,8 +4,10 @@ import { Component, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; import { lastValueFrom, Observable, firstValueFrom } from "rxjs"; import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -64,7 +66,8 @@ export class EmergencyAccessComponent implements OnInit { private userNamePipe: UserNamePipe, private logService: LogService, private stateService: StateService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, protected dialogService: DialogService, billingAccountProfileStateService: BillingAccountProfileStateService, protected organizationManagementPreferencesService: OrganizationManagementPreferencesService, @@ -74,7 +77,8 @@ export class EmergencyAccessComponent implements OnInit { } async ngOnInit() { - const orgs = await this.organizationService.getAll(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const orgs = await firstValueFrom(this.organizationService.organizations$(userId)); this.isOrganizationOwner = orgs.some((o) => o.isOwner); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts index 59228431e65..b0327b182a9 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-add-edit-cipher.component.ts @@ -6,7 +6,7 @@ import { Component, OnInit } from "@angular/core"; import { CollectionService } from "@bitwarden/admin-console/common"; 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; @@ -49,7 +49,7 @@ export class EmergencyAddEditCipherComponent extends BaseAddEditComponent implem eventCollectionService: EventCollectionService, policyService: PolicyService, passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, logService: LogService, dialogService: DialogService, datePipe: DatePipe, diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 341e44f643b..3200170d73d 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -5,8 +5,12 @@ import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { mock } from "jest-mock-extended"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -29,6 +33,8 @@ describe("EmergencyViewDialogComponent", () => { card: {}, } as CipherView; + const userId = Utils.newGuid() as UserId; + beforeEach(async () => { open.mockClear(); close.mockClear(); @@ -36,7 +42,8 @@ describe("EmergencyViewDialogComponent", () => { await TestBed.configureTestingModule({ imports: [EmergencyViewDialogComponent, NoopAnimationsModule], providers: [ - { provide: OrganizationService, useValue: mock() }, + { provide: vNextOrganizationService, useValue: mock() }, + { provide: AccountService, useValue: mockAccountServiceWith(userId) }, { provide: CollectionService, useValue: mock() }, { provide: FolderService, useValue: mock() }, { provide: I18nService, useValue: { t: (...keys: string[]) => keys.join(" ") } }, diff --git a/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts index 0f6baa5f322..7e389138723 100644 --- a/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts +++ b/apps/web/src/app/billing/guards/organization-is-unmanaged.guard.ts @@ -1,15 +1,35 @@ import { inject } from "@angular/core"; import { ActivatedRouteSnapshot, CanActivateFn } from "@angular/router"; +import { firstValueFrom, map } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { ProviderStatusType } from "@bitwarden/common/admin-console/enums"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; export const organizationIsUnmanaged: CanActivateFn = async (route: ActivatedRouteSnapshot) => { - const organizationService = inject(OrganizationService); + const organizationService = inject(vNextOrganizationService); const providerService = inject(ProviderService); + const accountService = inject(AccountService); - const organization = await organizationService.get(route.params.organizationId); + const userId = await firstValueFrom(accountService.activeAccount$.pipe(map((a) => a?.id))); + + if (!userId) { + throw new Error("No user found."); + } + + const organization = await firstValueFrom( + organizationService + .organizations$(userId) + .pipe(getOrganizationById(route.params.organizationId)), + ); + + if (!organization) { + throw new Error("No organization found."); + } if (!organization.hasProvider) { return true; diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 0166560007e..85029d8c2f5 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -2,11 +2,16 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextInternalOrganizationServiceAbstraction, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ToastService } from "@bitwarden/components"; @@ -36,7 +41,8 @@ export class AdjustSubscription implements OnInit, OnDestroy { private organizationApiService: OrganizationApiServiceAbstraction, private formBuilder: FormBuilder, private toastService: ToastService, - private internalOrganizationService: InternalOrganizationServiceAbstraction, + private internalOrganizationService: vNextInternalOrganizationServiceAbstraction, + private accountService: AccountService, ) {} ngOnInit() { @@ -73,14 +79,19 @@ export class AdjustSubscription implements OnInit, OnDestroy { request, ); - const organization = await this.internalOrganizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.internalOrganizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const organizationData = new OrganizationData(response, { isMember: organization.isMember, isProviderUser: organization.isProviderUser, }); - await this.internalOrganizationService.upsert(organizationData); + await this.internalOrganizationService.upsert(organizationData, userId); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 9a80de555c6..a408559c356 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -13,17 +13,21 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, map, takeUntil } from "rxjs"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { @@ -189,13 +193,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { private router: Router, private syncService: SyncService, private policyService: PolicyService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, private taxService: TaxServiceAbstraction, + private accountService: AccountService, ) {} async ngOnInit(): Promise { @@ -211,7 +216,14 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { this.organizationId = this.dialogParams.organizationId; this.currentPlan = this.sub?.plan; this.selectedPlan = this.sub?.plan; - this.organization = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); if (this.deprecateStripeSourcesAPI) { const { accountCredit, paymentSource } = await this.billingApiService.getOrganizationPaymentMethod(this.organizationId); diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 4592f8de894..db2aed65152 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -11,13 +11,16 @@ import { } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; import { Router } from "@angular/router"; -import { Subject, takeUntil } from "rxjs"; -import { debounceTime } from "rxjs/operators"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; +import { debounceTime, map } from "rxjs/operators"; import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -27,6 +30,7 @@ import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/ import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request"; import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request"; import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction"; import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; @@ -170,7 +174,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private router: Router, private syncService: SyncService, private policyService: PolicyService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private messagingService: MessagingService, private formBuilder: FormBuilder, private organizationApiService: OrganizationApiServiceAbstraction, @@ -179,6 +183,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { private configService: ConfigService, private billingApiService: BillingApiServiceAbstraction, private taxService: TaxServiceAbstraction, + private accountService: AccountService, ) { this.selfHosted = this.platformUtilsService.isSelfHost(); } @@ -189,7 +194,14 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); if (this.organizationId) { - this.organization = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts index 09a4890549b..8d0e5e72bfc 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts @@ -6,9 +6,14 @@ import { concatMap, firstValueFrom, lastValueFrom, Observable, Subject, takeUnti import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationApiKeyType } 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 { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -72,7 +77,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy private apiService: ApiService, private i18nService: I18nService, private logService: LogService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private organizationApiService: OrganizationApiServiceAbstraction, private route: ActivatedRoute, private dialogService: DialogService, @@ -116,7 +122,12 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy async load() { this.loading = true; this.locale = await firstValueFrom(this.i18nService.locale$); - this.userOrg = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.userOrg = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const isIndependentOrganizationOwner = !this.userOrg.hasProvider && this.userOrg.isOwner; const isResoldOrganizationOwner = this.userOrg.hasReseller && this.userOrg.isOwner; diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index ef68de39526..5f149bb5032 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -7,10 +7,15 @@ import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationConnectionType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { OrganizationConnectionResponse } from "@bitwarden/common/admin-console/models/response/organization-connection.response"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { BillingSyncConfigApi } from "@bitwarden/common/billing/models/api/billing-sync-config.api"; import { SelfHostedOrganizationSubscriptionView } from "@bitwarden/common/billing/models/view/self-hosted-organization-subscription.view"; @@ -79,7 +84,8 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest constructor( private messagingService: MessagingService, private apiService: ApiService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private route: ActivatedRoute, private organizationApiService: OrganizationApiServiceAbstraction, private platformUtilsService: PlatformUtilsService, @@ -115,7 +121,12 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest return; } this.loading = true; - this.userOrg = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.userOrg = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); this.showAutomaticSyncAndManualUpload = this.userOrg.productTierType == ProductTierType.Families ? false : true; if (this.userOrg.canViewSubscription) { diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index 270ba54f70d..f7b1cead5fe 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -4,11 +4,15 @@ import { Location } from "@angular/common"; import { Component, OnDestroy } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ActivatedRoute, Router } from "@angular/router"; -import { from, lastValueFrom, switchMap } from "rxjs"; +import { firstValueFrom, from, lastValueFrom, map, switchMap } from "rxjs"; 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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { VerifyBankAccountRequest } from "@bitwarden/common/billing/models/request/verify-bank-account.request"; @@ -59,7 +63,8 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { private toastService: ToastService, private location: Location, private trialFlowService: TrialFlowService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, protected syncService: SyncService, ) { this.activatedRoute.params @@ -120,7 +125,14 @@ export class OrganizationPaymentMethodComponent implements OnDestroy { const organizationSubscriptionPromise = this.organizationApiService.getSubscription( this.organizationId, ); - const organizationPromise = this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const organizationPromise = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); [this.organizationSubscriptionResponse, this.organization] = await Promise.all([ organizationSubscriptionPromise, diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index fc7a188f967..93c0924eb90 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -2,11 +2,16 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core"; import { FormBuilder, Validators } from "@angular/forms"; -import { Subject, takeUntil } from "rxjs"; +import { Subject, firstValueFrom, takeUntil } from "rxjs"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextInternalOrganizationServiceAbstraction, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -106,7 +111,8 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private toastService: ToastService, - private internalOrganizationService: InternalOrganizationServiceAbstraction, + private internalOrganizationService: vNextInternalOrganizationServiceAbstraction, + private accountService: AccountService, ) {} ngOnInit() { @@ -165,14 +171,19 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest request, ); - const organization = await this.internalOrganizationService.get(this.organizationId); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const organization = await firstValueFrom( + this.internalOrganizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); const organizationData = new OrganizationData(response, { isMember: organization.isMember, isProviderUser: organization.isProviderUser, }); - await this.internalOrganizationService.upsert(organizationData); + await this.internalOrganizationService.upsert(organizationData, userId); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 7ad0895809c..51d8d45b9c5 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -2,12 +2,15 @@ // @ts-strict-ignore import { Component, EventEmitter, Input, Output } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextInternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data"; 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 { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request"; import { BillingCustomerDiscount } from "@bitwarden/common/billing/models/response/organization-subscription.response"; import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response"; @@ -35,8 +38,9 @@ export class SecretsManagerSubscribeStandaloneComponent { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private organizationApiService: OrganizationApiServiceAbstraction, - private organizationService: InternalOrganizationServiceAbstraction, + private organizationService: vNextInternalOrganizationServiceAbstraction, private toastService: ToastService, + private accountService: AccountService, ) {} submit = async () => { @@ -56,7 +60,8 @@ export class SecretsManagerSubscribeStandaloneComponent { isMember: this.organization.isMember, isProviderUser: this.organization.isProviderUser, }); - await this.organizationService.upsert(organizationData); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + await this.organizationService.upsert(organizationData, userId); /* Because subscribing to Secrets Manager automatically provides access to Secrets Manager for the diff --git a/apps/web/src/app/billing/services/free-families-policy.service.ts b/apps/web/src/app/billing/services/free-families-policy.service.ts index cc53e0a32bc..f6f44a0308d 100644 --- a/apps/web/src/app/billing/services/free-families-policy.service.ts +++ b/apps/web/src/app/billing/services/free-families-policy.service.ts @@ -1,10 +1,11 @@ import { Injectable } from "@angular/core"; import { combineLatest, filter, from, map, Observable, of, switchMap } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -24,16 +25,35 @@ export class FreeFamiliesPolicyService { constructor( private policyService: PolicyService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private configService: ConfigService, ) {} + canManageSponsorships$ = this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (account?.id) { + return this.organizationService.canManageSponsorships$(account?.id); + } else { + return of(); + } + }), + ); + + organizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => { + if (account?.id) { + return this.organizationService.organizations$(account?.id); + } else { + return of(); + } + }), + ); + get showFreeFamilies$(): Observable { return this.isFreeFamilyFlagEnabled$.pipe( switchMap((isFreeFamilyFlagEnabled) => - isFreeFamilyFlagEnabled - ? this.getFreeFamiliesVisibility$() - : this.organizationService.canManageSponsorships$, + isFreeFamilyFlagEnabled ? this.getFreeFamiliesVisibility$() : this.canManageSponsorships$, ), ); } @@ -41,7 +61,7 @@ export class FreeFamiliesPolicyService { private getFreeFamiliesVisibility$(): Observable { return combineLatest([ this.checkEnterpriseOrganizationsAndFetchPolicy(), - this.organizationService.canManageSponsorships$, + this.canManageSponsorships$, ]).pipe( map(([orgStatus, canManageSponsorships]) => this.shouldShowFreeFamilyLink(orgStatus, canManageSponsorships), @@ -61,7 +81,7 @@ export class FreeFamiliesPolicyService { } checkEnterpriseOrganizationsAndFetchPolicy(): Observable { - return this.organizationService.organizations$.pipe( + return this.organizations$.pipe( filter((organizations) => Array.isArray(organizations) && organizations.length > 0), switchMap((organizations) => this.fetchEnterpriseOrganizationPolicy(organizations)), ); diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 5e26e80a30a..3a714f05770 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -14,11 +14,12 @@ import { Router } from "@angular/router"; import { combineLatest, firstValueFrom, map, Observable, Subject, takeUntil } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { 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 { PlanSponsorshipType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @@ -59,7 +60,7 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private syncService: SyncService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private formBuilder: FormBuilder, private accountService: AccountService, private toastService: ToastService, @@ -90,11 +91,13 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { FeatureFlag.DisableFreeFamiliesSponsorship, ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + if (this.isFreeFamilyFlagEnabled) { await this.preventAccessToFreeFamiliesPage(); this.availableSponsorshipOrgs$ = combineLatest([ - this.organizationService.organizations$, + this.organizationService.organizations$(userId), this.policyService.getAll$(PolicyType.FreeFamiliesSponsorshipPolicy), ]).pipe( map(([organizations, policies]) => @@ -111,9 +114,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { ), ); } else { - this.availableSponsorshipOrgs$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable)), - ); + this.availableSponsorshipOrgs$ = this.organizationService + .organizations$(userId) + .pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipAvailable))); } this.availableSponsorshipOrgs$.pipe(takeUntil(this._destroy)).subscribe((orgs) => { @@ -126,9 +129,9 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy { this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); - this.activeSponsorshipOrgs$ = this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null)), - ); + this.activeSponsorshipOrgs$ = this.organizationService + .organizations$(userId) + .pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null))); this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0)); diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index 71afde81ee3..8074d14cff7 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -6,7 +6,10 @@ import { FormControl, FormGroup, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BitPayInvoiceRequest } from "@bitwarden/common/billing/models/request/bit-pay-invoice.request"; @@ -61,7 +64,7 @@ export class AddCreditDialogComponent implements OnInit { private accountService: AccountService, private apiService: ApiService, private platformUtilsService: PlatformUtilsService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private logService: LogService, private configService: ConfigService, ) { @@ -77,7 +80,14 @@ export class AddCreditDialogComponent implements OnInit { this.creditAmount = "20.00"; } this.ppButtonCustomField = "organization_id:" + this.organizationId; - const org = await this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const org = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); if (org != null) { this.subject = org.name; this.name = org.name; diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index 149b4adf520..675d7060ba1 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -4,12 +4,16 @@ import { Location } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { FormBuilder, FormControl, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; -import { lastValueFrom } from "rxjs"; +import { firstValueFrom, lastValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { PaymentMethodType } from "@bitwarden/common/billing/enums"; import { BillingPaymentResponse } from "@bitwarden/common/billing/models/response/billing-payment.response"; import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response"; @@ -72,7 +76,8 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { private dialogService: DialogService, private toastService: ToastService, private trialFlowService: TrialFlowService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, protected syncService: SyncService, ) { const state = this.router.getCurrentNavigation()?.extras?.state; @@ -117,7 +122,14 @@ export class PaymentMethodComponent implements OnInit, OnDestroy { const organizationSubscriptionPromise = this.organizationApiService.getSubscription( this.organizationId, ); - const organizationPromise = this.organizationService.get(this.organizationId); + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + const organizationPromise = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe(getOrganizationById(this.organizationId)), + ); [this.billing, this.org, this.organization] = await Promise.all([ billingPromise, diff --git a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts index b09b32d060e..7d9ad341908 100644 --- a/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts +++ b/apps/web/src/app/layouts/org-switcher/org-switcher.component.ts @@ -3,11 +3,12 @@ import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; -import { combineLatest, map, Observable } from "rxjs"; +import { combineLatest, map, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import type { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions/billing-api.service.abstraction"; import { DialogService, NavigationModule } from "@bitwarden/components"; @@ -20,12 +21,17 @@ import { TrialFlowService } from "./../../billing/services/trial-flow.service"; imports: [CommonModule, JslibModule, NavigationModule], }) export class OrgSwitcherComponent { - protected organizations$: Observable = - this.organizationService.organizations$.pipe( - map((orgs) => - orgs.filter((org) => this.filter(org)).sort((a, b) => a.name.localeCompare(b.name)), - ), - ); + protected organizations$: Observable = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe( + map((orgs) => + orgs.filter((org) => this.filter(org)).sort((a, b) => a.name.localeCompare(b.name)), + ), + ), + ), + ); protected activeOrganization$: Observable = combineLatest([ this.route.paramMap, @@ -58,9 +64,10 @@ export class OrgSwitcherComponent { constructor( private route: ActivatedRoute, protected dialogService: DialogService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private trialFlowService: TrialFlowService, protected billingApiService: BillingApiServiceAbstraction, + private accountService: AccountService, ) {} protected toggle(event?: MouseEvent) { diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index a7ff50b4264..7d6e1251040 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -1,15 +1,17 @@ import { Component, Directive, importProvidersFrom, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; import { LayoutComponent, NavigationModule } from "@bitwarden/components"; import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; @@ -20,13 +22,16 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon @Directive({ selector: "[mockOrgs]", }) -class MockOrganizationService implements Partial { +class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); - organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects + + organizations$(): Observable { + return MockOrganizationService._orgs.asObservable(); + } @Input() set mockOrgs(orgs: Organization[]) { - this.organizations$.next(orgs); + MockOrganizationService._orgs.next(orgs); } } @@ -52,6 +57,15 @@ class MockSyncService implements Partial { } } +class MockAccountService implements Partial { + activeAccount$?: Observable = of({ + id: "test-user-id" as UserId, + name: "Test User 1", + email: "test@email.com", + emailVerified: true, + }); +} + @Component({ selector: "story-layout", template: ``, @@ -85,7 +99,8 @@ export default { ], imports: [NavigationModule, RouterModule, LayoutComponent], providers: [ - { provide: OrganizationService, useClass: MockOrganizationService }, + { provide: vNextOrganizationService, useClass: MockOrganizationService }, + { provide: AccountService, useClass: MockAccountService }, { provide: ProviderService, useClass: MockProviderService }, { provide: SyncService, useClass: MockSyncService }, ProductSwitcherService, diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index b53d0243f64..d386594e0ae 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -1,15 +1,17 @@ import { Component, Directive, importProvidersFrom, Input } from "@angular/core"; import { RouterModule } from "@angular/router"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { BehaviorSubject, firstValueFrom, Observable, of } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +import { AccountService, Account } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; import { IconButtonModule, LinkModule, MenuModule } from "@bitwarden/components"; import { I18nMockService } from "@bitwarden/components/src/utils/i18n-mock.service"; @@ -20,13 +22,16 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Directive({ selector: "[mockOrgs]", }) -class MockOrganizationService implements Partial { +class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); - organizations$ = MockOrganizationService._orgs; // eslint-disable-line rxjs/no-exposed-subjects + + organizations$(): Observable { + return MockOrganizationService._orgs.asObservable(); + } @Input() set mockOrgs(orgs: Organization[]) { - this.organizations$.next(orgs); + MockOrganizationService._orgs.next(orgs); } } @@ -52,6 +57,15 @@ class MockSyncService implements Partial { } } +class MockAccountService implements Partial { + activeAccount$?: Observable = of({ + id: "test-user-id" as UserId, + name: "Test User 1", + email: "test@email.com", + emailVerified: true, + }); +} + @Component({ selector: "story-layout", template: ``, @@ -78,7 +92,9 @@ export default { ], imports: [JslibModule, MenuModule, IconButtonModule, LinkModule, RouterModule], providers: [ - { provide: OrganizationService, useClass: MockOrganizationService }, + { provide: AccountService, useClass: MockAccountService }, + MockAccountService, + { provide: vNextOrganizationService, useClass: MockOrganizationService }, MockOrganizationService, { provide: ProviderService, useClass: MockProviderService }, MockProviderService, @@ -134,7 +150,9 @@ export default { ], } as Meta; -type Story = StoryObj; +type Story = StoryObj< + ProductSwitcherComponent & MockProviderService & MockOrganizationService & MockAccountService +>; const Template: Story = { render: (args) => ({ diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a071d0f8852..736a79005c9 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -6,21 +6,27 @@ import { mock, MockProxy } from "jest-mock-extended"; import { Observable, firstValueFrom, of } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Provider } from "@bitwarden/common/admin-console/models/domain/provider"; +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 { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +import { UserId } from "@bitwarden/common/types/guid"; import { ProductSwitcherService } from "./product-switcher.service"; describe("ProductSwitcherService", () => { let service: ProductSwitcherService; let router: { url: string; events: Observable }; - let organizationService: MockProxy; + let organizationService: MockProxy; let providerService: MockProxy; + let accountService: FakeAccountService; let activeRouteParams = convertToParamMap({ organizationId: "1234" }); const getLastSync = jest.fn().mockResolvedValue(new Date("2024-05-14")); + const userId = Utils.newGuid() as UserId; // The service is dependent on the SyncService, which is behind a `setTimeout` // Most of the tests don't need to test this aspect so `advanceTimersByTime` @@ -34,19 +40,21 @@ describe("ProductSwitcherService", () => { jest.useFakeTimers(); getLastSync.mockResolvedValue(new Date("2024-05-14")); router = mock(); - organizationService = mock(); + organizationService = mock(); providerService = mock(); + accountService = mockAccountServiceWith(userId); router.url = "/"; router.events = of({}); - organizationService.organizations$ = of([{}] as Organization[]); + organizationService.organizations$.mockReturnValue(of([{}] as Organization[])); providerService.getAll.mockResolvedValue([] as Provider[]); TestBed.configureTestingModule({ providers: [ { provide: Router, useValue: router }, - { provide: OrganizationService, useValue: organizationService }, + { provide: vNextOrganizationService, useValue: organizationService }, { provide: ProviderService, useValue: providerService }, + { provide: AccountService, useValue: accountService }, { provide: ActivatedRoute, useValue: { @@ -111,14 +119,16 @@ describe("ProductSwitcherService", () => { }); it("is included in bento when there is an organization with SM", async () => { - organizationService.organizations$ = of([ - { - id: "1234", - canAccessSecretsManager: true, - enabled: true, - canAccessExport: (_) => true, - }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { + id: "1234", + canAccessSecretsManager: true, + enabled: true, + canAccessExport: (_) => true, + }, + ] as Organization[]), + ); initiateService(); @@ -139,7 +149,9 @@ describe("ProductSwitcherService", () => { }); it("includes Admin Console in bento when a user has access to it", async () => { - organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([{ id: "1234", isOwner: true }] as Organization[]), + ); initiateService(); @@ -195,7 +207,9 @@ describe("ProductSwitcherService", () => { }); it("marks Admin Console as active", async () => { - organizationService.organizations$ = of([{ id: "1234", isOwner: true }] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([{ id: "1234", isOwner: true }] as Organization[]), + ); activeRouteParams = convertToParamMap({ organizationId: "1" }); router.url = "/organizations/"; @@ -226,22 +240,24 @@ describe("ProductSwitcherService", () => { it("updates secrets manager path when the org id is found in the path", async () => { router.url = "/sm/4243"; - organizationService.organizations$ = of([ - { - id: "23443234", - canAccessSecretsManager: true, - enabled: true, - name: "Org 2", - canAccessExport: (_) => true, - }, - { - id: "4243", - canAccessSecretsManager: true, - enabled: true, - name: "Org 32", - canAccessExport: (_) => true, - }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { + id: "23443234", + canAccessSecretsManager: true, + enabled: true, + name: "Org 2", + canAccessExport: (_) => true, + }, + { + id: "4243", + canAccessSecretsManager: true, + enabled: true, + name: "Org 32", + canAccessExport: (_) => true, + }, + ] as Organization[]), + ); initiateService(); @@ -256,10 +272,12 @@ describe("ProductSwitcherService", () => { it("updates admin console path when the org id is found in the path", async () => { router.url = "/organizations/111-22-33"; - organizationService.organizations$ = of([ - { id: "111-22-33", isOwner: true, name: "Test Org" }, - { id: "4243", isOwner: true, name: "My Org" }, - ] as Organization[]); + organizationService.organizations$.mockReturnValue( + of([ + { id: "111-22-33", isOwner: true, name: "Test Org" }, + { id: "4243", isOwner: true, name: "My Org" }, + ] as Organization[]), + ); initiateService(); diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts index 2c16886a2d4..e15978c9e44 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.ts @@ -2,15 +2,23 @@ // @ts-strict-ignore import { Injectable } from "@angular/core"; import { ActivatedRoute, NavigationEnd, NavigationStart, ParamMap, Router } from "@angular/router"; -import { combineLatest, concatMap, filter, map, Observable, ReplaySubject, startWith } from "rxjs"; +import { + combineLatest, + concatMap, + filter, + map, + Observable, + ReplaySubject, + startWith, + switchMap, +} from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { - canAccessOrgAdmin, - OrganizationService, -} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { canAccessOrgAdmin } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SyncService } from "@bitwarden/common/platform/sync"; export type ProductSwitcherItem = { @@ -84,24 +92,26 @@ export class ProductSwitcherService { ]).pipe(map(() => null)); constructor( - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private providerService: ProviderService, private route: ActivatedRoute, private router: Router, private i18n: I18nPipe, private syncService: SyncService, + private accountService: AccountService, ) { this.pollUntilSynced(); } + organizations$ = this.accountService.activeAccount$.pipe( + map((a) => a?.id), + switchMap((id) => this.organizationService.organizations$(id)), + ); + products$: Observable<{ bento: ProductSwitcherItem[]; other: ProductSwitcherItem[]; - }> = combineLatest([ - this.organizationService.organizations$, - this.route.paramMap, - this.triggerProductUpdate$, - ]).pipe( + }> = combineLatest([this.organizations$, this.route.paramMap, this.triggerProductUpdate$]).pipe( map(([orgs, ...rest]): [Organization[], ParamMap, void] => { return [ // Sort orgs by name to match the order within the sidebar diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts index 8909df6cf8a..103d3667316 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/request-sm-access.component.ts @@ -3,9 +3,12 @@ import { Component, OnInit } from "@angular/core"; import { FormControl, FormGroup, Validators } from "@angular/forms"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { Guid } from "@bitwarden/common/types/guid"; import { NoItemsModule, SearchModule, ToastService } from "@bitwarden/components"; @@ -36,13 +39,15 @@ export class RequestSMAccessComponent implements OnInit { constructor( private router: Router, private i18nService: I18nService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private smLandingApiService: SmLandingApiService, private toastService: ToastService, + private accountService: AccountService, ) {} async ngOnInit() { - this.organizations = (await this.organizationService.getAll()) + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organizations = (await firstValueFrom(this.organizationService.organizations$(userId))) .filter((e) => e.enabled) .sort((a, b) => a.name.localeCompare(b.name)); diff --git a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts index 3698031a5b6..6d6b60621e8 100644 --- a/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts +++ b/apps/web/src/app/secrets-manager/secrets-manager-landing/sm-landing.component.ts @@ -1,9 +1,12 @@ // 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { NoItemsModule, SearchModule } from "@bitwarden/components"; import { HeaderModule } from "../../layouts/header/header.module"; @@ -22,10 +25,16 @@ export class SMLandingComponent implements OnInit { showSecretsManagerInformation: boolean = true; showGiveMembersAccessInstructions: boolean = false; - constructor(private organizationService: OrganizationService) {} + constructor( + private organizationService: vNextOrganizationService, + private accountService: AccountService, + ) {} async ngOnInit() { - const enabledOrganizations = (await this.organizationService.getAll()).filter((e) => e.enabled); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + const enabledOrganizations = ( + await firstValueFrom(this.organizationService.organizations$(userId)) + ).filter((e) => e.enabled); if (enabledOrganizations.length > 0) { this.handleEnabledOrganizations(enabledOrganizations); 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..28380006058 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, 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -44,11 +45,14 @@ export class CipherReportComponent implements OnDestroy { protected cipherService: CipherService, private modalService: ModalService, protected passwordRepromptService: PasswordRepromptService, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + protected accountService: AccountService, protected i18nService: I18nService, private syncService: SyncService, ) { - this.organizations$ = this.organizationService.organizations$; + this.organizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => this.organizationService.organizations$(account?.id)), + ); this.organizations$.pipe(takeUntil(this.destroyed$)).subscribe((orgs) => { this.organizations = orgs; }); 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..09ffed01ea3 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 @@ -6,8 +6,12 @@ import { of } from "rxjs"; 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { 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"; @@ -19,14 +23,17 @@ describe("ExposedPasswordsReportComponent", () => { let component: ExposedPasswordsReportComponent; let fixture: ComponentFixture; let auditService: MockProxy; - let organizationService: MockProxy; + let organizationService: MockProxy; let syncServiceMock: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { syncServiceMock = mock(); auditService = mock(); - organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([])); + accountService = mockAccountServiceWith(userId); // 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 TestBed.configureTestingModule({ @@ -41,9 +48,13 @@ describe("ExposedPasswordsReportComponent", () => { useValue: auditService, }, { - provide: OrganizationService, + provide: vNextOrganizationService, useValue: organizationService, }, + { + provide: AccountService, + useValue: accountService, + }, { provide: ModalService, useValue: mock(), 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..fc07d19c92b 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 @@ -2,7 +2,8 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -24,7 +25,8 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple constructor( protected cipherService: CipherService, protected auditService: AuditService, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + accountService: AccountService, modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, @@ -35,6 +37,7 @@ export class ExposedPasswordsReportComponent extends CipherReportComponent imple modalService, passwordRepromptService, organizationService, + accountService, i18nService, 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..e6b13cc566f 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 @@ -5,9 +5,13 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; +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"; @@ -18,13 +22,16 @@ import { cipherData } from "./reports-ciphers.mock"; describe("InactiveTwoFactorReportComponent", () => { let component: InactiveTwoFactorReportComponent; let fixture: ComponentFixture; - let organizationService: MockProxy; + let organizationService: MockProxy; let syncServiceMock: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { - organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); + accountService = mockAccountServiceWith(userId); // 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 TestBed.configureTestingModule({ @@ -35,9 +42,13 @@ describe("InactiveTwoFactorReportComponent", () => { useValue: mock(), }, { - provide: OrganizationService, + provide: vNextOrganizationService, useValue: organizationService, }, + { + provide: AccountService, + useValue: accountService, + }, { provide: ModalService, useValue: mock(), 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..cf432867150 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 @@ -3,7 +3,8 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -26,7 +27,8 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl constructor( protected cipherService: CipherService, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + accountService: AccountService, modalService: ModalService, private logService: LogService, passwordRepromptService: PasswordRepromptService, @@ -38,6 +40,7 @@ export class InactiveTwoFactorReportComponent extends CipherReportComponent impl modalService, passwordRepromptService, organizationService, + accountService, i18nService, 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..e70142c3c1b 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 @@ -5,8 +5,12 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { 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"; @@ -17,13 +21,17 @@ import { ReusedPasswordsReportComponent } from "./reused-passwords-report.compon describe("ReusedPasswordsReportComponent", () => { let component: ReusedPasswordsReportComponent; let fixture: ComponentFixture; - let organizationService: MockProxy; + let organizationService: MockProxy; let syncServiceMock: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { - organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); + accountService = mockAccountServiceWith(userId); + // 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 TestBed.configureTestingModule({ @@ -34,9 +42,13 @@ describe("ReusedPasswordsReportComponent", () => { useValue: mock(), }, { - provide: OrganizationService, + provide: vNextOrganizationService, useValue: organizationService, }, + { + provide: AccountService, + useValue: accountService, + }, { provide: ModalService, useValue: mock(), 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..7eb14081e92 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 @@ -3,7 +3,8 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -23,7 +24,8 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem constructor( protected cipherService: CipherService, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + accountService: AccountService, modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, @@ -34,6 +36,7 @@ export class ReusedPasswordsReportComponent extends CipherReportComponent implem modalService, passwordRepromptService, organizationService, + accountService, i18nService, 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..cda334f2694 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 @@ -6,8 +6,12 @@ import { of } from "rxjs"; 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { 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"; @@ -18,15 +22,18 @@ import { UnsecuredWebsitesReportComponent } from "./unsecured-websites-report.co describe("UnsecuredWebsitesReportComponent", () => { let component: UnsecuredWebsitesReportComponent; let fixture: ComponentFixture; - let organizationService: MockProxy; + let organizationService: MockProxy; let syncServiceMock: MockProxy; let collectionService: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { - organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([])); syncServiceMock = mock(); collectionService = mock(); + accountService = mockAccountServiceWith(userId); // 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 TestBed.configureTestingModule({ @@ -37,9 +44,13 @@ describe("UnsecuredWebsitesReportComponent", () => { useValue: mock(), }, { - provide: OrganizationService, + provide: vNextOrganizationService, useValue: organizationService, }, + { + provide: AccountService, + useValue: accountService, + }, { provide: ModalService, useValue: mock(), 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..fdb6f3ebf1a 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 @@ -2,7 +2,8 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -21,7 +22,8 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl constructor( protected cipherService: CipherService, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + accountService: AccountService, modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, @@ -33,6 +35,7 @@ export class UnsecuredWebsitesReportComponent extends CipherReportComponent impl modalService, passwordRepromptService, organizationService, + accountService, i18nService, 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..9e76ef8bb0b 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 @@ -5,9 +5,13 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { 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"; @@ -19,14 +23,17 @@ describe("WeakPasswordsReportComponent", () => { let component: WeakPasswordsReportComponent; let fixture: ComponentFixture; let passwordStrengthService: MockProxy; - let organizationService: MockProxy; + let organizationService: MockProxy; let syncServiceMock: MockProxy; + let accountService: FakeAccountService; + const userId = Utils.newGuid() as UserId; beforeEach(() => { syncServiceMock = mock(); passwordStrengthService = mock(); - organizationService = mock(); - organizationService.organizations$ = of([]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([])); + accountService = mockAccountServiceWith(userId); // 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 TestBed.configureTestingModule({ @@ -41,9 +48,13 @@ describe("WeakPasswordsReportComponent", () => { useValue: passwordStrengthService, }, { - provide: OrganizationService, + provide: vNextOrganizationService, useValue: organizationService, }, + { + provide: AccountService, + useValue: accountService, + }, { provide: ModalService, useValue: mock(), 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..9149e63b744 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 @@ -3,7 +3,8 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -31,7 +32,8 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen constructor( protected cipherService: CipherService, protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, + protected accountService: AccountService, modalService: ModalService, passwordRepromptService: PasswordRepromptService, i18nService: I18nService, @@ -42,6 +44,7 @@ export class WeakPasswordsReportComponent extends CipherReportComponent implemen modalService, passwordRepromptService, organizationService, + accountService, i18nService, syncService, ); diff --git a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts index 42d033dc4c2..fced6ff8f9f 100644 --- a/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/vault/components/collection-dialog/collection-dialog.component.ts @@ -5,6 +5,7 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from "@angula import { AbstractControl, FormBuilder, Validators } from "@angular/forms"; import { combineLatest, + firstValueFrom, map, Observable, of, @@ -24,8 +25,13 @@ import { CollectionResponse, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -102,7 +108,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { @Inject(DIALOG_DATA) private params: CollectionDialogParams, private formBuilder: FormBuilder, private dialogRef: DialogRef, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private groupService: GroupApiService, private collectionAdminService: CollectionAdminService, private i18nService: I18nService, @@ -110,6 +116,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, + private accountService: AccountService, ) { this.tabIndex = params.initialTab ?? CollectionDialogTabType.Info; } @@ -121,7 +128,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { this.formGroup.controls.selectedOrg.valueChanges .pipe(takeUntil(this.destroy$)) .subscribe((id) => this.loadOrg(id)); - this.organizations$ = this.organizationService.organizations$.pipe( + const userId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + this.organizations$ = this.organizationService.organizations$(userId).pipe( first(), map((orgs) => orgs @@ -139,8 +149,10 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { } async loadOrg(orgId: string) { + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const organization$ = this.organizationService - .get$(orgId) + .organizations$(userId) + .pipe(getOrganizationById(orgId)) .pipe(shareReplay({ refCount: true, bufferSize: 1 })); const groups$ = organization$.pipe( switchMap((organization) => { diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts index 6c126235234..ef79c7752df 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.spec.ts @@ -5,7 +5,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; @@ -24,7 +24,7 @@ import { AddEditComponentV2 } from "./add-edit-v2.component"; describe("AddEditComponentV2", () => { let component: AddEditComponentV2; let fixture: ComponentFixture; - let organizationService: MockProxy; + let organizationService: MockProxy; let policyService: MockProxy; let billingAccountProfileStateService: MockProxy; let activatedRoute: MockProxy; @@ -46,8 +46,8 @@ describe("AddEditComponentV2", () => { name: "Test Organization", } as Organization; - organizationService = mock(); - organizationService.organizations$ = of([mockOrganization]); + organizationService = mock(); + organizationService.organizations$.mockReturnValue(of([mockOrganization])); policyService = mock(); policyService.policyAppliesToActiveUser$.mockImplementation((policyType: PolicyType) => @@ -84,7 +84,7 @@ describe("AddEditComponentV2", () => { { provide: DialogService, useValue: dialogService }, { provide: CipherService, useValue: cipherService }, { provide: MessagingService, useValue: messagingService }, - { provide: OrganizationService, useValue: organizationService }, + { provide: vNextOrganizationService, useValue: organizationService }, { provide: Router, useValue: mock() }, { provide: ActivatedRoute, useValue: activatedRoute }, { provide: CollectionService, useValue: collectionService }, diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.ts b/apps/web/src/app/vault/individual-vault/add-edit.component.ts index 7038ffb898a..b5b2d9fa9c1 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit.component.ts @@ -8,7 +8,7 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { isCardExpired } from "@bitwarden/common/autofill/utils"; @@ -65,7 +65,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On protected messagingService: MessagingService, eventCollectionService: EventCollectionService, protected policyService: PolicyService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, logService: LogService, passwordRepromptService: PasswordRepromptService, dialogService: DialogService, diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts index 62798c21bca..2bbf2333137 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-share-dialog/bulk-share-dialog.component.ts @@ -5,9 +5,10 @@ import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { firstValueFrom, map } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -62,7 +63,7 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionService: CollectionService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private logService: LogService, private accountService: AccountService, ) { @@ -77,7 +78,8 @@ export class BulkShareDialogComponent implements OnInit, OnDestroy { this.nonShareableCount = this.ciphers.length - this.shareableCiphers.length; const allCollections = await this.collectionService.getAllDecrypted(); this.writeableCollections = allCollections.filter((c) => !c.readOnly); - this.organizations = await this.organizationService.getAll(); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + this.organizations = await firstValueFrom(this.organizationService.organizations$(userId)); if (this.organizationId == null && this.organizations.length > 0) { this.organizationId = this.organizations[0].id; } diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts index 6788471dd04..3a0bcedcc17 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/organization-options.component.ts @@ -1,7 +1,16 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; -import { combineLatest, map, Observable, of, Subject, switchMap, takeUntil } from "rxjs"; +import { + combineLatest, + firstValueFrom, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, +} from "rxjs"; import { OrganizationUserApiService, @@ -10,12 +19,14 @@ import { import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -59,7 +70,8 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { private userVerificationService: UserVerificationService, private toastService: ToastService, private configService: ConfigService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -67,16 +79,19 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { map((policies) => policies.filter((policy) => policy.type === PolicyType.ResetPassword)), ); + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); const managingOrg$ = this.configService .getFeatureFlag$(FeatureFlag.AccountDeprovisioning) .pipe( switchMap((isAccountDeprovisioningEnabled) => isAccountDeprovisioningEnabled - ? this.organizationService.organizations$.pipe( - map((organizations) => - organizations.find((o) => o.userIsManagedByOrganization === true), - ), - ) + ? this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => + organizations.find((o) => o.userIsManagedByOrganization === true), + ), + ) : of(null), ), ); 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 0386a20adbb..3e791f7477b 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 @@ -8,10 +8,11 @@ import { mock, MockProxy } from "jest-mock-extended"; import { firstValueFrom, ReplaySubject } from "rxjs"; import { CollectionService, CollectionView } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; @@ -26,7 +27,7 @@ import { VaultFilterService } from "./vault-filter.service"; describe("vault filter service", () => { let vaultFilterService: VaultFilterService; - let organizationService: MockProxy; + let organizationService: MockProxy; let folderService: MockProxy; let cipherService: MockProxy; let policyService: MockProxy; @@ -45,7 +46,7 @@ describe("vault filter service", () => { let collapsedGroupingsState: FakeActiveUserState; beforeEach(() => { - organizationService = mock(); + organizationService = mock(); folderService = mock(); cipherService = mock(); policyService = mock(); @@ -62,7 +63,7 @@ describe("vault filter service", () => { personalOwnershipPolicy = new ReplaySubject(1); singleOrgPolicy = new ReplaySubject(1); - organizationService.memberOrganizations$ = organizations; + organizationService.memberOrganizations$.mockReturnValue(organizations); folderService.folderViews$ = folderViews; collectionService.decryptedCollections$ = collectionViews; policyService.policyAppliesToActiveUser$ @@ -81,6 +82,7 @@ describe("vault filter service", () => { i18nService, stateProvider, collectionService, + accountService as AccountService, ); collapsedGroupingsState = stateProvider.activeUser.getFake(COLLAPSED_GROUPINGS); }); 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 c4ac3dc2d70..7ed46ee0115 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 @@ -17,10 +17,11 @@ import { CollectionService, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { 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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -45,8 +46,12 @@ const NestingDelimiter = "/"; @Injectable() export class VaultFilterService implements VaultFilterServiceAbstraction { + memberOrganizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => this.organizationService.memberOrganizations$(account?.id)), + ); + organizationTree$: Observable> = combineLatest([ - this.organizationService.memberOrganizations$, + this.memberOrganizations$, this.policyService.policyAppliesToActiveUser$(PolicyType.SingleOrg), this.policyService.policyAppliesToActiveUser$(PolicyType.PersonalOwnership), ]).pipe( @@ -88,13 +93,14 @@ export class VaultFilterService implements VaultFilterServiceAbstraction { this.collapsedGroupingsState.state$.pipe(map((c) => new Set(c))); constructor( - protected organizationService: OrganizationService, + protected organizationService: vNextOrganizationService, protected folderService: FolderService, protected cipherService: CipherService, protected policyService: PolicyService, protected i18nService: I18nService, protected stateProvider: StateProvider, protected collectionService: CollectionService, + protected accountService: AccountService, ) {} async getCollectionNodeFromTree(id: string) { 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..ea955bbe886 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -46,7 +46,10 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; 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 { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -190,7 +193,11 @@ export class VaultComponent implements OnInit, OnDestroy { private hasSubscription$ = new BehaviorSubject(false); private vaultItemDialogRef?: DialogRef | undefined; - private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( + private organizations$ = this.accountService.activeAccount$ + .pipe(map((a) => a?.id)) + .pipe(switchMap((id) => this.organizationService.organizations$(id))); + + private readonly unpaidSubscriptionDialog$ = this.organizations$.pipe( filter((organizations) => organizations.length === 1), map(([organization]) => organization), switchMap((organization) => @@ -209,9 +216,8 @@ export class VaultComponent implements OnInit, OnDestroy { ), ), ); - protected organizationsPaymentStatus$: Observable = combineLatest([ - this.organizationService.organizations$.pipe( + this.organizations$.pipe( map( (organizations) => organizations?.filter((org) => org.isOwner && org.canViewBillingHistory) ?? [], @@ -256,7 +262,7 @@ export class VaultComponent implements OnInit, OnDestroy { private platformUtilsService: PlatformUtilsService, private broadcasterService: BroadcasterService, private ngZone: NgZone, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private vaultFilterService: VaultFilterService, private routedVaultFilterService: RoutedVaultFilterService, private routedVaultFilterBridgeService: RoutedVaultFilterBridgeService, @@ -469,7 +475,7 @@ export class VaultComponent implements OnInit, OnDestroy { filter$, this.billingAccountProfileStateService.hasPremiumFromAnySource$, allCollections$, - this.organizationService.organizations$, + this.organizations$, ciphers$, collections$, selectedCollection$, @@ -614,7 +620,9 @@ export class VaultComponent implements OnInit, OnDestroy { this.messagingService.send("premiumRequired"); return; } else if (cipher.organizationId != null) { - const org = await this.organizationService.get(cipher.organizationId); + const org = await firstValueFrom( + this.organizations$.pipe(getOrganizationById(cipher.organizationId)), + ); if (org != null && (org.maxStorageGb == null || org.maxStorageGb === 0)) { this.messagingService.send("upgradeOrganization", { organizationId: cipher.organizationId, @@ -928,7 +936,9 @@ export class VaultComponent implements OnInit, OnDestroy { } async deleteCollection(collection: CollectionView): Promise { - const organization = await this.organizationService.get(collection.organizationId); + const organization = await firstValueFrom( + this.organizations$.pipe(getOrganizationById(collection.organizationId)), + ); if (!collection.canDelete(organization)) { this.showMissingPermissionsError(); return; @@ -1093,9 +1103,7 @@ export class VaultComponent implements OnInit, OnDestroy { .filter((i) => i.cipher === undefined) .map((i) => i.collection.organizationId); const orgs = await firstValueFrom( - this.organizationService.organizations$.pipe( - map((orgs) => orgs.filter((o) => orgIds.includes(o.id))), - ), + this.organizations$.pipe(map((orgs) => orgs.filter((o) => orgIds.includes(o.id)))), ); await this.bulkDelete(ciphers, collections, orgs); } diff --git a/apps/web/src/app/vault/individual-vault/view.component.spec.ts b/apps/web/src/app/vault/individual-vault/view.component.spec.ts index b26c55d46e8..23733c5a71f 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.spec.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.spec.ts @@ -1,15 +1,20 @@ import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; +import { AccountService } from "@bitwarden/common/auth/abstractions/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"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; +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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -38,6 +43,8 @@ describe("ViewComponent", () => { const mockParams: ViewCipherDialogParams = { cipher: mockCipher, }; + const userId = Utils.newGuid() as UserId; + const accountService: FakeAccountService = mockAccountServiceWith(userId); beforeEach(async () => { await TestBed.configureTestingModule({ @@ -50,10 +57,14 @@ describe("ViewComponent", () => { { provide: CipherService, useValue: mock() }, { provide: ToastService, useValue: mock() }, { provide: MessagingService, useValue: mock() }, + { + provide: AccountService, + useValue: accountService, + }, { provide: LogService, useValue: mock() }, { - provide: OrganizationService, - useValue: { get: jest.fn().mockResolvedValue(mockOrganization) }, + provide: vNextOrganizationService, + useValue: { organizations$: jest.fn().mockReturnValue(of([mockOrganization])) }, }, { provide: CollectionService, useValue: mock() }, { provide: FolderService, useValue: mock() }, 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..072cab53fe1 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,13 @@ 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; @@ -92,8 +94,9 @@ export class ViewComponent implements OnInit { private logService: LogService, private cipherService: CipherService, private toastService: ToastService, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, private cipherAuthorizationService: CipherAuthorizationService, + private accountService: AccountService, ) {} /** @@ -103,8 +106,17 @@ export class ViewComponent implements OnInit { this.cipher = this.params.cipher; this.collections = this.params.collections; this.cipherTypeString = this.getCipherViewTypeString(); + + const userId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); + if (this.cipher.organizationId) { - this.organization = await this.organizationService.get(this.cipher.organizationId); + this.organization = await firstValueFrom( + this.organizationService + .organizations$(userId) + .pipe( + map((organizations) => organizations.find((o) => o.id === this.cipher.organizationId)), + ), + ); } this.canDeleteCipher$ = this.cipherAuthorizationService.canDeleteCipher$(this.cipher, [ 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..a6d70394529 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 @@ -7,7 +7,7 @@ import { CollectionService } from "@bitwarden/admin-console/common"; 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; @@ -53,7 +53,7 @@ export class AddEditComponent extends BaseAddEditComponent { policyService: PolicyService, logService: LogService, passwordRepromptService: PasswordRepromptService, - organizationService: OrganizationService, + organizationService: vNextOrganizationService, dialogService: DialogService, datePipe: DatePipe, configService: ConfigService, diff --git a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts index 0fc7b6a31aa..f6526e881db 100644 --- a/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/vault/org-vault/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -10,8 +10,12 @@ import { OrganizationUserApiService, CollectionView, } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { + getOrganizationById, + vNextOrganizationService, +} from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService } from "@bitwarden/components"; @@ -62,7 +66,8 @@ export class BulkCollectionsDialogComponent implements OnDestroy { @Inject(DIALOG_DATA) private params: BulkCollectionsDialogParams, private dialogRef: DialogRef, private formBuilder: FormBuilder, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, + private accountService: AccountService, private groupService: GroupApiService, private organizationUserApiService: OrganizationUserApiService, private platformUtilsService: PlatformUtilsService, @@ -70,7 +75,13 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private collectionAdminService: CollectionAdminService, ) { this.numCollections = this.params.collections.length; - const organization$ = this.organizationService.get$(this.params.organizationId); + const organization$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService + .organizations$(account?.id) + .pipe(getOrganizationById(this.params.organizationId)), + ), + ); const groups$ = organization$.pipe( switchMap((organization) => { if (!organization.useGroups) { 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..77e2af27f57 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 @@ -3,13 +3,15 @@ import { BehaviorSubject } from "rxjs"; import { CollectionAdminService, CollectionAdminView } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.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 { 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 { Account } from "../../../../../../../libs/importer/src/importers/lastpass/access/models"; import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/services/routed-vault-filter.service"; import { AdminConsoleCipherFormConfigService } from "./admin-console-cipher-form-config.service"; @@ -50,8 +52,7 @@ describe("AdminConsoleCipherFormConfigService", () => { readOnly: false, } as CollectionAdminView; - const organization$ = new BehaviorSubject(testOrg as Organization); - const organizations$ = new BehaviorSubject([testOrg, testOrg2] as Organization[]); + const orgs$ = new BehaviorSubject([testOrg, testOrg2] as Organization[]); const getCipherAdmin = jest.fn().mockResolvedValue(null); const getCipher = jest.fn().mockResolvedValue(null); @@ -65,7 +66,7 @@ describe("AdminConsoleCipherFormConfigService", () => { TestBed.configureTestingModule({ providers: [ AdminConsoleCipherFormConfigService, - { provide: OrganizationService, useValue: { get$: () => organization$, organizations$ } }, + { provide: vNextOrganizationService, useValue: { organizations$: () => orgs$ } }, { provide: CollectionAdminService, useValue: { getAll: () => Promise.resolve([collection, collection2]) }, @@ -80,6 +81,10 @@ describe("AdminConsoleCipherFormConfigService", () => { }, { provide: ApiService, useValue: { getCipherAdmin } }, { provide: CipherService, useValue: { get: getCipher } }, + { + provide: AccountService, + useValue: { activeAccount$: new BehaviorSubject(new Account()) }, + }, ], }); adminConsoleConfigService = TestBed.inject(AdminConsoleCipherFormConfigService); 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..c80b7dfd9eb 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 @@ -5,10 +5,11 @@ import { combineLatest, filter, firstValueFrom, map, switchMap } from "rxjs"; import { CollectionAdminService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.organization.service.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"; @@ -26,11 +27,12 @@ import { RoutedVaultFilterService } from "../../individual-vault/vault-filter/se @Injectable() export class AdminConsoleCipherFormConfigService implements CipherFormConfigService { private policyService: PolicyService = inject(PolicyService); - private organizationService: OrganizationService = inject(OrganizationService); + private organizationService: vNextOrganizationService = inject(vNextOrganizationService); private routedVaultFilterService: RoutedVaultFilterService = inject(RoutedVaultFilterService); 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) @@ -41,12 +43,16 @@ export class AdminConsoleCipherFormConfigService implements CipherFormConfigServ filter((filter) => filter !== undefined), ); - private allOrganizations$ = this.organizationService.organizations$.pipe( - map((orgs) => { - return orgs.filter( - (o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed, - ); - }), + private allOrganizations$ = this.accountService.activeAccount$.pipe( + switchMap((account) => + this.organizationService.organizations$(account?.id).pipe( + map((orgs) => { + return orgs.filter( + (o) => o.isMember && o.enabled && o.status === OrganizationUserStatusType.Confirmed, + ); + }), + ), + ), ); private organization$ = combineLatest([this.allOrganizations$, this.organizationId$]).pipe( diff --git a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts index c4ac9d73df7..d4497378019 100644 --- a/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts +++ b/apps/web/src/app/vault/org-vault/vault-filter/vault-filter.service.ts @@ -2,8 +2,9 @@ import { Injectable, OnDestroy } from "@angular/core"; import { map, Observable, ReplaySubject, Subject } from "rxjs"; import { CollectionAdminView, CollectionService } from "@bitwarden/admin-console/common"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; +import { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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 { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -25,13 +26,14 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest ); constructor( - organizationService: OrganizationService, + organizationService: vNextOrganizationService, folderService: FolderService, cipherService: CipherService, policyService: PolicyService, i18nService: I18nService, stateProvider: StateProvider, collectionService: CollectionService, + accountService: AccountService, ) { super( organizationService, @@ -41,6 +43,7 @@ export class VaultFilterService extends BaseVaultFilterService implements OnDest i18nService, stateProvider, collectionService, + accountService, ); } diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts index d28148c49dc..7f118a48db3 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts @@ -11,7 +11,6 @@ import { Unassigned, } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { ProductTierType } from "@bitwarden/common/billing/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -90,7 +89,6 @@ export class VaultHeaderComponent implements OnInit { @Output() searchTextChanged = new EventEmitter(); protected CollectionDialogTabType = CollectionDialogTabType; - protected organizations$ = this.organizationService.organizations$; /** * Whether the extension refresh feature flag is enabled. @@ -101,7 +99,6 @@ export class VaultHeaderComponent implements OnInit { protected CipherType = CipherType; constructor( - private organizationService: OrganizationService, private i18nService: I18nService, private dialogService: DialogService, private collectionAdminService: CollectionAdminService, 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..b9064f3edf5 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -48,8 +48,9 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; 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 { vNextOrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/vnext.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"; @@ -211,19 +212,24 @@ export class VaultComponent implements OnInit, OnDestroy { private resellerManagedOrgAlert: boolean; private vaultItemDialogRef?: DialogRef | undefined; - private readonly unpaidSubscriptionDialog$ = this.organizationService.organizations$.pipe( - filter((organizations) => organizations.length === 1), - map(([organization]) => organization), - switchMap((organization) => - from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( - tap((organizationMetaData) => { - this.hasSubscription$.next(organizationMetaData.hasSubscription); - }), - switchMap((organizationMetaData) => - from( - this.trialFlowService.handleUnpaidSubscriptionDialog( - organization, - organizationMetaData, + private readonly unpaidSubscriptionDialog$ = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((id) => + this.organizationService.organizations$(id).pipe( + filter((organizations) => organizations.length === 1), + map(([organization]) => organization), + switchMap((organization) => + from(this.billingApiService.getOrganizationBillingMetadata(organization.id)).pipe( + tap((organizationMetaData) => { + this.hasSubscription$.next(organizationMetaData.hasSubscription); + }), + switchMap((organizationMetaData) => + from( + this.trialFlowService.handleUnpaidSubscriptionDialog( + organization, + organizationMetaData, + ), + ), ), ), ), @@ -233,7 +239,7 @@ export class VaultComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, - private organizationService: OrganizationService, + private organizationService: vNextOrganizationService, protected vaultFilterService: VaultFilterService, private routedVaultFilterBridgeService: RoutedVaultFilterBridgeService, private routedVaultFilterService: RoutedVaultFilterService, @@ -266,6 +272,7 @@ export class VaultComponent implements OnInit, OnDestroy { protected billingApiService: BillingApiServiceAbstraction, private organizationBillingService: OrganizationBillingServiceAbstraction, private resellerWarningService: ResellerWarningService, + private accountService: AccountService, ) {} async ngOnInit() { @@ -290,10 +297,19 @@ export class VaultComponent implements OnInit, OnDestroy { distinctUntilChanged(), ); - const organization$ = organizationId$.pipe( - switchMap((organizationId) => this.organizationService.get$(organizationId)), - takeUntil(this.destroy$), - shareReplay({ refCount: false, bufferSize: 1 }), + const organization$ = this.accountService.activeAccount$.pipe( + map((account) => account?.id), + switchMap((id) => + organizationId$.pipe( + switchMap((organizationId) => + this.organizationService + .organizations$(id) + .pipe(map((organizations) => organizations.find((org) => org.id === organizationId))), + ), + takeUntil(this.destroy$), + shareReplay({ refCount: false, bufferSize: 1 }), + ), + ), ); const firstSetup$ = combineLatest([organization$, this.route.queryParams]).pipe(