From 3e9fb2009ebb252182af08b369e4828cb441bf59 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:50:34 -0500 Subject: [PATCH 01/28] [PM-10934] Remove last form-field bottom border (#10751) * match API of new CL FormField component * remove readonly border for additional options component * remove readonly border for last autofill option * remove readonly border for last custom-field form field * remove readonly border for when collection,org or folder is available * add `ReadOnlyCipherCardComponent` to handle readonly border * remove readonly border for the last identity form field * remove readonly border for the last card form field * remove readonly border for the last login form field * remove unneeded true value --- .../src/form-field/form-field.component.ts | 8 ++++++ .../additional-options.component.html | 2 +- .../autofill-options-view.component.html | 6 ++++- .../card-details-view.component.html | 4 +-- .../card-details-view.component.ts | 3 +++ .../custom-fields-v2.component.html | 6 ++--- .../item-details-v2.component.html | 3 +++ .../login-credentials-view.component.html | 4 +-- .../login-credentials-view.component.ts | 2 ++ .../read-only-cipher-card.component.html | 3 +++ .../read-only-cipher-card.component.ts | 26 +++++++++++++++++++ .../view-identity-sections.component.html | 12 ++++----- .../view-identity-sections.component.ts | 3 +++ 13 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.html create mode 100644 libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 6fcb4090ddd..1e364115a6f 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -1,6 +1,7 @@ import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { AfterContentChecked, + booleanAttribute, Component, ContentChild, ContentChildren, @@ -38,6 +39,13 @@ export class BitFormFieldComponent implements AfterContentChecked { return this._disableMargin; } + /** + * NOTE: Placeholder to match the API of the form-field component in the `ps/extension` branch, + * no functionality is implemented as of now. + */ + @Input({ transform: booleanAttribute }) + disableReadOnlyBorder = false; + @HostBinding("class") get classList() { return ["tw-block"].concat(this.disableMargin ? [] : ["tw-mb-6"]); diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.html b/libs/vault/src/cipher-view/additional-options/additional-options.component.html index 6f254b8c729..0913629ad9e 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.html +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.html @@ -3,7 +3,7 @@

{{ "additionalOptions" | i18n }}

- + {{ "note" | i18n }} - + diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts index 028417faf16..6ab2795afd9 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.ts +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.ts @@ -13,6 +13,8 @@ import { IconButtonModule, } from "@bitwarden/components"; +import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; + @Component({ selector: "app-card-details-view", templateUrl: "card-details-view.component.html", @@ -26,6 +28,7 @@ import { TypographyModule, FormFieldModule, IconButtonModule, + ReadOnlyCipherCardComponent, ], }) export class CardDetailsComponent { diff --git a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html index d4c29cf262b..96cb63fe39b 100644 --- a/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html +++ b/libs/vault/src/cipher-view/custom-fields/custom-fields-v2.component.html @@ -8,7 +8,7 @@ *ngFor="let field of fields; let last = last" [ngClass]="{ 'tw-mb-4': !last }" > - + {{ field.name }} - + {{ field.name }} @@ -45,7 +45,7 @@ /> {{ field.name }} - + {{ "linked" | i18n }}: {{ field.name }} {{ "itemName" | i18n }} diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 6913c34ee5d..17d02658c48 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -2,7 +2,7 @@

{{ "loginCredentials" | i18n }}

- + {{ "username" | i18n }} @@ -132,5 +132,5 @@ class="disabled:tw-cursor-default" > - + diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 3973a666847..6f572f31e87 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -19,6 +19,7 @@ import { } from "@bitwarden/components"; import { BitTotpCountdownComponent } from "../../components/totp-countdown/totp-countdown.component"; +import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; type TotpCodeValues = { totpCode: string; @@ -41,6 +42,7 @@ type TotpCodeValues = { BadgeModule, ColorPasswordModule, BitTotpCountdownComponent, + ReadOnlyCipherCardComponent, ], }) export class LoginCredentialsViewComponent { diff --git a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.html b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.html new file mode 100644 index 00000000000..65061e818cb --- /dev/null +++ b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.html @@ -0,0 +1,3 @@ + + + diff --git a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts new file mode 100644 index 00000000000..ed16f3a7cc0 --- /dev/null +++ b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts @@ -0,0 +1,26 @@ +import { AfterViewInit, Component, ContentChildren, QueryList } from "@angular/core"; + +import { CardComponent, BitFormFieldComponent } from "@bitwarden/components"; + +@Component({ + selector: "read-only-cipher-card", + templateUrl: "./read-only-cipher-card.component.html", + standalone: true, + imports: [CardComponent], +}) +/** + * A thin wrapper around the `bit-card` component that disables the bottom border for the last form field. + */ +export class ReadOnlyCipherCardComponent implements AfterViewInit { + @ContentChildren(BitFormFieldComponent) formFields: QueryList; + + ngAfterViewInit(): void { + // Disable the bottom border for the last form field + if (this.formFields.last) { + // Delay model update until next change detection cycle + setTimeout(() => { + this.formFields.last.disableReadOnlyBorder = true; + }); + } + } +} diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html index d12a729f99a..29ccd5daa6b 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.html @@ -3,7 +3,7 @@

{{ "personalDetails" | i18n }}

- + {{ "name" | i18n }} @@ -43,7 +43,7 @@ [valueLabel]="'company' | i18n" > - + @@ -51,7 +51,7 @@

{{ "identification" | i18n }}

- + {{ "ssn" | i18n }} @@ -111,7 +111,7 @@ [valueLabel]="'licenseNumber' | i18n" > - +
@@ -119,7 +119,7 @@

{{ "contactInfo" | i18n }}

- + {{ "email" | i18n }} @@ -166,5 +166,5 @@ [valueLabel]="'address' | i18n" > - +
diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts index 0fd2c292952..0f3a9f89712 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts @@ -12,6 +12,8 @@ import { TypographyModule, } from "@bitwarden/components"; +import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only-cipher-card.component"; + @Component({ standalone: true, selector: "app-view-identity-sections", @@ -25,6 +27,7 @@ import { TypographyModule, FormFieldModule, IconButtonModule, + ReadOnlyCipherCardComponent, ], }) export class ViewIdentitySectionsComponent implements OnInit { From c5c8a0dd5e76da730cfb633ed3a1b8a02520f561 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:51:53 -0500 Subject: [PATCH 02/28] swap account font color for muted (#10883) --- apps/web/src/app/layouts/header/web-header.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/layouts/header/web-header.component.html b/apps/web/src/app/layouts/header/web-header.component.html index c8cbd9f8dab..7cba19b29ad 100644 --- a/apps/web/src/app/layouts/header/web-header.component.html +++ b/apps/web/src/app/layouts/header/web-header.component.html @@ -36,13 +36,13 @@
{{ "loggedInAs" | i18n }} - + {{ account | userName }}
From c73ee8812639d0183429e43718249bbba411502b Mon Sep 17 00:00:00 2001 From: Merissa Weinstein Date: Wed, 4 Sep 2024 10:52:22 -0500 Subject: [PATCH 03/28] turn enableCipherKeyEncryption flag off (#10621) --- apps/browser/config/base.json | 2 +- apps/browser/config/development.json | 2 +- apps/browser/config/production.json | 2 +- apps/cli/config/development.json | 2 +- apps/cli/config/production.json | 2 +- apps/desktop/config/base.json | 2 +- apps/desktop/config/development.json | 2 +- apps/desktop/config/production.json | 2 +- apps/web/config/base.json | 2 +- apps/web/config/cloud.json | 2 +- apps/web/config/development.json | 2 +- apps/web/config/euprd.json | 2 +- apps/web/config/qa.json | 2 +- apps/web/config/selfhosted.json | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/browser/config/base.json b/apps/browser/config/base.json index b6f24bf9ae3..6c428c43d26 100644 --- a/apps/browser/config/base.json +++ b/apps/browser/config/base.json @@ -2,7 +2,7 @@ "devFlags": {}, "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true, + "enableCipherKeyEncryption": false, "accountSwitching": false } } diff --git a/apps/browser/config/development.json b/apps/browser/config/development.json index 950c5372d8f..e0925ebecc9 100644 --- a/apps/browser/config/development.json +++ b/apps/browser/config/development.json @@ -7,7 +7,7 @@ }, "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true, + "enableCipherKeyEncryption": false, "accountSwitching": true } } diff --git a/apps/browser/config/production.json b/apps/browser/config/production.json index 64c6cb92a3b..027003f6c75 100644 --- a/apps/browser/config/production.json +++ b/apps/browser/config/production.json @@ -1,6 +1,6 @@ { "flags": { - "enableCipherKeyEncryption": true, + "enableCipherKeyEncryption": false, "accountSwitching": true } } diff --git a/apps/cli/config/development.json b/apps/cli/config/development.json index bc06f69d657..f57c3d9bc38 100644 --- a/apps/cli/config/development.json +++ b/apps/cli/config/development.json @@ -1,5 +1,5 @@ { "flags": { - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/cli/config/production.json b/apps/cli/config/production.json index bc06f69d657..f57c3d9bc38 100644 --- a/apps/cli/config/production.json +++ b/apps/cli/config/production.json @@ -1,5 +1,5 @@ { "flags": { - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/desktop/config/base.json b/apps/desktop/config/base.json index 7f18c63878b..7a8659feffe 100644 --- a/apps/desktop/config/base.json +++ b/apps/desktop/config/base.json @@ -1,6 +1,6 @@ { "devFlags": {}, "flags": { - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/desktop/config/development.json b/apps/desktop/config/development.json index 7f18c63878b..7a8659feffe 100644 --- a/apps/desktop/config/development.json +++ b/apps/desktop/config/development.json @@ -1,6 +1,6 @@ { "devFlags": {}, "flags": { - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/desktop/config/production.json b/apps/desktop/config/production.json index bc06f69d657..f57c3d9bc38 100644 --- a/apps/desktop/config/production.json +++ b/apps/desktop/config/production.json @@ -1,5 +1,5 @@ { "flags": { - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/web/config/base.json b/apps/web/config/base.json index b9102a769d7..5dc03a4633d 100644 --- a/apps/web/config/base.json +++ b/apps/web/config/base.json @@ -12,6 +12,6 @@ }, "flags": { "showPasswordless": false, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/web/config/cloud.json b/apps/web/config/cloud.json index c8ba07e755e..3faa2926929 100644 --- a/apps/web/config/cloud.json +++ b/apps/web/config/cloud.json @@ -18,6 +18,6 @@ }, "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/web/config/development.json b/apps/web/config/development.json index 3fcd8641b32..44391a7450d 100644 --- a/apps/web/config/development.json +++ b/apps/web/config/development.json @@ -21,7 +21,7 @@ ], "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false }, "devFlags": {} } diff --git a/apps/web/config/euprd.json b/apps/web/config/euprd.json index 2d554e57043..72f0c1857d9 100644 --- a/apps/web/config/euprd.json +++ b/apps/web/config/euprd.json @@ -12,6 +12,6 @@ }, "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/web/config/qa.json b/apps/web/config/qa.json index f03d47fe4ee..ac36b107846 100644 --- a/apps/web/config/qa.json +++ b/apps/web/config/qa.json @@ -28,6 +28,6 @@ ], "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } diff --git a/apps/web/config/selfhosted.json b/apps/web/config/selfhosted.json index 121f59ba0b3..7e916a11169 100644 --- a/apps/web/config/selfhosted.json +++ b/apps/web/config/selfhosted.json @@ -8,6 +8,6 @@ }, "flags": { "showPasswordless": true, - "enableCipherKeyEncryption": true + "enableCipherKeyEncryption": false } } From fdeac584697f40fd6fb68bf1175dbe8fd83179fc Mon Sep 17 00:00:00 2001 From: Will Martin Date: Wed, 4 Sep 2024 12:12:47 -0400 Subject: [PATCH 04/28] [CL-312] fix dialog scroll blocking + virtual scroll (#9606) --- libs/components/src/dialog/dialog.service.ts | 26 +++++++- .../dialog-virtual-scroll-block.component.ts | 61 +++++++++++++++++++ .../kitchen-sink/kitchen-sink.stories.ts | 33 +++++++++- 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 9488da4ac6d..62a56d20af0 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -5,7 +5,7 @@ import { DialogRef, DIALOG_SCROLL_STRATEGY, } from "@angular/cdk/dialog"; -import { ComponentType, Overlay, OverlayContainer } from "@angular/cdk/overlay"; +import { ComponentType, Overlay, OverlayContainer, ScrollStrategy } from "@angular/cdk/overlay"; import { Inject, Injectable, @@ -25,12 +25,35 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; import { SimpleDialogOptions, Translation } from "./simple-dialog/types"; +/** + * The default `BlockScrollStrategy` does not work well with virtual scrolling. + * + * https://github.com/angular/components/issues/7390 + */ +class CustomBlockScrollStrategy implements ScrollStrategy { + enable() { + document.body.classList.add("tw-overflow-hidden"); + } + + disable() { + document.body.classList.remove("tw-overflow-hidden"); + } + + /** Noop */ + attach() {} + + /** Noop */ + detach() {} +} + @Injectable() export class DialogService extends Dialog implements OnDestroy { private _destroy$ = new Subject(); private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"]; + private defaultScrollStrategy = new CustomBlockScrollStrategy(); + constructor( /** Parent class constructor */ _overlay: Overlay, @@ -73,6 +96,7 @@ export class DialogService extends Dialog implements OnDestroy { ): DialogRef { config = { backdropClass: this.backDropClasses, + scrollStrategy: this.defaultScrollStrategy, ...config, }; diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts new file mode 100644 index 00000000000..a867d9cdf53 --- /dev/null +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -0,0 +1,61 @@ +import { ScrollingModule } from "@angular/cdk/scrolling"; +import { Component, OnInit } from "@angular/core"; + +import { DialogModule, DialogService } from "../../../dialog"; +import { IconButtonModule } from "../../../icon-button"; +import { SectionComponent } from "../../../section"; +import { TableDataSource, TableModule } from "../../../table"; + +@Component({ + selector: "dialog-virtual-scroll-block", + standalone: true, + imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], + template: ` + + + + + Id + Name + Options + + + + + {{ r.id }} + {{ r.name }} + + + + + + + + `, +}) +export class DialogVirtualScrollBlockComponent implements OnInit { + constructor(public dialogService: DialogService) {} + + protected dataSource = new TableDataSource<{ id: number; name: string; other: string }>(); + + ngOnInit(): void { + this.dataSource.data = [...Array(100).keys()].map((i) => ({ + id: i, + name: `name-${i}`, + other: `other-${i}`, + })); + } + + async openDefaultDialog() { + await this.dialogService.openSimpleDialog({ + type: "info", + title: "Foo", + content: "Bar", + }); + } +} diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index fa78f04d236..203c510f814 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -8,7 +8,15 @@ import { componentWrapperDecorator, moduleMetadata, } from "@storybook/angular"; -import { userEvent, getAllByRole, getByRole, getByLabelText, fireEvent } from "@storybook/test"; +import { + userEvent, + getAllByRole, + getByRole, + getByLabelText, + fireEvent, + getByText, + getAllByLabelText, +} from "@storybook/test"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -16,6 +24,7 @@ import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; +import { DialogVirtualScrollBlockComponent } from "./components/dialog-virtual-scroll-block.component"; import { KitchenSinkForm } from "./components/kitchen-sink-form.component"; import { KitchenSinkMainComponent } from "./components/kitchen-sink-main.component"; import { KitchenSinkTable } from "./components/kitchen-sink-table.component"; @@ -64,7 +73,9 @@ export default { skipToContent: "Skip to content", submenu: "submenu", toggleCollapse: "toggle collapse", - toggleSideNavigation: "toggle side navigation", + toggleSideNavigation: "Toggle side navigation", + yes: "Yes", + no: "No", }); }, }, @@ -78,6 +89,7 @@ export default { [ { path: "", redirectTo: "bitwarden", pathMatch: "full" }, { path: "bitwarden", component: KitchenSinkMainComponent }, + { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, ], { useHash: true }, ), @@ -100,6 +112,7 @@ export const Default: Story = { + @@ -165,3 +178,19 @@ export const EmptyTab: Story = { await userEvent.click(emptyTab); }, }; + +export const VirtualScrollBlockingDialog: Story = { + ...Default, + play: async (context) => { + const canvas = context.canvasElement; + const navItem = getByText(canvas, "Virtual Scroll"); + await userEvent.click(navItem); + + const htmlEl = canvas.ownerDocument.documentElement; + htmlEl.scrollTop = 2000; + + const dialogButton = getAllByLabelText(canvas, "Options")[0]; + + await userEvent.click(dialogButton); + }, +}; From 6edc3edb9a10d7489e6de91e2cfd0857b1c843f0 Mon Sep 17 00:00:00 2001 From: Alex Morask <144709477+amorask-bitwarden@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:30:47 -0400 Subject: [PATCH 05/28] [AC-2960] Create new adjust-storage-dialog component without payment.component (#10793) * Add adjust-storage-dialog-v2.component * (No Logic) Rename old adjust-storage.component to adjust-storage-dialog.component * (No Logic) Move existing adjust-storage-dialog.component into new adjust-storage-dialog folder * Use adjust-storage-dialog-v2.component in adjustStorage methods when FF is on --- .../individual/user-subscription.component.ts | 49 +++++++-- ...ganization-subscription-cloud.component.ts | 51 +++++++-- .../adjust-storage-dialog-v2.component.html | 34 ++++++ .../adjust-storage-dialog-v2.component.ts | 104 ++++++++++++++++++ .../adjust-storage-dialog.component.html} | 0 .../adjust-storage-dialog.component.ts} | 8 +- .../billing/shared/billing-shared.module.ts | 8 +- 7 files changed, 225 insertions(+), 29 deletions(-) create mode 100644 apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html create mode 100644 apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts rename apps/web/src/app/billing/shared/{adjust-storage.component.html => adjust-storage-dialog/adjust-storage-dialog.component.html} (100%) rename apps/web/src/app/billing/shared/{adjust-storage.component.ts => adjust-storage-dialog/adjust-storage-dialog.component.ts} (95%) diff --git a/apps/web/src/app/billing/individual/user-subscription.component.ts b/apps/web/src/app/billing/individual/user-subscription.component.ts index 2d02cbc5bdf..113d2feabe4 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -5,6 +5,8 @@ import { firstValueFrom, lastValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { SubscriptionResponse } from "@bitwarden/common/billing/models/response/subscription.response"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -12,10 +14,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { + AdjustStorageDialogV2Component, + AdjustStorageDialogV2ResultType, +} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; import { AdjustStorageDialogResult, openAdjustStorageDialog, -} from "../shared/adjust-storage.component"; +} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -38,6 +44,10 @@ export class UserSubscriptionComponent implements OnInit { cancelPromise: Promise; reinstatePromise: Promise; + protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( + FeatureFlag.AC2476_DeprecateStripeSourcesAPI, + ); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -49,6 +59,7 @@ export class UserSubscriptionComponent implements OnInit { private environmentService: EnvironmentService, private billingAccountProfileStateService: BillingAccountProfileStateService, private toastService: ToastService, + private configService: ConfigService, ) { this.selfHosted = platformUtilsService.isSelfHost(); } @@ -150,15 +161,33 @@ export class UserSubscriptionComponent implements OnInit { }; adjustStorage = async (add: boolean) => { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: 4, - add: add, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); + const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + + if (deprecateStripeSourcesAPI) { + const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { + data: { + price: 4, + cadence: "year", + type: add ? "Add" : "Remove", + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result === AdjustStorageDialogV2ResultType.Submitted) { + await this.load(); + } + } else { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: 4, + add: add, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } } }; 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 f28933a4ecc..2a565face75 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 @@ -18,10 +18,14 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; +import { + AdjustStorageDialogV2Component, + AdjustStorageDialogV2ResultType, +} from "../shared/adjust-storage-dialog/adjust-storage-dialog-v2.component"; import { AdjustStorageDialogResult, openAdjustStorageDialog, -} from "../shared/adjust-storage.component"; +} from "../shared/adjust-storage-dialog/adjust-storage-dialog.component"; import { OffboardingSurveyDialogResultType, openOffboardingSurvey, @@ -71,6 +75,10 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy FeatureFlag.EnableUpgradePasswordManagerSub, ); + protected deprecateStripeSourcesAPI$ = this.configService.getFeatureFlag$( + FeatureFlag.AC2476_DeprecateStripeSourcesAPI, + ); + constructor( private apiService: ApiService, private platformUtilsService: PlatformUtilsService, @@ -458,17 +466,36 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy adjustStorage = (add: boolean) => { return async () => { - const dialogRef = openAdjustStorageDialog(this.dialogService, { - data: { - storageGbPrice: this.storageGbPrice, - add: add, - organizationId: this.organizationId, - interval: this.billingInterval, - }, - }); - const result = await lastValueFrom(dialogRef.closed); - if (result === AdjustStorageDialogResult.Adjusted) { - await this.load(); + const deprecateStripeSourcesAPI = await firstValueFrom(this.deprecateStripeSourcesAPI$); + + if (deprecateStripeSourcesAPI) { + const dialogRef = AdjustStorageDialogV2Component.open(this.dialogService, { + data: { + price: this.storageGbPrice, + cadence: this.billingInterval, + type: add ? "Add" : "Remove", + organizationId: this.organizationId, + }, + }); + + const result = await lastValueFrom(dialogRef.closed); + + if (result === AdjustStorageDialogV2ResultType.Submitted) { + await this.load(); + } + } else { + const dialogRef = openAdjustStorageDialog(this.dialogService, { + data: { + storageGbPrice: this.storageGbPrice, + add: add, + organizationId: this.organizationId, + interval: this.billingInterval, + }, + }); + const result = await lastValueFrom(dialogRef.closed); + if (result === AdjustStorageDialogResult.Adjusted) { + await this.load(); + } } }; }; diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html new file mode 100644 index 00000000000..7b74379acb6 --- /dev/null +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.html @@ -0,0 +1,34 @@ +
+ + +

{{ body }}

+
+ + {{ storageFieldLabel }} + + + + {{ "total" | i18n }} + {{ this.formGroup.value.storage }} GB × {{ this.price | currency: "$" }} = + {{ this.price * this.formGroup.value.storage | currency: "$" }} / + {{ this.cadence | i18n }} + + +
+
+ + + + +
+
diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts new file mode 100644 index 00000000000..23d5e46fa1b --- /dev/null +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog-v2.component.ts @@ -0,0 +1,104 @@ +import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; +import { Component, Inject } from "@angular/core"; +import { FormControl, FormGroup, Validators } from "@angular/forms"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { StorageRequest } from "@bitwarden/common/models/request/storage.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DialogService, ToastService } from "@bitwarden/components"; + +export interface AdjustStorageDialogV2Params { + price: number; + cadence: "month" | "year"; + type: "Add" | "Remove"; + organizationId?: string; +} + +export enum AdjustStorageDialogV2ResultType { + Submitted = "submitted", + Closed = "closed", +} + +@Component({ + templateUrl: "./adjust-storage-dialog-v2.component.html", +}) +export class AdjustStorageDialogV2Component { + protected formGroup = new FormGroup({ + storage: new FormControl(0, [ + Validators.required, + Validators.min(0), + Validators.max(99), + ]), + }); + + protected organizationId?: string; + protected price: number; + protected cadence: "month" | "year"; + + protected title: string; + protected body: string; + protected storageFieldLabel: string; + + protected ResultType = AdjustStorageDialogV2ResultType; + + constructor( + private apiService: ApiService, + @Inject(DIALOG_DATA) protected dialogParams: AdjustStorageDialogV2Params, + private dialogRef: DialogRef, + private i18nService: I18nService, + private organizationApiService: OrganizationApiServiceAbstraction, + private toastService: ToastService, + ) { + this.price = this.dialogParams.price; + this.cadence = this.dialogParams.cadence; + this.organizationId = this.dialogParams.organizationId; + switch (this.dialogParams.type) { + case "Add": + this.title = this.i18nService.t("addStorage"); + this.body = this.i18nService.t("storageAddNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageAdd"); + break; + case "Remove": + this.title = this.i18nService.t("removeStorage"); + this.body = this.i18nService.t("storageRemoveNote"); + this.storageFieldLabel = this.i18nService.t("gbStorageRemove"); + break; + } + } + + submit = async () => { + const request = new StorageRequest(); + switch (this.dialogParams.type) { + case "Add": + request.storageGbAdjustment = this.formGroup.value.storage; + break; + case "Remove": + request.storageGbAdjustment = this.formGroup.value.storage * -1; + break; + } + + if (this.organizationId) { + await this.organizationApiService.updateStorage(this.organizationId, request); + } else { + await this.apiService.postAccountStorage(request); + } + + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("adjustedStorage", request.storageGbAdjustment.toString()), + }); + + this.dialogRef.close(this.ResultType.Submitted); + }; + + static open = ( + dialogService: DialogService, + dialogConfig: DialogConfig, + ) => + dialogService.open( + AdjustStorageDialogV2Component, + dialogConfig, + ); +} diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.html b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html similarity index 100% rename from apps/web/src/app/billing/shared/adjust-storage.component.html rename to apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.html diff --git a/apps/web/src/app/billing/shared/adjust-storage.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts similarity index 95% rename from apps/web/src/app/billing/shared/adjust-storage.component.ts rename to apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 5cf05ea015c..a67c63a9fad 100644 --- a/apps/web/src/app/billing/shared/adjust-storage.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -12,7 +12,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PaymentComponent } from "./payment/payment.component"; +import { PaymentComponent } from "../payment/payment.component"; export interface AdjustStorageDialogData { storageGbPrice: number; @@ -27,9 +27,9 @@ export enum AdjustStorageDialogResult { } @Component({ - templateUrl: "adjust-storage.component.html", + templateUrl: "adjust-storage-dialog.component.html", }) -export class AdjustStorageComponent { +export class AdjustStorageDialogComponent { storageGbPrice: number; add: boolean; organizationId: string; @@ -126,5 +126,5 @@ export function openAdjustStorageDialog( dialogService: DialogService, config: DialogConfig, ) { - return dialogService.open(AdjustStorageComponent, config); + return dialogService.open(AdjustStorageDialogComponent, config); } diff --git a/apps/web/src/app/billing/shared/billing-shared.module.ts b/apps/web/src/app/billing/shared/billing-shared.module.ts index 300817bad55..c9b3f2de855 100644 --- a/apps/web/src/app/billing/shared/billing-shared.module.ts +++ b/apps/web/src/app/billing/shared/billing-shared.module.ts @@ -6,7 +6,8 @@ import { SharedModule } from "../../shared"; import { AddCreditDialogComponent } from "./add-credit-dialog.component"; import { AdjustPaymentDialogV2Component } from "./adjust-payment-dialog/adjust-payment-dialog-v2.component"; import { AdjustPaymentDialogComponent } from "./adjust-payment-dialog/adjust-payment-dialog.component"; -import { AdjustStorageComponent } from "./adjust-storage.component"; +import { AdjustStorageDialogV2Component } from "./adjust-storage-dialog/adjust-storage-dialog-v2.component"; +import { AdjustStorageDialogComponent } from "./adjust-storage-dialog/adjust-storage-dialog.component"; import { BillingHistoryComponent } from "./billing-history.component"; import { OffboardingSurveyComponent } from "./offboarding-survey.component"; import { PaymentV2Component } from "./payment/payment-v2.component"; @@ -30,7 +31,7 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac declarations: [ AddCreditDialogComponent, AdjustPaymentDialogComponent, - AdjustStorageComponent, + AdjustStorageDialogComponent, BillingHistoryComponent, PaymentMethodComponent, SecretsManagerSubscribeComponent, @@ -38,12 +39,13 @@ import { VerifyBankAccountComponent } from "./verify-bank-account/verify-bank-ac UpdateLicenseDialogComponent, OffboardingSurveyComponent, AdjustPaymentDialogV2Component, + AdjustStorageDialogV2Component, ], exports: [ SharedModule, PaymentComponent, TaxInfoComponent, - AdjustStorageComponent, + AdjustStorageDialogComponent, BillingHistoryComponent, SecretsManagerSubscribeComponent, UpdateLicenseComponent, From 72dab94216160c15799b2b8e537fc8693921d7aa Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:44:52 -0500 Subject: [PATCH 06/28] remove brand from logic that determines if the card section should show (#10871) --- libs/vault/src/cipher-view/cipher-view.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index cb8f86b9809..e737e43f355 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -70,8 +70,8 @@ export class CipherViewComponent implements OnInit, OnDestroy { } get hasCard() { - const { cardholderName, code, expMonth, expYear, brand, number } = this.cipher.card; - return cardholderName || code || expMonth || expYear || brand || number; + const { cardholderName, code, expMonth, expYear, number } = this.cipher.card; + return cardholderName || code || expMonth || expYear || number; } get hasLogin() { From 44f1fc156c6c160b8de30885869adbc03c51cadf Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 4 Sep 2024 13:39:48 -0400 Subject: [PATCH 07/28] [PM-11458] Bugfix - If two digit year was entered for card, the expired card message shows if card is not expired (#10801) * normalize card expiry year before determining if it is expired * add tests --- .../individual-vault/add-edit.component.ts | 22 +------- libs/common/src/vault/utils.spec.ts | 50 ++++++++++++++++++- libs/common/src/vault/utils.ts | 41 +++++++++++++++ .../src/cipher-view/cipher-view.component.ts | 24 +-------- 4 files changed, 94 insertions(+), 43 deletions(-) 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 71ccaab7dd7..d1b51b611f5 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 @@ -24,7 +24,7 @@ import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folde import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Launchable } from "@bitwarden/common/vault/interfaces/launchable"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { isCardExpired } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -123,7 +123,7 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.configService.getFeatureFlag$(FeatureFlag.ExtensionRefresh), ); - this.cardIsExpired = extensionRefreshEnabled && this.isCardExpiryInThePast(); + this.cardIsExpired = extensionRefreshEnabled && isCardExpired(this.cipher.card); } ngOnDestroy() { @@ -235,24 +235,6 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit, On this.viewingPasswordHistory = !this.viewingPasswordHistory; } - isCardExpiryInThePast() { - if (this.cipher.card) { - const { expMonth, expYear }: CardView = this.cipher.card; - - if (expYear && expMonth) { - // `Date` months are zero-indexed - const parsedMonth = parseInt(expMonth) - 1; - const parsedYear = parseInt(expYear); - - // First day of the next month minus one, to get last day of the card month - const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); - const now = new Date(); - - return cardExpiry < now; - } - } - } - protected cleanUp() { if (this.totpInterval) { window.clearInterval(this.totpInterval); diff --git a/libs/common/src/vault/utils.spec.ts b/libs/common/src/vault/utils.spec.ts index 1cb185cffd3..54ec66984e2 100644 --- a/libs/common/src/vault/utils.spec.ts +++ b/libs/common/src/vault/utils.spec.ts @@ -1,4 +1,5 @@ -import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { normalizeExpiryYearFormat, isCardExpired } from "@bitwarden/common/vault/utils"; function getExpiryYearValueFormats(currentCentury: string) { return [ @@ -72,3 +73,50 @@ describe("normalizeExpiryYearFormat", () => { jest.clearAllTimers(); }); }); + +function getCardExpiryDateValues() { + const currentDate = new Date(); + + const currentYear = currentDate.getFullYear(); + + // `Date` months are zero-indexed, our expiry date month inputs are one-indexed + const currentMonth = currentDate.getMonth() + 1; + + return [ + [null, null, false], // no month, no year + [undefined, undefined, false], // no month, no year, invalid values + ["", "", false], // no month, no year, invalid values + ["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values + ["0", `${currentYear - 1}`, true], // invalid 0 month + ["00", `${currentYear + 1}`, false], // invalid 0 month + [`${currentMonth}`, "0000", true], // current month, in the year 2000 + [null, `${currentYear}`.slice(-2), false], // no month, this year + [null, `${currentYear - 1}`.slice(-2), true], // no month, last year + ["1", null, false], // no year, January + ["1", `${currentYear - 1}`, true], // January last year + ["13", `${currentYear}`, false], // 12 + 1 is Feb. in the next year (Date is zero-indexed) + [`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired + [`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over) + [`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over) + [`${currentMonth - 1}`, `${currentYear}`, true], // last month + [`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now + ]; +} + +describe("isCardExpired", () => { + const expiryYearValueFormats = getCardExpiryDateValues(); + + expiryYearValueFormats.forEach( + ([inputMonth, inputYear, expectedValue]: [string | null, string | null, boolean]) => { + it(`should return ${expectedValue} when the card expiry month is ${inputMonth} and the card expiry year is ${inputYear}`, () => { + const testCardView = new CardView(); + testCardView.expMonth = inputMonth; + testCardView.expYear = inputYear; + + const cardIsExpired = isCardExpired(testCardView); + + expect(cardIsExpired).toBe(expectedValue); + }); + }, + ); +}); diff --git a/libs/common/src/vault/utils.ts b/libs/common/src/vault/utils.ts index 7fed4abc12e..7d8784eda78 100644 --- a/libs/common/src/vault/utils.ts +++ b/libs/common/src/vault/utils.ts @@ -1,3 +1,5 @@ +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; + type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; @@ -40,3 +42,42 @@ export function normalizeExpiryYearFormat(yearInput: string | number): Year | nu return expirationYear as Year | null; } + +/** + * Takes a cipher card view and returns "true" if the month and year affirmativey indicate + * the card is expired. + * + * @export + * @param {CardView} cipherCard + * @return {*} {boolean} + */ +export function isCardExpired(cipherCard: CardView): boolean { + if (cipherCard) { + const { expMonth = null, expYear = null } = cipherCard; + + const now = new Date(); + const normalizedYear = normalizeExpiryYearFormat(expYear); + + // If the card year is before the current year, don't bother checking the month + if (normalizedYear && parseInt(normalizedYear) < now.getFullYear()) { + return true; + } + + if (normalizedYear && expMonth) { + // `Date` months are zero-indexed + const parsedMonth = + parseInt(expMonth) - 1 || + // Add a month floor of 0 to protect against an invalid low month value of "0" + 0; + + const parsedYear = parseInt(normalizedYear); + + // First day of the next month minus one, to get last day of the card month + const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); + + return cardExpiry < now; + } + } + + return false; +} diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index e737e43f355..10701083b79 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -8,10 +8,10 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga import { CollectionId } from "@bitwarden/common/types/guid"; import { CollectionService } from "@bitwarden/common/vault/abstractions/collection.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; -import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { CollectionView } from "@bitwarden/common/vault/models/view/collection.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; +import { isCardExpired } from "@bitwarden/common/vault/utils"; import { SearchModule, CalloutModule } from "@bitwarden/components"; import { AdditionalOptionsComponent } from "./additional-options/additional-options.component"; @@ -61,7 +61,7 @@ export class CipherViewComponent implements OnInit, OnDestroy { async ngOnInit() { await this.loadCipherData(); - this.cardIsExpired = this.isCardExpiryInThePast(); + this.cardIsExpired = isCardExpired(this.cipher.card); } ngOnDestroy(): void { @@ -102,24 +102,4 @@ export class CipherViewComponent implements OnInit, OnDestroy { .pipe(takeUntil(this.destroyed$)); } } - - isCardExpiryInThePast() { - if (this.cipher.card) { - const { expMonth, expYear }: CardView = this.cipher.card; - - if (expYear && expMonth) { - // `Date` months are zero-indexed - const parsedMonth = parseInt(expMonth) - 1; - const parsedYear = parseInt(expYear); - - // First day of the next month minus one, to get last day of the card month - const cardExpiry = new Date(parsedYear, parsedMonth + 1, 0); - const now = new Date(); - - return cardExpiry < now; - } - } - - return false; - } } From 095ce7ec30061e49dafc9fbff8581f3f9571e57d Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 4 Sep 2024 13:50:48 -0400 Subject: [PATCH 08/28] [PM-7956] Update passkey pop-out to new UI (#10796) * rename existing fido2 components to use v1 designation * use fido2 message type value constants in components * add v2 fido2 components * add search to login UX of fido2 v2 component * add new item button in top nav of fido2 v2 component * get and pass activeUserId to cipher key decription methods * cleanup / PR suggestions --- apps/browser/src/_locales/en/messages.json | 10 +- .../src/auth/popup/lock.component.html | 2 +- .../fido2/content/messaging/message.ts | 4 +- .../fido2/fido2-cipher-row-v1.component.html | 36 ++ .../fido2/fido2-cipher-row-v1.component.ts | 39 ++ .../fido2/fido2-cipher-row.component.html | 57 +-- .../popup/fido2/fido2-cipher-row.component.ts | 25 +- .../fido2-use-browser-link-v1.component.html | 52 ++ .../fido2-use-browser-link-v1.component.ts | 113 +++++ .../fido2/fido2-use-browser-link.component.ts | 11 +- .../popup/fido2/fido2-v1.component.html | 142 ++++++ .../popup/fido2/fido2-v1.component.ts | 443 ++++++++++++++++++ .../autofill/popup/fido2/fido2.component.html | 254 +++++----- .../autofill/popup/fido2/fido2.component.ts | 233 +++++---- apps/browser/src/popup/app-routing.module.ts | 8 +- apps/browser/src/popup/app.module.ts | 12 +- apps/browser/src/popup/scss/pages.scss | 2 +- 17 files changed, 1159 insertions(+), 284 deletions(-) create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-v1.component.html create mode 100644 apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 89b605ab632..3aa1ac097ce 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3477,7 +3477,7 @@ "passkeyFeatureIsNotImplementedForAccountsWithoutMasterPassword": { "message": "Verification required by the initiating site. This feature is not yet implemented for accounts without master password." }, - "logInWithPasskey": { + "logInWithPasskeyQuestion": { "message": "Log in with passkey?" }, "passkeyAlreadyExists": { @@ -3489,6 +3489,9 @@ "noMatchingPasskeyLogin": { "message": "You do not have a matching login for this site." }, + "noMatchingLoginsForSite": { + "message": "No matching logins for this site" + }, "confirm": { "message": "Confirm" }, @@ -3498,9 +3501,12 @@ "savePasskeyNewLogin": { "message": "Save passkey as new login" }, - "choosePasskey": { + "chooseCipherForPasskeySave": { "message": "Choose a login to save this passkey to" }, + "chooseCipherForPasskeyAuth": { + "message": "Choose a passkey to log in with" + }, "passkeyItem": { "message": "Passkey Item" }, diff --git a/apps/browser/src/auth/popup/lock.component.html b/apps/browser/src/auth/popup/lock.component.html index ccc743d86d4..fb1b09de49c 100644 --- a/apps/browser/src/auth/popup/lock.component.html +++ b/apps/browser/src/auth/popup/lock.component.html @@ -94,7 +94,7 @@ {{ "awaitDesktop" | i18n }}

- + diff --git a/apps/browser/src/autofill/fido2/content/messaging/message.ts b/apps/browser/src/autofill/fido2/content/messaging/message.ts index d42c10a5d88..5815be9eb60 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/message.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/message.ts @@ -18,7 +18,7 @@ export enum MessageType { } /** - * The params provided by the page-script are created in an insecure environemnt and + * The params provided by the page-script are created in an insecure environment and * should not be trusted. This type is used to ensure that the content-script does not * trust the `origin` or `sameOriginWithAncestors` params. */ @@ -38,7 +38,7 @@ export type CredentialCreationResponse = { }; /** - * The params provided by the page-script are created in an insecure environemnt and + * The params provided by the page-script are created in an insecure environment and * should not be trusted. This type is used to ensure that the content-script does not * trust the `origin` or `sameOriginWithAncestors` params. */ diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html new file mode 100644 index 00000000000..852fd4a0e81 --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.html @@ -0,0 +1,36 @@ +
+
+ +
+
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts new file mode 100644 index 00000000000..d9d492bdcc1 --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row-v1.component.ts @@ -0,0 +1,39 @@ +import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; + +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +@Component({ + selector: "app-fido2-cipher-row-v1", + templateUrl: "fido2-cipher-row-v1.component.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Fido2CipherRowV1Component { + @Output() onSelected = new EventEmitter(); + @Input() cipher: CipherView; + @Input() last: boolean; + @Input() title: string; + @Input() isSearching: boolean; + @Input() isSelected: boolean; + + protected selectCipher(c: CipherView) { + this.onSelected.emit(c); + } + + /** + * Returns a subname for the cipher. + * If this has a FIDO2 credential, and the cipher.name is different from the FIDO2 credential's rpId, return the rpId. + * @param c Cipher + * @returns + */ + protected getSubName(c: CipherView): string | null { + const fido2Credentials = c.login?.fido2Credentials; + + if (!fido2Credentials || fido2Credentials.length === 0) { + return null; + } + + const [fido2Credential] = fido2Credentials; + + return c.name !== fido2Credential.rpId ? fido2Credential.rpId : null; + } +} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.html b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.html index 852fd4a0e81..0328a91bff5 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.html +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.html @@ -1,36 +1,21 @@ -
-
- -
-
+ + + diff --git a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts index 25d623b1692..91bcd6494e6 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-cipher-row.component.ts @@ -1,19 +1,40 @@ +import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from "@angular/core"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + BadgeModule, + ButtonModule, + IconButtonModule, + ItemModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, +} from "@bitwarden/components"; @Component({ selector: "app-fido2-cipher-row", templateUrl: "fido2-cipher-row.component.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + BadgeModule, + ButtonModule, + CommonModule, + IconButtonModule, + ItemModule, + JslibModule, + SectionComponent, + SectionHeaderComponent, + TypographyModule, + ], }) export class Fido2CipherRowComponent { @Output() onSelected = new EventEmitter(); @Input() cipher: CipherView; @Input() last: boolean; @Input() title: string; - @Input() isSearching: boolean; - @Input() isSelected: boolean; protected selectCipher(c: CipherView) { this.onSelected.emit(c); diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html new file mode 100644 index 00000000000..9f6c0aca50d --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.html @@ -0,0 +1,52 @@ + + + + +
+ +
+
+ +
+
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts new file mode 100644 index 00000000000..cf79dfc6520 --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link-v1.component.ts @@ -0,0 +1,113 @@ +import { animate, state, style, transition, trigger } from "@angular/animations"; +import { ConnectedPosition } from "@angular/cdk/overlay"; +import { Component } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; + +import { fido2PopoutSessionData$ } from "../../../vault/popup/utils/fido2-popout-session-data"; +import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-fido2-user-interface.service"; + +@Component({ + selector: "app-fido2-use-browser-link-v1", + templateUrl: "fido2-use-browser-link-v1.component.html", + animations: [ + trigger("transformPanel", [ + state( + "void", + style({ + opacity: 0, + }), + ), + transition( + "void => open", + animate( + "100ms linear", + style({ + opacity: 1, + }), + ), + ), + transition("* => void", animate("100ms linear", style({ opacity: 0 }))), + ]), + ], +}) +export class Fido2UseBrowserLinkV1Component { + showOverlay = false; + isOpen = false; + overlayPosition: ConnectedPosition[] = [ + { + originX: "start", + originY: "bottom", + overlayX: "start", + overlayY: "top", + offsetY: 5, + }, + ]; + + protected fido2PopoutSessionData$ = fido2PopoutSessionData$(); + + constructor( + private domainSettingsService: DomainSettingsService, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + ) {} + + toggle() { + this.isOpen = !this.isOpen; + } + + close() { + this.isOpen = false; + } + + /** + * Aborts the current FIDO2 session and fallsback to the browser. + * @param excludeDomain - Identifies if the domain should be excluded from future FIDO2 prompts. + */ + protected async abort(excludeDomain = true) { + this.close(); + const sessionData = await firstValueFrom(this.fido2PopoutSessionData$); + + if (!excludeDomain) { + this.abortSession(sessionData.sessionId); + return; + } + // Show overlay to prevent the user from interacting with the page. + this.showOverlay = true; + await this.handleDomainExclusion(sessionData.senderUrl); + // Give the user a chance to see the toast before closing the popout. + await Utils.delay(2000); + this.abortSession(sessionData.sessionId); + } + + /** + * Excludes the domain from future FIDO2 prompts. + * @param uri - The domain uri to exclude from future FIDO2 prompts. + */ + private async handleDomainExclusion(uri: string) { + const existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + + const validDomain = Utils.getHostname(uri); + const savedDomains: NeverDomains = { + ...existingDomains, + }; + savedDomains[validDomain] = null; + + await this.domainSettingsService.setNeverDomains(savedDomains); + + this.platformUtilsService.showToast( + "success", + null, + this.i18nService.t("domainAddedToExcludedDomains", validDomain), + ); + } + + private abortSession(sessionId: string) { + BrowserFido2UserInterfaceSession.abortPopout(sessionId, true); + } +} diff --git a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts index d9a7c7c9cbc..86f13d29c7a 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2-use-browser-link.component.ts @@ -1,8 +1,11 @@ import { animate, state, style, transition, trigger } from "@angular/animations"; -import { ConnectedPosition } from "@angular/cdk/overlay"; +import { A11yModule } from "@angular/cdk/a11y"; +import { ConnectedPosition, CdkOverlayOrigin, CdkConnectedOverlay } from "@angular/cdk/overlay"; +import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { firstValueFrom } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -15,6 +18,8 @@ import { BrowserFido2UserInterfaceSession } from "../../fido2/services/browser-f @Component({ selector: "app-fido2-use-browser-link", templateUrl: "fido2-use-browser-link.component.html", + standalone: true, + imports: [A11yModule, CdkConnectedOverlay, CdkOverlayOrigin, CommonModule, JslibModule], animations: [ trigger("transformPanel", [ state( @@ -90,11 +95,11 @@ export class Fido2UseBrowserLinkComponent { * @param uri - The domain uri to exclude from future FIDO2 prompts. */ private async handleDomainExclusion(uri: string) { - const exisitingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); + const existingDomains = await firstValueFrom(this.domainSettingsService.neverDomains$); const validDomain = Utils.getHostname(uri); const savedDomains: NeverDomains = { - ...exisitingDomains, + ...existingDomains, }; savedDomains[validDomain] = null; diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html new file mode 100644 index 00000000000..8a052fbc5b7 --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.html @@ -0,0 +1,142 @@ + +
+
+
+ + + + + + +
+ + +
+ +
+
+
+ + + +
+

+ {{ subtitleText | i18n }} +

+ + +
+
+ +
+
+ +
+ +
+
+ + +
+ +
+
+
+
+ +
+

{{ "passkeyAlreadyExists" | i18n }}

+
+
+ +
+
+ +
+
+ +
+

{{ "noPasskeysFoundForThisApplication" | i18n }}

+
+ +
+
+ + +
+
diff --git a/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts new file mode 100644 index 00000000000..d6026a8c7a0 --- /dev/null +++ b/apps/browser/src/autofill/popup/fido2/fido2-v1.component.ts @@ -0,0 +1,443 @@ +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { + BehaviorSubject, + combineLatest, + concatMap, + filter, + firstValueFrom, + map, + Observable, + Subject, + take, + takeUntil, +} from "rxjs"; + +import { SearchService } from "@bitwarden/common/abstractions/search.service"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; +import { CardView } from "@bitwarden/common/vault/models/view/card.view"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; +import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; +import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; +import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; +import { DialogService } from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +import { ZonedMessageListenerService } from "../../../platform/browser/zoned-message-listener.service"; +import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; +import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; +import { + BrowserFido2Message, + BrowserFido2UserInterfaceSession, + BrowserFido2MessageTypes, +} from "../../fido2/services/browser-fido2-user-interface.service"; + +interface ViewData { + message: BrowserFido2Message; + fallbackSupported: boolean; +} + +@Component({ + selector: "app-fido2-v1", + templateUrl: "fido2-v1.component.html", + styleUrls: [], +}) +export class Fido2V1Component implements OnInit, OnDestroy { + private destroy$ = new Subject(); + private hasSearched = false; + + protected cipher: CipherView; + protected searchTypeSearch = false; + protected searchPending = false; + protected searchText: string; + protected url: string; + protected hostname: string; + protected data$: Observable; + protected sessionId?: string; + protected senderTabId?: string; + protected ciphers?: CipherView[] = []; + protected displayedCiphers?: CipherView[] = []; + protected loading = false; + protected subtitleText: string; + protected credentialText: string; + protected BrowserFido2MessageTypes = BrowserFido2MessageTypes; + + private message$ = new BehaviorSubject(null); + + constructor( + private router: Router, + private activatedRoute: ActivatedRoute, + private cipherService: CipherService, + private platformUtilsService: PlatformUtilsService, + private domainSettingsService: DomainSettingsService, + private searchService: SearchService, + private logService: LogService, + private dialogService: DialogService, + private browserMessagingApi: ZonedMessageListenerService, + private passwordRepromptService: PasswordRepromptService, + private fido2UserVerificationService: Fido2UserVerificationService, + private accountService: AccountService, + ) {} + + ngOnInit() { + this.searchTypeSearch = !this.platformUtilsService.isSafari(); + + const queryParams$ = this.activatedRoute.queryParamMap.pipe( + take(1), + map((queryParamMap) => ({ + sessionId: queryParamMap.get("sessionId"), + senderTabId: queryParamMap.get("senderTabId"), + senderUrl: queryParamMap.get("senderUrl"), + })), + ); + + combineLatest([ + queryParams$, + this.browserMessagingApi.messageListener$() as Observable, + ]) + .pipe( + concatMap(async ([queryParams, message]) => { + this.sessionId = queryParams.sessionId; + this.senderTabId = queryParams.senderTabId; + this.url = queryParams.senderUrl; + // For a 'NewSessionCreatedRequest', abort if it doesn't belong to the current session. + if ( + message.type === BrowserFido2MessageTypes.NewSessionCreatedRequest && + message.sessionId !== queryParams.sessionId + ) { + this.abort(false); + return; + } + + // Ignore messages that don't belong to the current session. + if (message.sessionId !== queryParams.sessionId) { + return; + } + + if (message.type === BrowserFido2MessageTypes.AbortRequest) { + this.abort(false); + return; + } + + return message; + }), + filter((message) => !!message), + takeUntil(this.destroy$), + ) + .subscribe((message) => { + this.message$.next(message); + }); + + this.data$ = this.message$.pipe( + filter((message) => message != undefined), + concatMap(async (message) => { + switch (message.type) { + case BrowserFido2MessageTypes.ConfirmNewCredentialRequest: { + const equivalentDomains = await firstValueFrom( + this.domainSettingsService.getUrlEquivalentDomains(this.url), + ); + + this.ciphers = (await this.cipherService.getAllDecrypted()).filter( + (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, + ); + this.displayedCiphers = this.ciphers.filter( + (cipher) => + cipher.login.matchesUri(this.url, equivalentDomains) && + this.hasNoOtherPasskeys(cipher, message.userHandle), + ); + + if (this.displayedCiphers.length > 0) { + this.selectedPasskey(this.displayedCiphers[0]); + } + break; + } + + case BrowserFido2MessageTypes.PickCredentialRequest: { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + this.ciphers = await Promise.all( + message.cipherIds.map(async (cipherId) => { + const cipher = await this.cipherService.get(cipherId); + return cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + }), + ); + this.displayedCiphers = [...this.ciphers]; + if (this.displayedCiphers.length > 0) { + this.selectedPasskey(this.displayedCiphers[0]); + } + break; + } + + case BrowserFido2MessageTypes.InformExcludedCredentialRequest: { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + this.ciphers = await Promise.all( + message.existingCipherIds.map(async (cipherId) => { + const cipher = await this.cipherService.get(cipherId); + return cipher.decrypt( + await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), + ); + }), + ); + this.displayedCiphers = [...this.ciphers]; + + if (this.displayedCiphers.length > 0) { + this.selectedPasskey(this.displayedCiphers[0]); + } + break; + } + } + + this.subtitleText = + this.displayedCiphers.length > 0 + ? this.getCredentialSubTitleText(message.type) + : "noMatchingPasskeyLogin"; + + this.credentialText = this.getCredentialButtonText(message.type); + return { + message, + fallbackSupported: "fallbackSupported" in message && message.fallbackSupported, + }; + }), + takeUntil(this.destroy$), + ); + + queryParams$.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => { + this.send({ + sessionId: queryParams.sessionId, + type: BrowserFido2MessageTypes.ConnectResponse, + }); + }); + } + + protected async submit() { + const data = this.message$.value; + if (data?.type === BrowserFido2MessageTypes.PickCredentialRequest) { + // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. + // PM-4577 - https://github.com/bitwarden/clients/pull/8746 + const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); + + this.send({ + sessionId: this.sessionId, + cipherId: this.cipher.id, + type: BrowserFido2MessageTypes.PickCredentialResponse, + userVerified, + }); + } else if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { + if (this.cipher.login.hasFido2Credentials) { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "overwritePasskey" }, + content: { key: "overwritePasskeyAlert" }, + type: "info", + }); + + if (!confirmed) { + return false; + } + } + + // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. + // PM-4577 - https://github.com/bitwarden/clients/pull/8746 + const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); + + this.send({ + sessionId: this.sessionId, + cipherId: this.cipher.id, + type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, + userVerified, + }); + } + + this.loading = true; + } + + protected async saveNewLogin() { + const data = this.message$.value; + if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { + const name = data.credentialName || data.rpId; + // TODO: Revert to check for user verification once user verification for passkeys is approved for production. + // PM-4577 - https://github.com/bitwarden/clients/pull/8746 + await this.createNewCipher(name, data.userName); + + // We are bypassing user verification pending approval. + this.send({ + sessionId: this.sessionId, + cipherId: this.cipher?.id, + type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, + userVerified: data.userVerification, + }); + } + + this.loading = true; + } + + getCredentialSubTitleText(messageType: string): string { + return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest + ? "chooseCipherForPasskeySave" + : "logInWithPasskeyQuestion"; + } + + getCredentialButtonText(messageType: string): string { + return messageType == BrowserFido2MessageTypes.ConfirmNewCredentialRequest + ? "savePasskey" + : "confirm"; + } + + selectedPasskey(item: CipherView) { + this.cipher = item; + } + + viewPasskey() { + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/view-cipher"], { + queryParams: { + cipherId: this.cipher.id, + uilocation: "popout", + senderTabId: this.senderTabId, + sessionId: this.sessionId, + singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, + }, + }); + } + + addCipher() { + const data = this.message$.value; + + if (data?.type !== BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { + return; + } + + // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.router.navigate(["/add-cipher"], { + queryParams: { + name: data.credentialName || data.rpId, + uri: this.url, + type: CipherType.Login.toString(), + uilocation: "popout", + username: data.userName, + senderTabId: this.senderTabId, + sessionId: this.sessionId, + userVerification: data.userVerification, + singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, + }, + }); + } + + protected async search() { + this.hasSearched = await this.searchService.isSearchable(this.searchText); + this.searchPending = true; + if (this.hasSearched) { + this.displayedCiphers = await this.searchService.searchCiphers( + this.searchText, + null, + this.ciphers, + ); + } else { + const equivalentDomains = await firstValueFrom( + this.domainSettingsService.getUrlEquivalentDomains(this.url), + ); + this.displayedCiphers = this.ciphers.filter((cipher) => + cipher.login.matchesUri(this.url, equivalentDomains), + ); + } + this.searchPending = false; + this.selectedPasskey(this.displayedCiphers[0]); + } + + abort(fallback: boolean) { + this.unload(fallback); + window.close(); + } + + unload(fallback = false) { + this.send({ + sessionId: this.sessionId, + type: BrowserFido2MessageTypes.AbortResponse, + fallbackRequested: fallback, + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private buildCipher(name: string, username: string) { + this.cipher = new CipherView(); + this.cipher.name = name; + + this.cipher.type = CipherType.Login; + this.cipher.login = new LoginView(); + this.cipher.login.username = username; + this.cipher.login.uris = [new LoginUriView()]; + this.cipher.login.uris[0].uri = this.url; + this.cipher.card = new CardView(); + this.cipher.identity = new IdentityView(); + this.cipher.secureNote = new SecureNoteView(); + this.cipher.secureNote.type = SecureNoteType.Generic; + this.cipher.reprompt = CipherRepromptType.None; + } + + private async createNewCipher(name: string, username: string) { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(map((a) => a?.id)), + ); + + this.buildCipher(name, username); + const cipher = await this.cipherService.encrypt(this.cipher, activeUserId); + try { + await this.cipherService.createWithServer(cipher); + this.cipher.id = cipher.id; + } catch (e) { + this.logService.error(e); + } + } + + // TODO: Remove and use fido2 user verification service once user verification for passkeys is approved for production. + private async handleUserVerification( + userVerificationRequested: boolean, + cipher: CipherView, + ): Promise { + const masterPasswordRepromptRequired = cipher && cipher.reprompt !== 0; + + if (masterPasswordRepromptRequired) { + return await this.passwordRepromptService.showPasswordPrompt(); + } + + return userVerificationRequested; + } + + private send(msg: BrowserFido2Message) { + BrowserFido2UserInterfaceSession.sendMessage({ + sessionId: this.sessionId, + ...msg, + }); + } + + /** + * This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle + * @param userHandle + */ + private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean { + if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) { + return true; + } + + return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle); + } +} diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.html b/apps/browser/src/autofill/popup/fido2/fido2.component.html index 9036d6d991c..00cd55d31b5 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.html +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.html @@ -1,136 +1,134 @@ - -
-
-
- - - + diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 8bd667c17fb..c389e9ad5b8 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -1,4 +1,6 @@ +import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; +import { FormsModule } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { BehaviorSubject, @@ -13,13 +15,14 @@ import { takeUntil, } from "rxjs"; +import { JslibModule } from "@bitwarden/angular/jslib.module"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherType, SecureNoteType } from "@bitwarden/common/vault/enums"; +import { SecureNoteType, CipherType } from "@bitwarden/common/vault/enums"; import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -27,17 +30,39 @@ import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view" import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; import { LoginView } from "@bitwarden/common/vault/models/view/login.view"; import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view"; -import { DialogService } from "@bitwarden/components"; +import { + ButtonModule, + DialogService, + Icons, + ItemModule, + NoItemsModule, + SearchModule, + SectionComponent, + SectionHeaderComponent, +} from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; import { ZonedMessageListenerService } from "../../../platform/browser/zoned-message-listener.service"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { VaultPopoutType } from "../../../vault/popup/utils/vault-popout-window"; import { Fido2UserVerificationService } from "../../../vault/services/fido2-user-verification.service"; import { BrowserFido2Message, BrowserFido2UserInterfaceSession, + BrowserFido2MessageTypes, } from "../../fido2/services/browser-fido2-user-interface.service"; +import { Fido2CipherRowComponent } from "./fido2-cipher-row.component"; +import { Fido2UseBrowserLinkComponent } from "./fido2-use-browser-link.component"; + +const PasskeyActions = { + Register: "register", + Authenticate: "authenticate", +} as const; + +type PasskeyActionValue = (typeof PasskeyActions)[keyof typeof PasskeyActions]; + interface ViewData { message: BrowserFido2Message; fallbackSupported: boolean; @@ -46,28 +71,45 @@ interface ViewData { @Component({ selector: "app-fido2", templateUrl: "fido2.component.html", - styleUrls: [], + standalone: true, + imports: [ + ButtonModule, + CommonModule, + Fido2CipherRowComponent, + Fido2UseBrowserLinkComponent, + FormsModule, + ItemModule, + JslibModule, + NoItemsModule, + PopupHeaderComponent, + PopupPageComponent, + SearchModule, + SectionComponent, + SectionHeaderComponent, + ], }) export class Fido2Component implements OnInit, OnDestroy { private destroy$ = new Subject(); - private hasSearched = false; - - protected cipher: CipherView; - protected searchTypeSearch = false; - protected searchPending = false; - protected searchText: string; - protected url: string; - protected hostname: string; - protected data$: Observable; - protected sessionId?: string; - protected senderTabId?: string; - protected ciphers?: CipherView[] = []; - protected displayedCiphers?: CipherView[] = []; - protected loading = false; - protected subtitleText: string; - protected credentialText: string; - private message$ = new BehaviorSubject(null); + private hasSearched = false; + protected BrowserFido2MessageTypes = BrowserFido2MessageTypes; + protected cipher: CipherView; + protected ciphers?: CipherView[] = []; + protected data$: Observable; + protected displayedCiphers?: CipherView[] = []; + protected equivalentDomains: Set; + protected equivalentDomainsURL: string; + protected hostname: string; + protected loading = false; + protected noResultsIcon = Icons.NoResults; + protected passkeyAction: PasskeyActionValue = PasskeyActions.Register; + protected PasskeyActions = PasskeyActions; + protected searchText: string; + protected searchTypeSearch = false; + protected senderTabId?: string; + protected sessionId?: string; + protected showNewPasskeyButton: boolean = false; + protected url: string; constructor( private router: Router, @@ -80,8 +122,8 @@ export class Fido2Component implements OnInit, OnDestroy { private dialogService: DialogService, private browserMessagingApi: ZonedMessageListenerService, private passwordRepromptService: PasswordRepromptService, - private fido2UserVerificationService: Fido2UserVerificationService, private accountService: AccountService, + private fido2UserVerificationService: Fido2UserVerificationService, ) {} ngOnInit() { @@ -107,7 +149,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.url = queryParams.senderUrl; // For a 'NewSessionCreatedRequest', abort if it doesn't belong to the current session. if ( - message.type === "NewSessionCreatedRequest" && + message.type === BrowserFido2MessageTypes.NewSessionCreatedRequest && message.sessionId !== queryParams.sessionId ) { this.abort(false); @@ -119,7 +161,7 @@ export class Fido2Component implements OnInit, OnDestroy { return; } - if (message.type === "AbortRequest") { + if (message.type === BrowserFido2MessageTypes.AbortRequest) { this.abort(false); return; } @@ -137,7 +179,7 @@ export class Fido2Component implements OnInit, OnDestroy { filter((message) => message != undefined), concatMap(async (message) => { switch (message.type) { - case "ConfirmNewCredentialRequest": { + case BrowserFido2MessageTypes.ConfirmNewCredentialRequest: { const equivalentDomains = await firstValueFrom( this.domainSettingsService.getUrlEquivalentDomains(this.url), ); @@ -145,19 +187,22 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = (await this.cipherService.getAllDecrypted()).filter( (cipher) => cipher.type === CipherType.Login && !cipher.isDeleted, ); + this.displayedCiphers = this.ciphers.filter( (cipher) => cipher.login.matchesUri(this.url, equivalentDomains) && - this.hasNoOtherPasskeys(cipher, message.userHandle), + this.cipherHasNoOtherPasskeys(cipher, message.userHandle), ); - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } + this.passkeyAction = PasskeyActions.Register; + + // @TODO fix new cipher creation for other fido2 registration message types and remove `showNewPasskeyButton` from the template + this.showNewPasskeyButton = true; + break; } - case "PickCredentialRequest": { + case BrowserFido2MessageTypes.PickCredentialRequest: { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); @@ -170,14 +215,15 @@ export class Fido2Component implements OnInit, OnDestroy { ); }), ); + this.displayedCiphers = [...this.ciphers]; - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } + + this.passkeyAction = PasskeyActions.Authenticate; + break; } - case "InformExcludedCredentialRequest": { + case BrowserFido2MessageTypes.InformExcludedCredentialRequest: { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); @@ -190,40 +236,42 @@ export class Fido2Component implements OnInit, OnDestroy { ); }), ); + this.displayedCiphers = [...this.ciphers]; - if (this.displayedCiphers.length > 0) { - this.selectedPasskey(this.displayedCiphers[0]); - } + this.passkeyAction = PasskeyActions.Register; + + break; + } + + case BrowserFido2MessageTypes.InformCredentialNotFoundRequest: { + this.passkeyAction = PasskeyActions.Authenticate; + break; } } - this.subtitleText = - this.displayedCiphers.length > 0 - ? this.getCredentialSubTitleText(message.type) - : "noMatchingPasskeyLogin"; - - this.credentialText = this.getCredentialButtonText(message.type); return { message, fallbackSupported: "fallbackSupported" in message && message.fallbackSupported, }; }), + takeUntil(this.destroy$), ); queryParams$.pipe(takeUntil(this.destroy$)).subscribe((queryParams) => { this.send({ sessionId: queryParams.sessionId, - type: "ConnectResponse", + type: BrowserFido2MessageTypes.ConnectResponse, }); }); } protected async submit() { const data = this.message$.value; - if (data?.type === "PickCredentialRequest") { + + if (data?.type === BrowserFido2MessageTypes.PickCredentialRequest) { // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. // PM-4577 - https://github.com/bitwarden/clients/pull/8746 const userVerified = await this.handleUserVerification(data.userVerification, this.cipher); @@ -231,10 +279,10 @@ export class Fido2Component implements OnInit, OnDestroy { this.send({ sessionId: this.sessionId, cipherId: this.cipher.id, - type: "PickCredentialResponse", + type: BrowserFido2MessageTypes.PickCredentialResponse, userVerified, }); - } else if (data?.type === "ConfirmNewCredentialRequest") { + } else if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { if (this.cipher.login.hasFido2Credentials) { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "overwritePasskey" }, @@ -254,7 +302,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.send({ sessionId: this.sessionId, cipherId: this.cipher.id, - type: "ConfirmNewCredentialResponse", + type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, userVerified, }); } @@ -264,7 +312,8 @@ export class Fido2Component implements OnInit, OnDestroy { protected async saveNewLogin() { const data = this.message$.value; - if (data?.type === "ConfirmNewCredentialRequest") { + + if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { const name = data.credentialName || data.rpId; // TODO: Revert to check for user verification once user verification for passkeys is approved for production. // PM-4577 - https://github.com/bitwarden/clients/pull/8746 @@ -274,7 +323,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.send({ sessionId: this.sessionId, cipherId: this.cipher?.id, - type: "ConfirmNewCredentialResponse", + type: BrowserFido2MessageTypes.ConfirmNewCredentialResponse, userVerified: data.userVerification, }); } @@ -282,59 +331,47 @@ export class Fido2Component implements OnInit, OnDestroy { this.loading = true; } - getCredentialSubTitleText(messageType: string): string { - return messageType == "ConfirmNewCredentialRequest" ? "choosePasskey" : "logInWithPasskey"; - } - - getCredentialButtonText(messageType: string): string { - return messageType == "ConfirmNewCredentialRequest" ? "savePasskey" : "confirm"; - } - - selectedPasskey(item: CipherView) { + async handleCipherItemSelect(item: CipherView) { this.cipher = item; + + await this.submit(); } - viewPasskey() { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/view-cipher"], { - queryParams: { - cipherId: this.cipher.id, - uilocation: "popout", - senderTabId: this.senderTabId, - sessionId: this.sessionId, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); - } - - addCipher() { + async addCipher() { const data = this.message$.value; - if (data?.type !== "ConfirmNewCredentialRequest") { - return; + if (data?.type === BrowserFido2MessageTypes.ConfirmNewCredentialRequest) { + await this.router.navigate(["/add-cipher"], { + queryParams: { + type: CipherType.Login.toString(), + name: data.credentialName || data.rpId, + uri: this.url, + uilocation: "popout", + username: data.userName, + senderTabId: this.senderTabId, + sessionId: this.sessionId, + userVerification: data.userVerification, + singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, + }, + }); } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["/add-cipher"], { - queryParams: { - name: data.credentialName || data.rpId, - uri: this.url, - type: CipherType.Login.toString(), - uilocation: "popout", - username: data.userName, - senderTabId: this.senderTabId, - sessionId: this.sessionId, - userVerification: data.userVerification, - singleActionPopout: `${VaultPopoutType.fido2Popout}_${this.sessionId}`, - }, - }); + return; + } + + async getEquivalentDomains() { + if (this.equivalentDomainsURL !== this.url) { + this.equivalentDomainsURL = this.url; + this.equivalentDomains = await firstValueFrom( + this.domainSettingsService.getUrlEquivalentDomains(this.url), + ); + } + + return this.equivalentDomains; } protected async search() { this.hasSearched = await this.searchService.isSearchable(this.searchText); - this.searchPending = true; if (this.hasSearched) { this.displayedCiphers = await this.searchService.searchCiphers( this.searchText, @@ -342,15 +379,11 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers, ); } else { - const equivalentDomains = await firstValueFrom( - this.domainSettingsService.getUrlEquivalentDomains(this.url), - ); + const equivalentDomains = await this.getEquivalentDomains(); this.displayedCiphers = this.ciphers.filter((cipher) => cipher.login.matchesUri(this.url, equivalentDomains), ); } - this.searchPending = false; - this.selectedPasskey(this.displayedCiphers[0]); } abort(fallback: boolean) { @@ -361,7 +394,7 @@ export class Fido2Component implements OnInit, OnDestroy { unload(fallback = false) { this.send({ sessionId: this.sessionId, - type: "AbortResponse", + type: BrowserFido2MessageTypes.AbortResponse, fallbackRequested: fallback, }); } @@ -427,13 +460,11 @@ export class Fido2Component implements OnInit, OnDestroy { * This methods returns true if a cipher either has no passkeys, or has a passkey matching with userHandle * @param userHandle */ - private hasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean { + private cipherHasNoOtherPasskeys(cipher: CipherView, userHandle: string): boolean { if (cipher.login.fido2Credentials == null || cipher.login.fido2Credentials.length === 0) { return true; } - return cipher.login.fido2Credentials.some((passkey) => { - passkey.userHandle === userHandle; - }); + return cipher.login.fido2Credentials.some((passkey) => passkey.userHandle === userHandle); } } diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 82e673a9e54..aa8955035dd 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -41,6 +41,7 @@ import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component" import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; +import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; @@ -127,12 +128,11 @@ const routes: Routes = [ canActivate: [unauthGuardFn(unauthRouteOverrides)], data: { state: "home" }, }, - { + ...extensionRefreshSwap(Fido2V1Component, Fido2Component, { path: "fido2", - component: Fido2Component, canActivate: [fido2AuthGuard], data: { state: "fido2" }, - }, + }), { path: "login", component: LoginComponent, @@ -304,7 +304,6 @@ const routes: Routes = [ }, ...extensionRefreshSwap(NotificationsSettingsV1Component, NotificationsSettingsComponent, { path: "notifications", - component: NotificationsSettingsV1Component, canActivate: [authGuard], data: { state: "notifications" }, }), @@ -338,7 +337,6 @@ const routes: Routes = [ }, ...extensionRefreshSwap(ExcludedDomainsV1Component, ExcludedDomainsComponent, { path: "excluded-domains", - component: ExcludedDomainsV1Component, canActivate: [authGuard], data: { state: "excluded-domains" }, }), diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 56ddd3c6ba3..f8d3c691051 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -35,8 +35,11 @@ import { SsoComponent } from "../auth/popup/sso.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; import { TwoFactorComponent } from "../auth/popup/two-factor.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; +import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component"; import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component"; +import { Fido2UseBrowserLinkV1Component } from "../autofill/popup/fido2/fido2-use-browser-link-v1.component"; import { Fido2UseBrowserLinkComponent } from "../autofill/popup/fido2/fido2-use-browser-link.component"; +import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; import { AutofillV1Component } from "../autofill/popup/settings/autofill-v1.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; @@ -112,6 +115,9 @@ import "../platform/popup/locales"; ServicesModule, DialogModule, ExcludedDomainsComponent, + Fido2CipherRowComponent, + Fido2Component, + Fido2UseBrowserLinkComponent, FilePopoutCalloutComponent, AvatarModule, AccountComponent, @@ -140,8 +146,8 @@ import "../platform/popup/locales"; CurrentTabComponent, EnvironmentComponent, ExcludedDomainsV1Component, - Fido2CipherRowComponent, - Fido2UseBrowserLinkComponent, + Fido2CipherRowV1Component, + Fido2UseBrowserLinkV1Component, FolderAddEditComponent, FoldersComponent, VaultFilterComponent, @@ -180,7 +186,7 @@ import "../platform/popup/locales"; ViewCustomFieldsComponent, RemovePasswordComponent, VaultSelectComponent, - Fido2Component, + Fido2V1Component, AutofillV1Component, EnvironmentSelectorComponent, ], diff --git a/apps/browser/src/popup/scss/pages.scss b/apps/browser/src/popup/scss/pages.scss index 3ae36472996..bf8f03e7d03 100644 --- a/apps/browser/src/popup/scss/pages.scss +++ b/apps/browser/src/popup/scss/pages.scss @@ -217,7 +217,7 @@ app-vault-attachments { } } -app-fido2 { +app-fido2-v1 { .auth-wrapper { display: flex; flex-direction: column; From 53efb9a7c375712a80e091c192122bffa833ac34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:55:11 -0400 Subject: [PATCH 09/28] [deps] Platform: Update @types/node to v20.16.4 (#10843) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 12d141f59cd..12633b5976d 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.16.1", + "@types/node": "20.16.4", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,9 +106,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", - "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", + "version": "20.16.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.4.tgz", + "integrity": "sha512-ioyQ1zK9aGEomJ45zz8S8IdzElyxhvP1RVWnPrXDf6wFaUb+kk1tEcVVJkF7RPGM0VWI7cp5U57oCPIn5iN1qg==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index 757ce74b59f..595cb9c2e0e 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "20.16.1", + "@types/node": "20.16.4", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index 54e3a5cc6a7..6fde9b93f93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,7 +108,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.16.1", + "@types/node": "20.16.4", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -9401,9 +9401,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.16.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", - "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", + "version": "20.16.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.4.tgz", + "integrity": "sha512-ioyQ1zK9aGEomJ45zz8S8IdzElyxhvP1RVWnPrXDf6wFaUb+kk1tEcVVJkF7RPGM0VWI7cp5U57oCPIn5iN1qg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 722fd6c3e00..391474243b4 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "20.16.1", + "@types/node": "20.16.4", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", From 0419f91df6d1e620034ee5df57dd38893088a419 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 13:55:46 -0400 Subject: [PATCH 10/28] [deps]: Lock file maintenance (#10852) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/desktop_native/Cargo.lock | 28 +-- .../package-lock.json | 6 +- package-lock.json | 198 ++++++++---------- 3 files changed, 103 insertions(+), 129 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 3c0c5c41822..6c73c3622b4 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -440,9 +440,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c4eae4b7fc8dcb0032eb3b1beee46b38d371cdeaf2d0c64b9944f6f69ad7755" +checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" dependencies = [ "cc", "cxxbridge-flags", @@ -452,9 +452,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c822bf7fb755d97328d6c337120b6f843678178751cba33c9da25cf522272e0" +checksum = "c77953e99f01508f89f55c494bfa867171ef3a6c8cea03d26975368f2121a5c1" dependencies = [ "cc", "codespan-reporting", @@ -467,15 +467,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d6197dc016c88744aff3c0d0340a01ecce12e8939fc282e7c8f583ee64bc6" +checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" [[package]] name = "cxxbridge-macro" -version = "1.0.126" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35de3b547387863c8f82013c4f79f1c2162edee956383e4089e1d04c18c4f16c" +checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", @@ -896,9 +896,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1242,9 +1242,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1646,9 +1646,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 12633b5976d..3343b4b6024 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -241,9 +241,9 @@ "license": "MIT" }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" diff --git a/package-lock.json b/package-lock.json index 6fde9b93f93..cb0d391541d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -358,14 +358,14 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1802.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.1.tgz", - "integrity": "sha512-XTnJfCBMDQl3xF4w/eNrq821gbj2Ig1cqbzpRflhz4pqrANTAfHfPoIC7piWEZ60FNlHapzb6fvh6tJUGXG9og==", + "version": "0.1802.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.2.tgz", + "integrity": "sha512-LPRl9jhcf0NgshaL6RoUy1uL/cAyNt7oxctoZ9EHUu8eh5E9W/jZGhVowjOLpirwqYhmEzKJJIeS49Ssqs3RQg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@angular-devkit/core": "18.2.1", + "@angular-devkit/core": "18.2.2", "rxjs": "7.8.1" }, "engines": { @@ -1439,9 +1439,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "18.2.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.1.tgz", - "integrity": "sha512-fSuGj6CxiTFR+yjuVcaWqaVb5Wts39CSBYRO1BlsOlbuWFZ2NKC/BAb5bdxpB31heCBJi7e3XbPvcMMJIcnKlA==", + "version": "18.2.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.2.tgz", + "integrity": "sha512-Zz0tGptI/QQnUBDdp+1G5wGwQWMjpfe2oO+UohkrDVgFS71yVj4VDnOy51kMTxBvzw+36evTgthPpmzqPIfxBw==", "dev": true, "license": "MIT", "peer": true, @@ -1947,13 +1947,13 @@ } }, "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -2146,12 +2146,12 @@ } }, "node_modules/@babel/core/node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -2587,13 +2587,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "license": "MIT", "dependencies": { "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -2700,12 +2700,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.4.tgz", - "integrity": "sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4" + "@babel/types": "^7.25.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -2948,13 +2948,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", - "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz", + "integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -2964,13 +2964,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", - "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -4490,16 +4490,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.4.tgz", - "integrity": "sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.4", - "@babel/parser": "^7.25.4", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", "@babel/template": "^7.25.0", - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -4508,12 +4508,12 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.25.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.5.tgz", - "integrity": "sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.4", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -4537,9 +4537,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.4.tgz", - "integrity": "sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -8081,9 +8081,9 @@ } }, "node_modules/@storybook/angular/node_modules/@types/node": { - "version": "18.19.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz", - "integrity": "sha512-1f7dB3BL/bpd9tnDJrrHb66Y+cVrhxSOTGorRNdHwYTUlTay3HuTDPKo9a/4vX9pMQkhYBcAbL4jQdNlhCFP9A==", + "version": "18.19.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", + "integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", "dev": true, "license": "MIT", "dependencies": { @@ -8186,9 +8186,9 @@ } }, "node_modules/@storybook/builder-webpack5/node_modules/@types/node": { - "version": "18.19.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz", - "integrity": "sha512-1f7dB3BL/bpd9tnDJrrHb66Y+cVrhxSOTGorRNdHwYTUlTay3HuTDPKo9a/4vX9pMQkhYBcAbL4jQdNlhCFP9A==", + "version": "18.19.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", + "integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", "dev": true, "license": "MIT", "dependencies": { @@ -8395,9 +8395,9 @@ } }, "node_modules/@storybook/core-webpack/node_modules/@types/node": { - "version": "18.19.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz", - "integrity": "sha512-1f7dB3BL/bpd9tnDJrrHb66Y+cVrhxSOTGorRNdHwYTUlTay3HuTDPKo9a/4vX9pMQkhYBcAbL4jQdNlhCFP9A==", + "version": "18.19.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", + "integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", "dev": true, "license": "MIT", "dependencies": { @@ -8412,9 +8412,9 @@ "license": "MIT" }, "node_modules/@storybook/core/node_modules/@types/node": { - "version": "18.19.47", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.47.tgz", - "integrity": "sha512-1f7dB3BL/bpd9tnDJrrHb66Y+cVrhxSOTGorRNdHwYTUlTay3HuTDPKo9a/4vX9pMQkhYBcAbL4jQdNlhCFP9A==", + "version": "18.19.48", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.48.tgz", + "integrity": "sha512-7WevbG4ekUcRQSZzOwxWgi5dZmTak7FaxXDoW7xVxPBmKx1rTzfmRLkeCgJzcbBnOV2dkhAPc8cCeT6agocpjg==", "dev": true, "license": "MIT", "dependencies": { @@ -9517,9 +9517,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.4", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz", - "integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==", + "version": "18.3.5", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz", + "integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==", "dev": true, "license": "MIT", "dependencies": { @@ -12380,9 +12380,9 @@ } }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -13624,9 +13624,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001653", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz", - "integrity": "sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==", + "version": "1.0.30001655", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", + "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", "funding": [ { "type": "opencollective", @@ -17129,9 +17129,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -18917,9 +18917,9 @@ "license": "ISC" }, "node_modules/flow-parser": { - "version": "0.244.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.244.0.tgz", - "integrity": "sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA==", + "version": "0.245.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.245.0.tgz", + "integrity": "sha512-xUBkkpIDfDZHAebnDEX65FCVitJUctab82KFmtP5SY4cGly1vbuYNe6Muyp0NLXrgmBChVdoC2T+3/RUHi4Mww==", "dev": true, "license": "MIT", "engines": { @@ -19656,9 +19656,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", - "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.0.tgz", + "integrity": "sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==", "dev": true, "license": "MIT", "dependencies": { @@ -21732,9 +21732,9 @@ } }, "node_modules/i18next/node_modules/@babel/runtime": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", - "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", "dev": true, "license": "MIT", "dependencies": { @@ -35190,9 +35190,9 @@ } }, "node_modules/streamx": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.19.0.tgz", - "integrity": "sha512-5z6CNR4gtkPbwlxyEqoDGDmWIzoNJqCBt4Eac1ICP9YaIT08ct712cFj0u1rx4F8luAuL+3Qc+RFIdI4OX00kg==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", + "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -37224,9 +37224,9 @@ "license": "MIT" }, "node_modules/uglify-js": { - "version": "3.19.2", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", - "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==", + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -37589,14 +37589,13 @@ } }, "node_modules/unplugin": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.2.tgz", - "integrity": "sha512-bEqQxeC7rxtxPZ3M5V4Djcc4lQqKPgGe3mAWZvxcSmX5jhGxll19NliaRzQSQPrk4xJZSGniK3puLWpRuZN7VQ==", + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.3.tgz", + "integrity": "sha512-my8DH0/T/Kx33KO+6QXAqdeMYgyy0GktlOpdQjpagfHKw5DrD0ctPr7SHUyOT3g4ZVpzCQGt/qcpuoKJ/pniHA==", "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.12.1", - "chokidar": "^3.6.0", "webpack-sources": "^3.2.3", "webpack-virtual-modules": "^0.6.2" }, @@ -37604,31 +37603,6 @@ "node": ">=14.0.0" } }, - "node_modules/unplugin/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", From a5ea22d0ccd08f0080f959452d742a670ab79d60 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:33:43 -0400 Subject: [PATCH 11/28] [AC-2843] Member access report api setup (#10434) * Initial setup and modifications for member access report api implementation * Adding the permissions logic for getting the permissions text * fixing the test cases * Some refactoring on async calls * Comments on the model * Resolving the mock issue * Added functionality to edit members on MemberAccessReportComponent (#10618) * Added functionality to edit members on MemberAccessReportComponent * Fixed test cases --------- Co-authored-by: aj-rosado <109146700+aj-rosado@users.noreply.github.com> --- .../member-access-report.component.html | 2 +- .../member-access-report.component.ts | 61 +++- .../model/member-access-report.model.ts | 31 +- .../response/member-access-report.response.ts | 57 ++++ .../member-access-report-api.service.ts | 18 +- .../member-access-report.abstraction.ts | 4 +- .../services/member-access-report.mock.ts | 302 ++++++++++++------ .../member-access-report.service.spec.ts | 68 ++-- .../services/member-access-report.service.ts | 144 ++++----- .../view/member-access-report.view.ts | 4 + 10 files changed, 455 insertions(+), 236 deletions(-) create mode 100644 bitwarden_license/bit-web/src/app/tools/reports/member-access-report/response/member-access-report.response.ts diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html index 82ca34a89b8..b50f197a110 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.html @@ -32,7 +32,7 @@
- diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index a169d447011..c547c53d739 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -2,13 +2,22 @@ import { Component, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { debounceTime, firstValueFrom } from "rxjs"; +import { debounceTime, firstValueFrom, lastValueFrom } from "rxjs"; +import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe"; import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { BillingApiServiceAbstraction } from "@bitwarden/common/billing/abstractions"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { SearchModule, TableDataSource } from "@bitwarden/components"; +import { DialogService, SearchModule, TableDataSource } from "@bitwarden/components"; import { ExportHelper } from "@bitwarden/vault-export-core"; +import { CoreOrganizationModule } from "@bitwarden/web-vault/app/admin-console/organizations/core"; +import { + openUserAddEditDialog, + MemberDialogResult, + MemberDialogTab, +} from "@bitwarden/web-vault/app/admin-console/organizations/members/components/member-dialog"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { exportToCSV } from "@bitwarden/web-vault/app/tools/reports/report-utils"; @@ -22,12 +31,12 @@ import { MemberAccessReportView } from "./view/member-access-report.view"; @Component({ selector: "member-access-report", templateUrl: "member-access-report.component.html", - imports: [SharedModule, SearchModule, HeaderModule], + imports: [SharedModule, SearchModule, HeaderModule, CoreOrganizationModule], providers: [ safeProvider({ provide: MemberAccessReportServiceAbstraction, useClass: MemberAccessReportService, - deps: [MemberAccessReportApiService], + deps: [MemberAccessReportApiService, I18nService], }), ], standalone: true, @@ -36,11 +45,15 @@ export class MemberAccessReportComponent implements OnInit { protected dataSource = new TableDataSource(); protected searchControl = new FormControl("", { nonNullable: true }); protected organizationId: OrganizationId; + protected orgIsOnSecretsManagerStandalone: boolean; constructor( private route: ActivatedRoute, protected reportService: MemberAccessReportService, protected fileDownloadService: FileDownloadService, + protected dialogService: DialogService, + protected userNamePipe: UserNamePipe, + protected billingApiService: BillingApiServiceAbstraction, ) { // Connect the search input to the table dataSource filter input this.searchControl.valueChanges @@ -51,7 +64,20 @@ export class MemberAccessReportComponent implements OnInit { async ngOnInit() { const params = await firstValueFrom(this.route.params); this.organizationId = params.organizationId; - this.dataSource.data = this.reportService.generateMemberAccessReportView(); + + const billingMetadata = await this.billingApiService.getOrganizationBillingMetadata( + this.organizationId, + ); + + this.orgIsOnSecretsManagerStandalone = billingMetadata.isOnSecretsManagerStandalone; + + await this.load(); + } + + async load() { + this.dataSource.data = await this.reportService.generateMemberAccessReportView( + this.organizationId, + ); } exportReportAction = async (): Promise => { @@ -64,4 +90,29 @@ export class MemberAccessReportComponent implements OnInit { blobOptions: { type: "text/plain" }, }); }; + + edit = async (user: MemberAccessReportView | null): Promise => { + const dialog = openUserAddEditDialog(this.dialogService, { + data: { + name: this.userNamePipe.transform(user), + organizationId: this.organizationId, + organizationUserId: user != null ? user.userGuid : null, + allOrganizationUserEmails: this.dataSource.data?.map((user) => user.email) ?? [], + usesKeyConnector: user?.usesKeyConnector, + isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, + initialTab: MemberDialogTab.Role, + numConfirmedMembers: this.dataSource.data.length, + }, + }); + + const result = await lastValueFrom(dialog.closed); + switch (result) { + case MemberDialogResult.Deleted: + case MemberDialogResult.Saved: + case MemberDialogResult.Revoked: + case MemberDialogResult.Restored: + await this.load(); + return; + } + }; } diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/model/member-access-report.model.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/model/member-access-report.model.ts index 35f37eb45f8..dae70f26fbf 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/model/member-access-report.model.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/model/member-access-report.model.ts @@ -1,23 +1,16 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -export type MemberAccessCollectionModel = { - id: string; - name: EncString; +/** + * Details for the parents MemberAccessReport + */ +export type MemberAccessDetails = { + collectionId: string; + groupId: string; + groupName: string; + // Comes encrypted from the server + collectionName: EncString; itemCount: number; -}; - -export type MemberAccessGroupModel = { - id: string; - name: string; - itemCount: number; - collections: MemberAccessCollectionModel[]; -}; - -export type MemberAccessReportModel = { - userName: string; - email: string; - twoFactorEnabled: boolean; - accountRecoveryEnabled: boolean; - collections: MemberAccessCollectionModel[]; - groups: MemberAccessGroupModel[]; + readOnly: boolean; + hidePasswords: boolean; + manage: boolean; }; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/response/member-access-report.response.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/response/member-access-report.response.ts new file mode 100644 index 00000000000..959b70b9729 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/response/member-access-report.response.ts @@ -0,0 +1,57 @@ +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { Guid } from "@bitwarden/common/types/guid"; + +export class MemberAccessDetails extends BaseResponse { + collectionId: string; + groupId: string; + groupName: string; + collectionName: EncString; + itemCount: number; + readOnly: boolean; + hidePasswords: boolean; + manage: boolean; + + constructor(response: any) { + super(response); + this.groupId = this.getResponseProperty("GroupId"); + this.collectionId = this.getResponseProperty("CollectionId"); + this.groupName = this.getResponseProperty("GroupName"); + this.collectionName = new EncString(this.getResponseProperty("CollectionName")); + this.itemCount = this.getResponseProperty("ItemCount"); + this.readOnly = this.getResponseProperty("ReadOnly"); + this.hidePasswords = this.getResponseProperty("HidePasswords"); + this.manage = this.getResponseProperty("Manage"); + } +} + +export class MemberAccessResponse extends BaseResponse { + userName: string; + email: string; + twoFactorEnabled: boolean; + accountRecoveryEnabled: boolean; + collectionsCount: number; + groupsCount: number; + totalItemCount: number; + accessDetails: MemberAccessDetails[] = []; + userGuid: Guid; + usesKeyConnector: boolean; + + constructor(response: any) { + super(response); + this.userName = this.getResponseProperty("UserName"); + this.email = this.getResponseProperty("Email"); + this.twoFactorEnabled = this.getResponseProperty("TwoFactorEnabled"); + this.accountRecoveryEnabled = this.getResponseProperty("AccountRecoveryEnabled"); + this.collectionsCount = this.getResponseProperty("CollectionsCount"); + this.groupsCount = this.getResponseProperty("GroupsCount"); + this.totalItemCount = this.getResponseProperty("TotalItemCount"); + this.userGuid = this.getResponseProperty("UserGuid"); + this.usesKeyConnector = this.getResponseProperty("UsesKeyConnector"); + + const details = this.getResponseProperty("AccessDetails"); + if (details != null) { + this.accessDetails = details.map((o: any) => new MemberAccessDetails(o)); + } + } +} diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report-api.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report-api.service.ts index ad25308b61b..ef3e7b90f00 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report-api.service.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report-api.service.ts @@ -1,12 +1,22 @@ import { Injectable } from "@angular/core"; -import { MemberAccessReportModel } from "../model/member-access-report.model"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { memberAccessReportsMock } from "./member-access-report.mock"; +import { MemberAccessResponse } from "../response/member-access-report.response"; @Injectable({ providedIn: "root" }) export class MemberAccessReportApiService { - getMemberAccessData(): MemberAccessReportModel[] { - return memberAccessReportsMock; + constructor(protected apiService: ApiService) {} + async getMemberAccessData(orgId: string): Promise { + const response = await this.apiService.send( + "GET", + "/reports/member-access/" + orgId, + null, + true, + true, + ); + const memberAccessResponses = response.map((o: any) => new MemberAccessResponse(o)); + + return memberAccessResponses; } } diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts index f26961e11d6..5d17f8a0174 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.abstraction.ts @@ -4,7 +4,9 @@ import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; export abstract class MemberAccessReportServiceAbstraction { - generateMemberAccessReportView: () => MemberAccessReportView[]; + generateMemberAccessReportView: ( + organizationId: OrganizationId, + ) => Promise; generateUserReportExportItems: ( organizationId: OrganizationId, ) => Promise; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.mock.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.mock.ts index 4a0ad310c36..9ace555dd2e 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.mock.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.mock.ts @@ -1,137 +1,231 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { MemberAccessReportModel } from "../model/member-access-report.model"; +import { + MemberAccessDetails, + MemberAccessResponse, +} from "../response/member-access-report.response"; -export const memberAccessReportsMock: MemberAccessReportModel[] = [ +export const memberAccessReportsMock: MemberAccessResponse[] = [ { userName: "Sarah Johnson", email: "sjohnson@email.com", twoFactorEnabled: true, accountRecoveryEnabled: true, - collections: [ + groupsCount: 2, + collectionsCount: 4, + totalItemCount: 20, + userGuid: "1234", + usesKeyConnector: false, + accessDetails: [ { - id: "c1", - name: new EncString( - "2.UiXa3L3Ol1G4QnfFfBjMQw==|sbVTj0EiEkhIrDiropn2Cg==|82P78YgmapW4TdN9jQJgMWKv2gGyK1AnGkr+W9/sq+A=", - ), + groupId: "", + collectionId: "c1", + collectionName: new EncString("Collection 1"), + groupName: "", itemCount: 10, - }, - { id: "c2", name: new EncString("Collection 2"), itemCount: 20 }, - { id: "c3", name: new EncString("Collection 3"), itemCount: 30 }, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c2", + collectionName: new EncString("Collection 2"), + groupName: "", + itemCount: 20, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c3", + collectionName: new EncString("Collection 3"), + groupName: "", + itemCount: 30, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g1", + collectionId: "c1", + collectionName: new EncString("Collection 1"), + groupName: "Group 1", + itemCount: 30, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g1", + collectionId: "c2", + collectionName: new EncString("Collection 2"), + groupName: "Group 1", + itemCount: 20, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, ], - groups: [ - { - id: "g1", - name: "Group 1", - itemCount: 3, - collections: [ - { - id: "c6", - name: new EncString( - "2.UiXa3L3Ol1G4QnfFfBjMQw==|sbVTj0EiEkhIrDiropn2Cg==|82P78YgmapW4TdN9jQJgMWKv2gGyK1AnGkr+W9/sq+A=", - ), - itemCount: 10, - }, - { id: "c2", name: new EncString("Collection 2"), itemCount: 20 }, - ], - }, - { - id: "g2", - name: "Group 2", - itemCount: 2, - collections: [ - { id: "c2", name: new EncString("Collection 2"), itemCount: 20 }, - { id: "c3", name: new EncString("Collection 3"), itemCount: 30 }, - ], - }, - { - id: "g3", - name: "Group 3", - itemCount: 2, - collections: [ - { - id: "c1", - name: new EncString( - "2.UiXa3L3Ol1G4QnfFfBjMQw==|sbVTj0EiEkhIrDiropn2Cg==|82P78YgmapW4TdN9jQJgMWKv2gGyK1AnGkr+W9/sq+A=", - ), - itemCount: 10, - }, - { id: "c3", name: new EncString("Collection 3"), itemCount: 30 }, - ], - }, - ], - }, + } as MemberAccessResponse, { userName: "James Lull", email: "jlull@email.com", twoFactorEnabled: false, accountRecoveryEnabled: false, - collections: [ - { id: "c4", name: new EncString("Collection 4"), itemCount: 5 }, - { id: "c5", name: new EncString("Collection 5"), itemCount: 15 }, - ], - groups: [ + groupsCount: 2, + collectionsCount: 4, + totalItemCount: 20, + userGuid: "1234", + usesKeyConnector: false, + accessDetails: [ { - id: "g4", - name: "Group 4", - itemCount: 2, - collections: [ - { id: "c4", name: new EncString("Collection 4"), itemCount: 5 }, - { id: "c5", name: new EncString("Collection 5"), itemCount: 15 }, - ], - }, + groupId: "g4", + collectionId: "c4", + groupName: "Group 4", + collectionName: new EncString("Collection 4"), + itemCount: 5, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, { - id: "g5", - name: "Group 5", - itemCount: 1, - collections: [{ id: "c5", name: new EncString("Collection 5"), itemCount: 15 }], - }, + groupId: "g4", + collectionId: "c5", + groupName: "Group 4", + collectionName: new EncString("Collection 5"), + itemCount: 15, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c4", + groupName: "", + collectionName: new EncString("Collection 4"), + itemCount: 5, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c5", + groupName: "", + collectionName: new EncString("Collection 5"), + itemCount: 15, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, ], - }, + } as MemberAccessResponse, { userName: "Beth Williams", email: "bwilliams@email.com", twoFactorEnabled: true, accountRecoveryEnabled: true, - collections: [{ id: "c6", name: new EncString("Collection 6"), itemCount: 25 }], - groups: [ + groupsCount: 2, + collectionsCount: 4, + totalItemCount: 20, + userGuid: "1234", + usesKeyConnector: false, + accessDetails: [ { - id: "g6", - name: "Group 6", - itemCount: 1, - collections: [{ id: "c4", name: new EncString("Collection 4"), itemCount: 35 }], - }, + groupId: "", + collectionId: "c6", + groupName: "", + collectionName: new EncString("Collection 6"), + itemCount: 25, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g6", + collectionId: "c4", + groupName: "Group 6", + collectionName: new EncString("Collection 4"), + itemCount: 35, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, ], - }, + } as MemberAccessResponse, { userName: "Ray Williams", email: "rwilliams@email.com", twoFactorEnabled: false, accountRecoveryEnabled: false, - collections: [ - { id: "c7", name: new EncString("Collection 7"), itemCount: 8 }, - { id: "c8", name: new EncString("Collection 8"), itemCount: 12 }, - { id: "c9", name: new EncString("Collection 9"), itemCount: 16 }, + groupsCount: 2, + collectionsCount: 4, + totalItemCount: 20, + userGuid: "1234", + usesKeyConnector: false, + accessDetails: [ + { + groupId: "", + collectionId: "c7", + groupName: "", + collectionName: new EncString("Collection 7"), + itemCount: 8, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c8", + groupName: "", + collectionName: new EncString("Collection 8"), + itemCount: 12, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "", + collectionId: "c9", + groupName: "", + collectionName: new EncString("Collection 9"), + itemCount: 16, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g9", + collectionId: "c7", + groupName: "Group 9", + collectionName: new EncString("Collection 7"), + itemCount: 8, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g10", + collectionId: "c8", + groupName: "Group 10", + collectionName: new EncString("Collection 8"), + itemCount: 12, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, + { + groupId: "g11", + collectionId: "c9", + groupName: "Group 11", + collectionName: new EncString("Collection 9"), + itemCount: 16, + readOnly: false, + hidePasswords: false, + manage: false, + } as MemberAccessDetails, ], - groups: [ - { - id: "g9", - name: "Group 9", - itemCount: 1, - collections: [{ id: "c7", name: new EncString("Collection 7"), itemCount: 8 }], - }, - { - id: "g10", - name: "Group 10", - itemCount: 1, - collections: [{ id: "c8", name: new EncString("Collection 8"), itemCount: 12 }], - }, - { - id: "g11", - name: "Group 11", - itemCount: 1, - collections: [{ id: "c9", name: new EncString("Collection 9"), itemCount: 16 }], - }, - ], - }, + } as MemberAccessResponse, ]; diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts index 6a4e53ce233..20d33e314af 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.spec.ts @@ -1,5 +1,6 @@ import { mock } from "jest-mock-extended"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { OrganizationId } from "@bitwarden/common/src/types/guid"; import { MemberAccessReportApiService } from "./member-access-report-api.service"; @@ -9,44 +10,56 @@ describe("ImportService", () => { const mockOrganizationId = "mockOrgId" as OrganizationId; const reportApiService = mock(); let memberAccessReportService: MemberAccessReportService; + const i18nService = mock(); beforeEach(() => { - reportApiService.getMemberAccessData.mockImplementation(() => memberAccessReportsMock); - memberAccessReportService = new MemberAccessReportService(reportApiService); + reportApiService.getMemberAccessData.mockImplementation(() => + Promise.resolve(memberAccessReportsMock), + ); + memberAccessReportService = new MemberAccessReportService(reportApiService, i18nService); }); describe("generateMemberAccessReportView", () => { - it("should generate member access report view", () => { - const result = memberAccessReportService.generateMemberAccessReportView(); + it("should generate member access report view", async () => { + const result = + await memberAccessReportService.generateMemberAccessReportView(mockOrganizationId); expect(result).toEqual([ { name: "Sarah Johnson", email: "sjohnson@email.com", collectionsCount: 4, - groupsCount: 3, - itemsCount: 70, + groupsCount: 2, + itemsCount: 20, + userGuid: expect.any(String), + usesKeyConnector: expect.any(Boolean), }, { name: "James Lull", email: "jlull@email.com", - collectionsCount: 2, + collectionsCount: 4, groupsCount: 2, itemsCount: 20, + userGuid: expect.any(String), + usesKeyConnector: expect.any(Boolean), }, { name: "Beth Williams", email: "bwilliams@email.com", - collectionsCount: 2, - groupsCount: 1, - itemsCount: 60, + collectionsCount: 4, + groupsCount: 2, + itemsCount: 20, + userGuid: expect.any(String), + usesKeyConnector: expect.any(Boolean), }, { name: "Ray Williams", email: "rwilliams@email.com", - collectionsCount: 3, - groupsCount: 3, - itemsCount: 36, + collectionsCount: 4, + groupsCount: 2, + itemsCount: 20, + userGuid: expect.any(String), + usesKeyConnector: expect.any(Boolean), }, ]); }); @@ -57,7 +70,24 @@ describe("ImportService", () => { const result = await memberAccessReportService.generateUserReportExportItems(mockOrganizationId); - expect(result).toEqual( + const filteredReportItems = result + .filter( + (item) => + (item.name === "Sarah Johnson" && + item.group === "Group 1" && + item.totalItems === "20") || + (item.name === "James Lull" && item.group === "Group 4" && item.totalItems === "5"), + ) + .map((item) => ({ + name: item.name, + email: item.email, + group: item.group, + totalItems: item.totalItems, + accountRecovery: item.accountRecovery, + twoStepLogin: item.twoStepLogin, + })); + + expect(filteredReportItems).toEqual( expect.arrayContaining([ expect.objectContaining({ email: "sjohnson@email.com", @@ -65,19 +95,15 @@ describe("ImportService", () => { twoStepLogin: "On", accountRecovery: "On", group: "Group 1", - collection: expect.any(String), - collectionPermission: "read only", - totalItems: "10", + totalItems: "20", }), expect.objectContaining({ email: "jlull@email.com", name: "James Lull", twoStepLogin: "Off", accountRecovery: "Off", - group: "(No group)", - collection: expect.any(String), - collectionPermission: "read only", - totalItems: "15", + group: "Group 4", + totalItems: "5", }), ]), ); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts index 6f0cb926462..4ab7ffd6e42 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/services/member-access-report.service.ts @@ -1,16 +1,15 @@ import { Injectable } from "@angular/core"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { OrganizationId } from "@bitwarden/common/types/guid"; +import { CollectionAccessSelectionView } from "@bitwarden/web-vault/app/admin-console/organizations/core/views"; import { - collectProperty, - getUniqueItems, - sumValue, -} from "@bitwarden/web-vault/app/tools/reports/report-utils"; + getPermissionList, + convertToPermission, +} from "@bitwarden/web-vault/app/admin-console/organizations/shared/components/access-selector"; -import { - MemberAccessCollectionModel, - MemberAccessGroupModel, -} from "../model/member-access-report.model"; +import { MemberAccessDetails } from "../response/member-access-report.response"; import { MemberAccessExportItem } from "../view/member-access-export.view"; import { MemberAccessReportView } from "../view/member-access-report.view"; @@ -18,7 +17,10 @@ import { MemberAccessReportApiService } from "./member-access-report-api.service @Injectable({ providedIn: "root" }) export class MemberAccessReportService { - constructor(private reportApiService: MemberAccessReportApiService) {} + constructor( + private reportApiService: MemberAccessReportApiService, + private i18nService: I18nService, + ) {} /** * Transforms user data into a MemberAccessReportView. * @@ -26,88 +28,68 @@ export class MemberAccessReportService { * @param {ReportCollection[]} collections - An array of collections, each with an ID and a total number of items. * @returns {MemberAccessReportView} The aggregated report view. */ - generateMemberAccessReportView(): MemberAccessReportView[] { - const memberAccessReportViewCollection: MemberAccessReportView[] = []; - const memberAccessData = this.reportApiService.getMemberAccessData(); - memberAccessData.forEach((userData) => { - const name = userData.userName; - const email = userData.email; - const groupCollections = collectProperty< - MemberAccessGroupModel, - "collections", - MemberAccessCollectionModel - >(userData.groups, "collections"); - - const uniqueCollections = getUniqueItems( - [...groupCollections, ...userData.collections], - (item: MemberAccessCollectionModel) => item.id, - ); - const collectionsCount = uniqueCollections.length; - const groupsCount = userData.groups.length; - const itemsCount = sumValue( - uniqueCollections, - (collection: MemberAccessCollectionModel) => collection.itemCount, - ); - - memberAccessReportViewCollection.push({ - name: name, - email: email, - collectionsCount: collectionsCount, - groupsCount: groupsCount, - itemsCount: itemsCount, - }); - }); - + async generateMemberAccessReportView( + organizationId: OrganizationId, + ): Promise { + const memberAccessData = await this.reportApiService.getMemberAccessData(organizationId); + const memberAccessReportViewCollection = memberAccessData.map((userData) => ({ + name: userData.userName, + email: userData.email, + collectionsCount: userData.collectionsCount, + groupsCount: userData.groupsCount, + itemsCount: userData.totalItemCount, + userGuid: userData.userGuid, + usesKeyConnector: userData.usesKeyConnector, + })); return memberAccessReportViewCollection; } async generateUserReportExportItems( organizationId: OrganizationId, ): Promise { - const memberAccessReports = this.reportApiService.getMemberAccessData(); - const userReportItemPromises = memberAccessReports.flatMap(async (memberAccessReport) => { - const partialMemberReportItem: Partial = { - email: memberAccessReport.email, - name: memberAccessReport.userName, - twoStepLogin: memberAccessReport.twoFactorEnabled ? "On" : "Off", - accountRecovery: memberAccessReport.accountRecoveryEnabled ? "On" : "Off", - }; - const groupCollectionPromises = memberAccessReport.groups.map(async (group) => { - const groupPartialReportItem = { ...partialMemberReportItem, group: group.name }; - return await this.buildReportItemFromCollection( - group.collections, - groupPartialReportItem, - organizationId, - ); + const memberAccessReports = await this.reportApiService.getMemberAccessData(organizationId); + const collectionNames = memberAccessReports.flatMap((item) => + item.accessDetails.map((dtl) => { + if (dtl.collectionName) { + return dtl.collectionName.encryptedString; + } + }), + ); + const collectionNameMap = new Map(collectionNames.map((col) => [col, ""])); + for await (const key of collectionNameMap.keys()) { + const decrypted = new EncString(key); + await decrypted.decrypt(organizationId); + collectionNameMap.set(key, decrypted.decryptedValue); + } + + const exportItems = memberAccessReports.flatMap((report) => { + const userDetails = report.accessDetails.map((detail) => { + return { + email: report.email, + name: report.userName, + twoStepLogin: report.twoFactorEnabled ? "On" : "Off", + accountRecovery: report.accountRecoveryEnabled ? "On" : "Off", + group: detail.groupName, + collection: collectionNameMap.get(detail.collectionName.encryptedString), + collectionPermission: this.getPermissionText(detail), + totalItems: detail.itemCount.toString(), + }; }); - const noGroupPartialReportItem = { ...partialMemberReportItem, group: "(No group)" }; - const noGroupCollectionPromises = await this.buildReportItemFromCollection( - memberAccessReport.collections, - noGroupPartialReportItem, - organizationId, - ); - - return Promise.all([...groupCollectionPromises, noGroupCollectionPromises]); + return userDetails; }); - - const nestedUserReportItems = (await Promise.all(userReportItemPromises)).flat(); - return nestedUserReportItems.flat(); + return exportItems.flat(); } - async buildReportItemFromCollection( - memberAccessCollections: MemberAccessCollectionModel[], - partialReportItem: Partial, - organizationId: string, - ): Promise { - const reportItemPromises = memberAccessCollections.map(async (collection) => { - return { - ...partialReportItem, - collection: await collection.name.decrypt(organizationId), - collectionPermission: "read only", //TODO update this value - totalItems: collection.itemCount.toString(), - }; + private getPermissionText(accessDetails: MemberAccessDetails): string { + const permissionList = getPermissionList(); + const collectionSelectionView = new CollectionAccessSelectionView({ + id: accessDetails.groupId ?? accessDetails.collectionId, + readOnly: accessDetails.readOnly, + hidePasswords: accessDetails.hidePasswords, + manage: accessDetails.manage, }); - - return Promise.all(reportItemPromises); + return this.i18nService.t( + permissionList.find((p) => p.perm === convertToPermission(collectionSelectionView))?.labelId, + ); } } diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts index eeb8cfee4f9..5412babc0e4 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/view/member-access-report.view.ts @@ -1,7 +1,11 @@ +import { Guid } from "@bitwarden/common/types/guid"; + export type MemberAccessReportView = { name: string; email: string; collectionsCount: number; groupsCount: number; itemsCount: number; + userGuid: Guid; + usesKeyConnector: boolean; }; From b264a3f4424998a3d3f2853c7981c44bcf95f9c8 Mon Sep 17 00:00:00 2001 From: Ike <137194738+ike-kottlowski@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:49:18 -0700 Subject: [PATCH 12/28] toast is how showing; removed event emitter to use built in emitter in base class; (#10761) --- .../settings/two-factor-setup.component.ts | 2 +- .../settings/two-factor-webauthn.component.ts | 32 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/apps/web/src/app/auth/settings/two-factor-setup.component.ts b/apps/web/src/app/auth/settings/two-factor-setup.component.ts index 8be6c7a3cc2..3b8a9edd955 100644 --- a/apps/web/src/app/auth/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-setup.component.ts @@ -220,7 +220,7 @@ export class TwoFactorSetupComponent implements OnInit, OnDestroy { this.dialogService, { data: result }, ); - this.twoFactorSetupSubscription = webAuthnComp.componentInstance.onChangeStatus + this.twoFactorSetupSubscription = webAuthnComp.componentInstance.onUpdated .pipe(first(), takeUntil(this.destroy$)) .subscribe((enabled: boolean) => { webAuthnComp.close(); diff --git a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts b/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts index 9aeafaf2c65..6dfee920991 100644 --- a/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts +++ b/apps/web/src/app/auth/settings/two-factor-webauthn.component.ts @@ -1,5 +1,5 @@ import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog"; -import { Component, EventEmitter, Inject, NgZone, Output } from "@angular/core"; +import { Component, Inject, NgZone } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -33,7 +33,6 @@ interface Key { templateUrl: "two-factor-webauthn.component.html", }) export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { - @Output() onChangeStatus = new EventEmitter(); type = TwoFactorProviderType.WebAuthn; name: string; keys: Key[]; @@ -85,34 +84,33 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { // Should never happen. return Promise.reject(); } + return this.enable(); + }; + + protected async enable() { const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest); request.deviceResponse = this.webAuthnResponse; request.id = this.keyIdAvailable; request.name = this.formGroup.value.name; - return this.enableWebAuth(request); - }; - - private enableWebAuth(request: any) { - return super.enable(async () => { - this.formPromise = this.apiService.putTwoFactorWebAuthn(request); - const response = await this.formPromise; - this.processResponse(response); + const response = await this.apiService.putTwoFactorWebAuthn(request); + this.processResponse(response); + this.toastService.showToast({ + title: this.i18nService.t("success"), + message: this.i18nService.t("twoFactorProviderEnabled"), + variant: "success", }); + this.onUpdated.emit(response.enabled); } disable = async () => { - await this.disableWebAuth(); + await this.disableMethod(); if (!this.enabled) { - this.onChangeStatus.emit(this.enabled); + this.onUpdated.emit(this.enabled); this.dialogRef.close(); } }; - private async disableWebAuth() { - return super.disable(this.formPromise); - } - async remove(key: Key) { if (this.keysConfiguredCount <= 1 || key.removePromise != null) { return; @@ -208,7 +206,7 @@ export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent { } } this.enabled = response.enabled; - this.onChangeStatus.emit(this.enabled); + this.onUpdated.emit(this.enabled); } static open( From 55ffe9913d9d572405981424f9f088654df00bed Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 4 Sep 2024 16:14:58 -0400 Subject: [PATCH 13/28] fixed typography issues (#10886) --- .../trash-list-items-container.component.ts | 2 ++ apps/browser/src/vault/popup/settings/trash.component.html | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts index 1ec0f52aa6d..c5fea3c2b8c 100644 --- a/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -15,6 +15,7 @@ import { SectionComponent, SectionHeaderComponent, ToastService, + TypographyModule, } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -30,6 +31,7 @@ import { PasswordRepromptService } from "@bitwarden/vault"; SectionHeaderComponent, MenuModule, IconButtonModule, + TypographyModule, ], }) export class TrashListItemsContainerComponent { diff --git a/apps/browser/src/vault/popup/settings/trash.component.html b/apps/browser/src/vault/popup/settings/trash.component.html index ab3b6716504..146e4161671 100644 --- a/apps/browser/src/vault/popup/settings/trash.component.html +++ b/apps/browser/src/vault/popup/settings/trash.component.html @@ -5,7 +5,11 @@ - + {{ "trashWarning" | i18n }} From e594059865d169d0ef5121f1a6ea26d03c4d556a Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 4 Sep 2024 15:42:37 -0500 Subject: [PATCH 14/28] [PM-11474] Rework injection of delayed passkey script to address identified bugs (#10830) * [PM-11474] Delay cleanup of injected passkey script * [PM-11474] Incorporating changes to ensure passkeys function within Safari script effectively * [PM-11474] Fixing jest tests * [PM-11474] Fixing jest tests --- .../fido2-page-script-append.mv2.spec.ts | 22 ++++++++----- .../content/fido2-page-script-append.mv2.ts | 12 ++++--- .../fido2-page-script-delay-append.mv2.ts | 31 ++++++++++++------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index d53d9e685ed..f5f8dd770c7 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -4,12 +4,21 @@ describe("FIDO2 page-script for manifest v2", () => { let createdScriptElement: HTMLScriptElement; jest.spyOn(window.document, "createElement"); + beforeEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { Object.defineProperty(window.document, "contentType", { value: "text/html", writable: true }); jest.clearAllMocks(); + jest.clearAllTimers(); jest.resetModules(); }); + afterAll(() => { + jest.useRealTimers(); + }); + it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); @@ -19,7 +28,7 @@ describe("FIDO2 page-script for manifest v2", () => { }); it("appends the `page-script.js` file to the document head when the contentType is `text/html`", () => { - jest.spyOn(window.document.head, "insertBefore").mockImplementation((node) => { + jest.spyOn(window.document.head, "prepend").mockImplementation((node) => { createdScriptElement = node as HTMLScriptElement; return node; }); @@ -28,16 +37,13 @@ describe("FIDO2 page-script for manifest v2", () => { expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); - expect(window.document.head.insertBefore).toHaveBeenCalledWith( - expect.any(HTMLScriptElement), - window.document.head.firstChild, - ); + expect(window.document.head.prepend).toHaveBeenCalledWith(expect.any(HTMLScriptElement)); expect(createdScriptElement.src).toBe(`chrome-extension://id/${Fido2ContentScript.PageScript}`); }); it("appends the `page-script.js` file to the document element if the head is not available", () => { window.document.documentElement.removeChild(window.document.head); - jest.spyOn(window.document.documentElement, "insertBefore").mockImplementation((node) => { + jest.spyOn(window.document.documentElement, "prepend").mockImplementation((node) => { createdScriptElement = node as HTMLScriptElement; return node; }); @@ -46,9 +52,8 @@ describe("FIDO2 page-script for manifest v2", () => { expect(window.document.createElement).toHaveBeenCalledWith("script"); expect(chrome.runtime.getURL).toHaveBeenCalledWith(Fido2ContentScript.PageScript); - expect(window.document.documentElement.insertBefore).toHaveBeenCalledWith( + expect(window.document.documentElement.prepend).toHaveBeenCalledWith( expect.any(HTMLScriptElement), - window.document.documentElement.firstChild, ); expect(createdScriptElement.src).toBe(`chrome-extension://id/${Fido2ContentScript.PageScript}`); }); @@ -63,6 +68,7 @@ describe("FIDO2 page-script for manifest v2", () => { jest.spyOn(createdScriptElement, "remove"); createdScriptElement.dispatchEvent(new Event("load")); + jest.runAllTimers(); expect(createdScriptElement.remove).toHaveBeenCalled(); }); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts index 4e806d29908..e5280c088bc 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.ts @@ -2,18 +2,20 @@ * This script handles injection of the FIDO2 override page script into the document. * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. */ -import { Fido2ContentScript } from "../enums/fido2-content-script.enum"; - (function (globalContext) { if (globalContext.document.contentType !== "text/html") { return; } const script = globalContext.document.createElement("script"); - script.src = chrome.runtime.getURL(Fido2ContentScript.PageScript); - script.addEventListener("load", () => script.remove()); + script.src = chrome.runtime.getURL("content/fido2-page-script.js"); + script.addEventListener("load", removeScriptOnLoad); const scriptInsertionPoint = globalContext.document.head || globalContext.document.documentElement; - scriptInsertionPoint.insertBefore(script, scriptInsertionPoint.firstChild); + scriptInsertionPoint.prepend(script); + + function removeScriptOnLoad() { + globalThis.setTimeout(() => script?.remove(), 5000); + } })(globalThis); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts index 4afeb76a0d3..c75a37c1b65 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-delay-append.mv2.ts @@ -2,26 +2,35 @@ * This script handles injection of the FIDO2 override page script into the document. * This is required for manifest v2, but will be removed when we migrate fully to manifest v3. */ -import { Fido2ContentScript } from "../enums/fido2-content-script.enum"; - (function (globalContext) { if (globalContext.document.contentType !== "text/html") { return; } - if (globalContext.document.readyState === "complete") { - loadScript(); + const script = globalContext.document.createElement("script"); + script.src = chrome.runtime.getURL("content/fido2-page-script.js"); + script.addEventListener("load", removeScriptOnLoad); + + // We are ensuring that the script injection is delayed in the event that we are loading + // within an iframe element. This prevents an issue with web mail clients that load content + // using ajax within iframes. In particular, Zimbra web mail client was observed to have this issue. + // @see https://github.com/bitwarden/clients/issues/9618 + const delayScriptInjection = + globalContext.window.top !== globalContext.window && + globalContext.document.readyState !== "complete"; + if (delayScriptInjection) { + globalContext.document.addEventListener("DOMContentLoaded", injectScript); } else { - globalContext.addEventListener("DOMContentLoaded", loadScript); + injectScript(); } - function loadScript() { - const script = globalContext.document.createElement("script"); - script.src = chrome.runtime.getURL(Fido2ContentScript.PageScript); - script.addEventListener("load", () => script.remove()); - + function injectScript() { const scriptInsertionPoint = globalContext.document.head || globalContext.document.documentElement; - scriptInsertionPoint.insertBefore(script, scriptInsertionPoint.firstChild); + scriptInsertionPoint.prepend(script); + } + + function removeScriptOnLoad() { + globalThis.setTimeout(() => script?.remove(), 5000); } })(globalThis); From 1a1d9e2d76353e3b0563f67c3a95f4cef0b566f2 Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 4 Sep 2024 15:42:52 -0500 Subject: [PATCH 15/28] [PM-11386] Limit presentation of inline menu for identities strictly to fields that contain valid autocomplete attribute (#10865) * [PM-11386] Limit presentation of inline menu for identities strictly to fields that contain valid autocomplete attribute * [PM-11386] Limit presentation of inline menu for identities strictly to fields that contain valid autocomplete attribute --- ...inline-menu-field-qualification.service.ts | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts index f27e2faf3fa..089c48cd230 100644 --- a/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts +++ b/apps/browser/src/autofill/services/inline-menu-field-qualification.service.ts @@ -122,29 +122,9 @@ export class InlineMenuFieldQualificationService ...this.identityAddressAutoCompleteValues, ...this.identityCountryAutocompleteValues, ...this.identityPhoneNumberAutocompleteValues, + this.identityCompanyAutocompleteValue, this.identityPostalCodeAutocompleteValue, ]); - private identityFieldKeywords = [ - ...new Set([ - ...IdentityAutoFillConstants.TitleFieldNames, - ...IdentityAutoFillConstants.FullNameFieldNames, - ...IdentityAutoFillConstants.FirstnameFieldNames, - ...IdentityAutoFillConstants.MiddlenameFieldNames, - ...IdentityAutoFillConstants.LastnameFieldNames, - ...IdentityAutoFillConstants.AddressFieldNames, - ...IdentityAutoFillConstants.Address1FieldNames, - ...IdentityAutoFillConstants.Address2FieldNames, - ...IdentityAutoFillConstants.Address3FieldNames, - ...IdentityAutoFillConstants.PostalCodeFieldNames, - ...IdentityAutoFillConstants.CityFieldNames, - ...IdentityAutoFillConstants.StateFieldNames, - ...IdentityAutoFillConstants.CountryFieldNames, - ...IdentityAutoFillConstants.CompanyFieldNames, - ...IdentityAutoFillConstants.PhoneFieldNames, - ...IdentityAutoFillConstants.EmailFieldNames, - ...IdentityAutoFillConstants.UserNameFieldNames, - ]), - ]; private inlineMenuFieldQualificationFlagSet = false; constructor() { @@ -288,14 +268,7 @@ export class InlineMenuFieldQualificationService return false; } - if (this.fieldContainsAutocompleteValues(field, this.identityAutocompleteValues)) { - return true; - } - - return ( - !this.fieldContainsAutocompleteValues(field, this.autocompleteDisabledValues) && - this.keywordsFoundInFieldData(field, this.identityFieldKeywords, false) - ); + return this.fieldContainsAutocompleteValues(field, this.identityAutocompleteValues); } /** From 2f69228c2181b27dd311b3010c12bb8c712441da Mon Sep 17 00:00:00 2001 From: Cesar Gonzalez Date: Wed, 4 Sep 2024 15:44:08 -0500 Subject: [PATCH 16/28] [PM-11656] Inline menu updates with empty list of ciphers (#10891) --- apps/browser/src/autofill/background/overlay.background.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/autofill/background/overlay.background.ts b/apps/browser/src/autofill/background/overlay.background.ts index a209523dc7c..0c626c68794 100644 --- a/apps/browser/src/autofill/background/overlay.background.ts +++ b/apps/browser/src/autofill/background/overlay.background.ts @@ -139,7 +139,7 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.triggerDestroyInlineMenuListeners(sender.tab, message.subFrameData.frameId), collectPageDetailsResponse: ({ message, sender }) => this.storePageDetails(message, sender), unlockCompleted: ({ message }) => this.unlockCompleted(message), - doFullSync: () => this.updateOverlayCiphers(true), + doFullSync: () => this.updateOverlayCiphers(), addedCipher: () => this.updateOverlayCiphers(), addEditCipherSubmitted: () => this.updateOverlayCiphers(), editedCipher: () => this.updateOverlayCiphers(), @@ -272,7 +272,10 @@ export class OverlayBackground implements OverlayBackgroundInterface { this.closeInlineMenuAfterCiphersUpdate().catch((error) => this.logService.error(error)); } - if (!currentTab) { + if (!currentTab || !currentTab.url?.startsWith("http")) { + if (updateAllCipherTypes) { + this.cardAndIdentityCiphers = null; + } return; } From 834462318505e8dfb140499b2c4658283d97f1ee Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:21:26 +1000 Subject: [PATCH 17/28] [PM-11450] Move organization-user domain to admin-console lib (#10785) - move organization-user files from libs/common/src/admin-console into libs/admin-console/src/common - add barrel files and update imports to use barrel files - rename OrganizationUserService to OrganizationUserApiService - rename OrganizationUserServiceImplementation to DefaultOrganizationUserApiService --- apps/browser/tsconfig.json | 2 +- .../admin-console/commands/confirm.command.ts | 12 ++++++---- apps/cli/src/commands/list.command.ts | 6 ++--- apps/cli/src/oss-serve-configurator.ts | 4 ++-- .../service-container/service-container.ts | 10 ++++---- apps/cli/src/vault.program.ts | 4 ++-- apps/cli/tsconfig.json | 1 + .../src/app/services/services.module.ts | 4 ++-- .../src/auth/set-password.component.ts | 6 ++--- apps/desktop/tsconfig.json | 2 +- .../core/services/user-admin.service.ts | 18 ++++++++------ .../core/views/organization-user.view.ts | 2 +- .../manage/entity-events.component.ts | 8 ++++--- .../organizations/manage/events.component.ts | 6 ++--- .../manage/group-add-edit.component.ts | 6 ++--- .../bulk/base-bulk-confirm.component.ts | 2 +- .../bulk/base-bulk-remove.component.ts | 2 +- .../components/bulk/bulk-confirm.component.ts | 12 ++++++---- .../bulk/bulk-enable-sm-dialog.component.ts | 6 ++--- .../components/bulk/bulk-remove.component.ts | 6 ++--- .../bulk/bulk-restore-revoke.component.ts | 8 +++---- .../components/bulk/bulk-status.component.ts | 2 +- .../member-dialog/member-dialog.component.ts | 10 ++++---- .../members/members.component.ts | 24 ++++++++++--------- ...zation-user-reset-password.service.spec.ts | 18 +++++++------- ...rganization-user-reset-password.service.ts | 20 +++++++++------- .../access-selector/access-selector.models.ts | 2 +- .../enroll-master-password-reset.component.ts | 10 ++++---- .../request/update-key.request.ts | 2 +- .../user-key-rotation.service.spec.ts | 2 +- .../accept-organization.service.spec.ts | 20 ++++++++-------- .../accept-organization.service.ts | 14 +++++------ apps/web/src/app/core/core.module.ts | 4 ++-- .../collection-dialog.component.ts | 10 ++++---- .../organization-options.component.ts | 21 +++++++++------- .../bulk-collections-dialog.component.ts | 6 ++--- .../app/vault/org-vault/vault.component.ts | 10 ++++---- apps/web/tsconfig.json | 2 +- .../bit-cli/src/service-container.ts | 2 +- bitwarden_license/bit-cli/tsconfig.json | 1 + .../organization-auth-request.service.spec.ts | 16 +++++++------ .../organization-auth-request.service.ts | 12 ++++++---- bitwarden_license/bit-common/tsconfig.json | 2 +- .../device-approvals.component.ts | 4 ++-- .../dialogs/bulk-confirm-dialog.component.ts | 4 ++-- bitwarden_license/bit-web/tsconfig.json | 2 +- libs/admin-console/src/common/index.ts | 1 + .../organization-user/abstractions/index.ts | 1 + .../organization-user-api.service.ts} | 8 +++---- .../src/common/organization-user/index.ts | 3 +++ .../common/organization-user/models/index.ts | 2 ++ .../models}/requests/index.ts | 1 + .../organization-user-accept-init.request.ts | 2 +- .../organization-user-accept.request.ts | 0 .../organization-user-bulk-confirm.request.ts | 0 .../organization-user-bulk.request.ts | 0 .../organization-user-confirm.request.ts | 0 .../organization-user-invite.request.ts | 12 ++++++++++ ...-user-reset-password-enrollment.request.ts | 2 +- ...rganization-user-reset-password.request.ts | 0 .../organization-user-update.request.ts | 11 +++++++++ .../models}/responses/index.ts | 0 ...anization-user-bulk-public-key.response.ts | 2 +- .../organization-user-bulk.response.ts | 2 +- .../responses/organization-user.response.ts | 13 ++++++---- .../default-organization-user-api.service.ts} | 16 ++++++------- .../organization-user/services/index.ts | 1 + libs/admin-console/src/index.ts | 0 ...base-login-decryption-options.component.ts | 4 ++-- .../auth/components/set-password.component.ts | 10 ++++---- .../src/services/jslib-services.module.ts | 14 ++++++----- .../default-set-password-jit.service.spec.ts | 16 +++++++------ .../default-set-password-jit.service.ts | 10 ++++---- .../organization-user-invite.request.ts | 12 ---------- .../organization-user-update.request.ts | 11 --------- .../provider-user-bulk-public-key.response.ts | 2 +- .../organization-user/requests/index.ts | 1 - ...update-tde-offboarding-password.request.ts | 2 +- .../request/update-temp-password.request.ts | 2 +- ...-enrollment.service.implementation.spec.ts | 13 +++++----- ...reset-enrollment.service.implementation.ts | 11 +++++---- libs/shared/tsconfig.libs.json | 2 +- tsconfig.json | 2 +- 83 files changed, 297 insertions(+), 239 deletions(-) create mode 100644 libs/admin-console/src/common/index.ts create mode 100644 libs/admin-console/src/common/organization-user/abstractions/index.ts rename libs/{common/src/admin-console/abstractions/organization-user/organization-user.service.ts => admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts} (98%) create mode 100644 libs/admin-console/src/common/organization-user/index.ts create mode 100644 libs/admin-console/src/common/organization-user/models/index.ts rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/index.ts (90%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-accept-init.request.ts (55%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-accept.request.ts (100%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-bulk-confirm.request.ts (100%) rename libs/{common/src/admin-console/services/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-bulk.request.ts (100%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-confirm.request.ts (100%) create mode 100644 libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-reset-password-enrollment.request.ts (70%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/requests/organization-user-reset-password.request.ts (100%) create mode 100644 libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/responses/index.ts (100%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/responses/organization-user-bulk-public-key.response.ts (80%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/responses/organization-user-bulk.response.ts (76%) rename libs/{common/src/admin-console/abstractions/organization-user => admin-console/src/common/organization-user/models}/responses/organization-user.response.ts (85%) rename libs/{common/src/admin-console/services/organization-user/organization-user.service.implementation.ts => admin-console/src/common/organization-user/services/default-organization-user-api.service.ts} (94%) create mode 100644 libs/admin-console/src/common/organization-user/services/index.ts delete mode 100644 libs/admin-console/src/index.ts delete mode 100644 libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-invite.request.ts delete mode 100644 libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-update.request.ts delete mode 100644 libs/common/src/admin-console/services/organization-user/requests/index.ts diff --git a/apps/browser/tsconfig.json b/apps/browser/tsconfig.json index b44d994f4e7..a6119a2e0a7 100644 --- a/apps/browser/tsconfig.json +++ b/apps/browser/tsconfig.json @@ -12,7 +12,7 @@ "baseUrl": ".", "lib": ["ES2021.String"], "paths": { - "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], diff --git a/apps/cli/src/admin-console/commands/confirm.command.ts b/apps/cli/src/admin-console/commands/confirm.command.ts index c6d9e4bd574..066cca48f13 100644 --- a/apps/cli/src/admin-console/commands/confirm.command.ts +++ b/apps/cli/src/admin-console/commands/confirm.command.ts @@ -1,6 +1,8 @@ +import { + OrganizationUserApiService, + OrganizationUserConfirmRequest, +} from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -10,7 +12,7 @@ export class ConfirmCommand { constructor( private apiService: ApiService, private cryptoService: CryptoService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, ) {} async run(object: string, id: string, cmdOptions: Record): Promise { @@ -42,7 +44,7 @@ export class ConfirmCommand { if (orgKey == null) { throw new Error("No encryption key for this organization."); } - const orgUser = await this.organizationUserService.getOrganizationUser( + const orgUser = await this.organizationUserApiService.getOrganizationUser( options.organizationId, id, ); @@ -54,7 +56,7 @@ export class ConfirmCommand { const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey); const req = new OrganizationUserConfirmRequest(); req.key = key.encryptedString; - await this.organizationUserService.postOrganizationUserConfirm( + await this.organizationUserApiService.postOrganizationUserConfirm( options.organizationId, id, req, diff --git a/apps/cli/src/commands/list.command.ts b/apps/cli/src/commands/list.command.ts index 88574635e1c..692a5c9bab3 100644 --- a/apps/cli/src/commands/list.command.ts +++ b/apps/cli/src/commands/list.command.ts @@ -1,10 +1,10 @@ import { firstValueFrom } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { EventType } from "@bitwarden/common/enums"; import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -35,7 +35,7 @@ export class ListCommand { private collectionService: CollectionService, private organizationService: OrganizationService, private searchService: SearchService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private apiService: ApiService, private eventCollectionService: EventCollectionService, ) {} @@ -211,7 +211,7 @@ export class ListCommand { } try { - const response = await this.organizationUserService.getAllUsers(options.organizationId); + const response = await this.organizationUserApiService.getAllUsers(options.organizationId); const res = new ListResponse( response.data.map((r) => { const u = new OrganizationUserResponse(); diff --git a/apps/cli/src/oss-serve-configurator.ts b/apps/cli/src/oss-serve-configurator.ts index 6e0fa1c43c3..d7ef9ac871d 100644 --- a/apps/cli/src/oss-serve-configurator.ts +++ b/apps/cli/src/oss-serve-configurator.ts @@ -71,7 +71,7 @@ export class OssServeConfigurator { this.serviceContainer.collectionService, this.serviceContainer.organizationService, this.serviceContainer.searchService, - this.serviceContainer.organizationUserService, + this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, ); @@ -114,7 +114,7 @@ export class OssServeConfigurator { this.confirmCommand = new ConfirmCommand( this.serviceContainer.apiService, this.serviceContainer.cryptoService, - this.serviceContainer.organizationUserService, + this.serviceContainer.organizationUserApiService, ); this.restoreCommand = new RestoreCommand(this.serviceContainer.cipherService); this.shareCommand = new ShareCommand( diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index c3fd55fa8b2..fb77e41a4b6 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -4,6 +4,10 @@ import * as path from "path"; import * as jsdom from "jsdom"; import { firstValueFrom } from "rxjs"; +import { + OrganizationUserApiService, + DefaultOrganizationUserApiService, +} from "@bitwarden/admin-console/common"; import { InternalUserDecryptionOptionsServiceAbstraction, AuthRequestService, @@ -16,12 +20,10 @@ import { import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction"; import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service"; import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service"; -import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; @@ -184,7 +186,7 @@ export class ServiceContainer { environmentService: EnvironmentService; cipherService: CipherService; folderService: InternalFolderService; - organizationUserService: OrganizationUserService; + organizationUserApiService: OrganizationUserApiService; collectionService: CollectionService; vaultTimeoutService: VaultTimeoutService; masterPasswordService: InternalMasterPasswordServiceAbstraction; @@ -492,7 +494,7 @@ export class ServiceContainer { this.providerService = new ProviderService(this.stateProvider); - this.organizationUserService = new OrganizationUserServiceImplementation(this.apiService); + this.organizationUserApiService = new DefaultOrganizationUserApiService(this.apiService); this.policyApiService = new PolicyApiService(this.policyService, this.apiService); diff --git a/apps/cli/src/vault.program.ts b/apps/cli/src/vault.program.ts index 9cf30086166..2dad9a7c68a 100644 --- a/apps/cli/src/vault.program.ts +++ b/apps/cli/src/vault.program.ts @@ -108,7 +108,7 @@ export class VaultProgram extends BaseProgram { this.serviceContainer.collectionService, this.serviceContainer.organizationService, this.serviceContainer.searchService, - this.serviceContainer.organizationUserService, + this.serviceContainer.organizationUserApiService, this.serviceContainer.apiService, this.serviceContainer.eventCollectionService, ); @@ -412,7 +412,7 @@ export class VaultProgram extends BaseProgram { const command = new ConfirmCommand( this.serviceContainer.apiService, this.serviceContainer.cryptoService, - this.serviceContainer.organizationUserService, + this.serviceContainer.organizationUserApiService, ); const response = await command.run(object, id, cmd); this.processResponse(response); diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 0a34b05496f..d84dcdaf675 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -13,6 +13,7 @@ "baseUrl": ".", "paths": { "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], "@bitwarden/common/*": ["../../libs/common/src/*"], diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index be110be138b..d4b51ca1c7e 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -1,6 +1,7 @@ import { APP_INITIALIZER, NgModule } from "@angular/core"; import { Subject, merge } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { SECURE_STORAGE, @@ -26,7 +27,6 @@ import { import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -285,7 +285,7 @@ const safeProviders: SafeProvider[] = [ KdfConfigService, InternalMasterPasswordServiceAbstraction, OrganizationApiServiceAbstraction, - OrganizationUserService, + OrganizationUserApiService, InternalUserDecryptionOptionsServiceAbstraction, ], }), diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 28f1f69a598..21bc7e8db14 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -1,11 +1,11 @@ import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/angular/auth/components/set-password.component"; import { InternalUserDecryptionOptionsServiceAbstraction } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -50,7 +50,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On private ngZone: NgZone, stateService: StateService, organizationApiService: OrganizationApiServiceAbstraction, - organizationUserService: OrganizationUserService, + organizationUserApiService: OrganizationUserApiService, userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction, dialogService: DialogService, @@ -74,7 +74,7 @@ export class SetPasswordComponent extends BaseSetPasswordComponent implements On route, stateService, organizationApiService, - organizationUserService, + organizationUserApiService, userDecryptionOptionsService, ssoLoginService, dialogService, diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 5ffdb3c2076..19f7b8bf70f 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -10,7 +10,7 @@ "types": [], "baseUrl": ".", "paths": { - "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], diff --git a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts index 52a522c89da..9741758e1e0 100644 --- a/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts +++ b/apps/web/src/app/admin-console/organizations/core/services/user-admin.service.ts @@ -1,11 +1,11 @@ import { Injectable } from "@angular/core"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { + OrganizationUserApiService, OrganizationUserInviteRequest, OrganizationUserUpdateRequest, -} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; -import { OrganizationUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; + OrganizationUserDetailsResponse, +} from "@bitwarden/admin-console/common"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CoreOrganizationModule } from "../core-organization.module"; @@ -15,14 +15,14 @@ import { OrganizationUserAdminView } from "../views/organization-user-admin-view export class UserAdminService { constructor( private configService: ConfigService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, ) {} async get( organizationId: string, organizationUserId: string, ): Promise { - const userResponse = await this.organizationUserService.getOrganizationUser( + const userResponse = await this.organizationUserApiService.getOrganizationUser( organizationId, organizationUserId, { @@ -47,7 +47,11 @@ export class UserAdminService { request.groups = user.groups; request.accessSecretsManager = user.accessSecretsManager; - await this.organizationUserService.putOrganizationUser(user.organizationId, user.id, request); + await this.organizationUserApiService.putOrganizationUser( + user.organizationId, + user.id, + request, + ); } async invite(emails: string[], user: OrganizationUserAdminView): Promise { @@ -59,7 +63,7 @@ export class UserAdminService { request.groups = user.groups; request.accessSecretsManager = user.accessSecretsManager; - await this.organizationUserService.postOrganizationUserInvite(user.organizationId, request); + await this.organizationUserApiService.postOrganizationUserInvite(user.organizationId, request); } private async decryptMany( diff --git a/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts b/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts index 86d1f4ded6b..8988f41487c 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/organization-user.view.ts @@ -1,4 +1,4 @@ -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType, OrganizationUserType, diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index c9af2c8b5de..79ada2b7a53 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -2,9 +2,9 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; +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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { EventResponse } from "@bitwarden/common/models/response/event.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { EventView } from "@bitwarden/common/models/view/event.view"; @@ -60,7 +60,7 @@ export class EntityEventsComponent implements OnInit { private platformUtilsService: PlatformUtilsService, private userNamePipe: UserNamePipe, private logService: LogService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private formBuilder: FormBuilder, private validationService: ValidationService, private toastService: ToastService, @@ -78,7 +78,9 @@ export class EntityEventsComponent implements OnInit { async load() { try { if (this.showUser) { - const response = await this.organizationUserService.getAllUsers(this.params.organizationId); + const response = await this.organizationUserApiService.getAllUsers( + this.params.organizationId, + ); response.data.forEach((u) => { const name = this.userNamePipe.transform(u); this.orgUsersIdMap.set(u.id, { name: name, email: u.email }); 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 0b7e3d42295..574335125e6 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,10 +2,10 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { concatMap, 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { ProviderService } from "@bitwarden/common/admin-console/abstractions/provider.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { EventSystemUser } from "@bitwarden/common/enums"; @@ -49,7 +49,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe logService: LogService, private userNamePipe: UserNamePipe, private organizationService: OrganizationService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private providerService: ProviderService, fileDownloadService: FileDownloadService, toastService: ToastService, @@ -83,7 +83,7 @@ export class EventsComponent extends BaseEventsComponent implements OnInit, OnDe } async load() { - const response = await this.organizationUserService.getAllUsers(this.organizationId); + const response = await this.organizationUserApiService.getAllUsers(this.organizationId); response.data.forEach((u) => { const name = this.userNamePipe.transform(u); this.orgUsersUserIdMap.set(u.userId, { name: name, email: u.email }); 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 82fa85476f2..36489e0ab1d 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 @@ -14,9 +14,9 @@ import { takeUntil, } from "rxjs"; +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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; 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"; @@ -131,7 +131,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { ); private get orgMembers$(): Observable> { - return from(this.organizationUserService.getAllUsers(this.organizationId)).pipe( + return from(this.organizationUserApiService.getAllUsers(this.organizationId)).pipe( map((response) => response.data.map((m) => ({ id: m.id, @@ -202,7 +202,7 @@ export class GroupAddEditComponent implements OnInit, OnDestroy { @Inject(DIALOG_DATA) private params: GroupAddEditDialogParams, private dialogRef: DialogRef, private apiService: ApiService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private groupService: GroupService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts index 8d634c38e05..5a5da935a66 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-confirm.component.ts @@ -3,7 +3,7 @@ import { Directive, OnInit } from "@angular/core"; import { OrganizationUserBulkPublicKeyResponse, OrganizationUserBulkResponse, -} from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +} from "@bitwarden/admin-console/common"; import { ProviderUserBulkPublicKeyResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk-public-key.response"; import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-remove.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-remove.component.ts index 6c736346604..80514e85995 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-remove.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/base-bulk-remove.component.ts @@ -1,6 +1,6 @@ import { Directive } from "@angular/core"; -import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common"; import { ProviderUserBulkResponse } from "@bitwarden/common/admin-console/models/response/provider/provider-user-bulk.response"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm.component.ts index d94edd55f85..14653169338 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm.component.ts @@ -1,9 +1,11 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; +import { + OrganizationUserApiService, + OrganizationUserBulkConfirmRequest, +} from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserBulkConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -40,7 +42,7 @@ export class BulkConfirmComponent implements OnInit { @Inject(DIALOG_DATA) protected data: BulkConfirmDialogData, protected cryptoService: CryptoService, protected apiService: ApiService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private i18nService: I18nService, ) { this.organizationId = data.organizationId; @@ -104,7 +106,7 @@ export class BulkConfirmComponent implements OnInit { } protected async getPublicKeys() { - return await this.organizationUserService.postOrganizationUsersPublicKey( + return await this.organizationUserApiService.postOrganizationUsersPublicKey( this.organizationId, this.filteredUsers.map((user) => user.id), ); @@ -116,7 +118,7 @@ export class BulkConfirmComponent implements OnInit { protected async postConfirmRequest(userIdsWithKeys: any[]) { const request = new OrganizationUserBulkConfirmRequest(userIdsWithKeys); - return await this.organizationUserService.postOrganizationUserBulkConfirm( + return await this.organizationUserApiService.postOrganizationUserBulkConfirm( this.organizationId, request, ); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts index de12d4f26d7..4b7b41a5c8c 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-enable-sm-dialog.component.ts @@ -1,7 +1,7 @@ import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { DialogService, TableDataSource, ToastService } from "@bitwarden/components"; @@ -21,7 +21,7 @@ export class BulkEnableSecretsManagerDialogComponent implements OnInit { constructor( public dialogRef: DialogRef, @Inject(DIALOG_DATA) private data: BulkEnableSecretsManagerDialogData, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private toastService: ToastService, @@ -32,7 +32,7 @@ export class BulkEnableSecretsManagerDialogComponent implements OnInit { } submit = async () => { - await this.organizationUserService.putOrganizationUserBulkEnableSecretsManager( + await this.organizationUserApiService.putOrganizationUserBulkEnableSecretsManager( this.data.orgId, this.dataSource.data.map((u) => u.id), ); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove.component.ts index 60d1aec41c6..74939238fcc 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove.component.ts @@ -1,8 +1,8 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; @@ -33,7 +33,7 @@ export class BulkRemoveComponent { @Inject(DIALOG_DATA) protected data: BulkRemoveDialogData, protected apiService: ApiService, protected i18nService: I18nService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, ) { this.organizationId = data.organizationId; this.users = data.users; @@ -60,7 +60,7 @@ export class BulkRemoveComponent { }; protected async removeUsers() { - return await this.organizationUserService.removeManyOrganizationUsers( + return await this.organizationUserApiService.removeManyOrganizationUsers( this.organizationId, this.users.map((user) => user.id), ); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts index a2ab93dd0e1..0ac413eb820 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-restore-revoke.component.ts @@ -1,7 +1,7 @@ import { DIALOG_DATA } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { DialogService } from "@bitwarden/components"; @@ -32,7 +32,7 @@ export class BulkRestoreRevokeComponent { constructor( protected i18nService: I18nService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, @Inject(DIALOG_DATA) protected data: BulkRestoreDialogParams, ) { this.isRevoking = data.isRevoking; @@ -66,12 +66,12 @@ export class BulkRestoreRevokeComponent { protected async performBulkUserAction() { const userIds = this.users.map((user) => user.id); if (this.isRevoking) { - return await this.organizationUserService.revokeManyOrganizationUsers( + return await this.organizationUserApiService.revokeManyOrganizationUsers( this.organizationId, userIds, ); } else { - return await this.organizationUserService.restoreManyOrganizationUsers( + return await this.organizationUserApiService.restoreManyOrganizationUsers( this.organizationId, userIds, ); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts index dba6319b273..7bcae82cfd8 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-status.component.ts @@ -1,7 +1,7 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject, OnInit } from "@angular/core"; -import { OrganizationUserBulkResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { OrganizationUserBulkResponse } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType, ProviderUserStatusType, 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 d39e7784b8d..fb11ad21c4c 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 @@ -13,8 +13,8 @@ import { takeUntil, } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserStatusType, OrganizationUserType, @@ -139,7 +139,7 @@ export class MemberDialogComponent implements OnDestroy { private collectionAdminService: CollectionAdminService, private groupService: GroupService, private userService: UserAdminService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private accountService: AccountService, organizationService: OrganizationService, @@ -491,7 +491,7 @@ export class MemberDialogComponent implements OnDestroy { } } - await this.organizationUserService.removeOrganizationUser( + await this.organizationUserApiService.removeOrganizationUser( this.params.organizationId, this.params.organizationUserId, ); @@ -528,7 +528,7 @@ export class MemberDialogComponent implements OnDestroy { } } - await this.organizationUserService.revokeOrganizationUser( + await this.organizationUserApiService.revokeOrganizationUser( this.params.organizationId, this.params.organizationUserId, ); @@ -547,7 +547,7 @@ export class MemberDialogComponent implements OnDestroy { return; } - await this.organizationUserService.restoreOrganizationUser( + await this.organizationUserApiService.restoreOrganizationUser( this.params.organizationId, this.params.organizationUserId, ); 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 c1bc970d5df..f4a5e738477 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 @@ -13,15 +13,17 @@ import { switchMap, } from "rxjs"; +import { + OrganizationUserApiService, + OrganizationUserConfirmRequest, + OrganizationUserUserDetailsResponse, +} from "@bitwarden/admin-console/common"; 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 { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserConfirmRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; 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"; import { @@ -116,7 +118,7 @@ export class MembersComponent extends BaseMembersComponent private syncService: SyncService, private organizationService: OrganizationService, private organizationApiService: OrganizationApiServiceAbstraction, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private router: Router, private groupService: GroupService, private collectionService: CollectionService, @@ -213,7 +215,7 @@ export class MembersComponent extends BaseMembersComponent let collectionsPromise: Promise>; // We don't need both groups and collections for the table, so only load one - const userPromise = this.organizationUserService.getAllUsers(this.organization.id, { + const userPromise = this.organizationUserApiService.getAllUsers(this.organization.id, { includeGroups: this.organization.useGroups, includeCollections: !this.organization.useGroups, }); @@ -270,19 +272,19 @@ export class MembersComponent extends BaseMembersComponent } removeUser(id: string): Promise { - return this.organizationUserService.removeOrganizationUser(this.organization.id, id); + return this.organizationUserApiService.removeOrganizationUser(this.organization.id, id); } revokeUser(id: string): Promise { - return this.organizationUserService.revokeOrganizationUser(this.organization.id, id); + return this.organizationUserApiService.revokeOrganizationUser(this.organization.id, id); } restoreUser(id: string): Promise { - return this.organizationUserService.restoreOrganizationUser(this.organization.id, id); + return this.organizationUserApiService.restoreOrganizationUser(this.organization.id, id); } reinviteUser(id: string): Promise { - return this.organizationUserService.postOrganizationUserReinvite(this.organization.id, id); + return this.organizationUserApiService.postOrganizationUserReinvite(this.organization.id, id); } async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise { @@ -290,7 +292,7 @@ export class MembersComponent extends BaseMembersComponent const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey); const request = new OrganizationUserConfirmRequest(); request.key = key.encryptedString; - await this.organizationUserService.postOrganizationUserConfirm( + await this.organizationUserApiService.postOrganizationUserConfirm( this.organization.id, user.id, request, @@ -585,7 +587,7 @@ export class MembersComponent extends BaseMembersComponent } try { - const response = this.organizationUserService.postManyOrganizationUserReinvite( + const response = this.organizationUserApiService.postManyOrganizationUserReinvite( this.organization.id, filteredUsers.map((user) => user.id), ); 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 637373b9367..b94cb4e926b 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,8 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordDetailsResponse, +} from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; 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"; @@ -24,7 +26,7 @@ describe("OrganizationUserResetPasswordService", () => { let cryptoService: MockProxy; let encryptService: MockProxy; let organizationService: MockProxy; - let organizationUserService: MockProxy; + let organizationUserApiService: MockProxy; let organizationApiService: MockProxy; let i18nService: MockProxy; @@ -32,7 +34,7 @@ describe("OrganizationUserResetPasswordService", () => { cryptoService = mock(); encryptService = mock(); organizationService = mock(); - organizationUserService = mock(); + organizationUserApiService = mock(); organizationApiService = mock(); i18nService = mock(); @@ -40,7 +42,7 @@ describe("OrganizationUserResetPasswordService", () => { cryptoService, encryptService, organizationService, - organizationUserService, + organizationUserApiService, organizationApiService, i18nService, ); @@ -112,7 +114,7 @@ describe("OrganizationUserResetPasswordService", () => { const mockOrgId = "test-org-id"; beforeEach(() => { - organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue( + organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue( new OrganizationUserResetPasswordDetailsResponse({ kdf: KdfType.PBKDF2_SHA256, kdfIterations: 5000, @@ -140,11 +142,11 @@ describe("OrganizationUserResetPasswordService", () => { it("should reset the user's master password", async () => { await sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId); - expect(organizationUserService.putOrganizationUserResetPassword).toHaveBeenCalled(); + expect(organizationUserApiService.putOrganizationUserResetPassword).toHaveBeenCalled(); }); it("should throw an error if the user details are null", async () => { - organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null); + organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue(null); await expect( sut.resetMasterPassword(mockNewMP, mockEmail, mockOrgUserId, mockOrgId), ).rejects.toThrow(); 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 860fa6abc49..b3107f2b93a 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,13 +1,13 @@ import { Injectable } from "@angular/core"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordRequest, + OrganizationUserResetPasswordWithIdRequest, +} from "@bitwarden/admin-console/common"; import { UserKeyRotationDataProvider } from "@bitwarden/auth/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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { - OrganizationUserResetPasswordRequest, - OrganizationUserResetPasswordWithIdRequest, -} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { Argon2KdfConfig, KdfConfig, @@ -33,7 +33,7 @@ export class OrganizationUserResetPasswordService private cryptoService: CryptoService, private encryptService: EncryptService, private organizationService: OrganizationService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private organizationApiService: OrganizationApiServiceAbstraction, private i18nService: I18nService, ) {} @@ -76,7 +76,7 @@ export class OrganizationUserResetPasswordService orgUserId: string, orgId: string, ): Promise { - const response = await this.organizationUserService.getOrganizationUserResetPasswordDetails( + const response = await this.organizationUserApiService.getOrganizationUserResetPasswordDetails( orgId, orgUserId, ); @@ -128,7 +128,11 @@ export class OrganizationUserResetPasswordService request.newMasterPasswordHash = newMasterKeyHash; // Change user's password - await this.organizationUserService.putOrganizationUserResetPassword(orgId, orgUserId, request); + await this.organizationUserApiService.putOrganizationUserResetPassword( + orgId, + orgUserId, + request, + ); } /** diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts index d0d05004c4c..429b62ed0cc 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts @@ -1,4 +1,4 @@ -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { OrganizationUserUserDetailsResponse } from "@bitwarden/admin-console/common"; import { OrganizationUserStatusType, OrganizationUserType, diff --git a/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts b/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts index bbd344e289e..17e608df3ee 100644 --- a/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts +++ b/apps/web/src/app/admin-console/organizations/users/enroll-master-password-reset.component.ts @@ -1,6 +1,8 @@ +import { + OrganizationUserApiService, + OrganizationUserResetPasswordEnrollmentRequest, +} from "@bitwarden/admin-console/common"; import { UserVerificationDialogComponent } from "@bitwarden/auth/angular"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationWithSecret } from "@bitwarden/common/auth/types/verification"; @@ -23,7 +25,7 @@ export class EnrollMasterPasswordReset { dialogService: DialogService, data: EnrollMasterPasswordResetData, resetPasswordService: OrganizationUserResetPasswordService, - organizationUserService: OrganizationUserService, + organizationUserApiService: OrganizationUserApiService, platformUtilsService: PlatformUtilsService, i18nService: I18nService, syncService: SyncService, @@ -50,7 +52,7 @@ export class EnrollMasterPasswordReset { // Process the enrollment request, which is an endpoint that is // gated by a server-side check of the master password hash - await organizationUserService.putOrganizationUserResetPasswordEnrollment( + await organizationUserApiService.putOrganizationUserResetPasswordEnrollment( data.organization.id, data.organization.userId, request, diff --git a/apps/web/src/app/auth/key-rotation/request/update-key.request.ts b/apps/web/src/app/auth/key-rotation/request/update-key.request.ts index 9ea40c88e6e..0988ed54a99 100644 --- a/apps/web/src/app/auth/key-rotation/request/update-key.request.ts +++ b/apps/web/src/app/auth/key-rotation/request/update-key.request.ts @@ -1,4 +1,4 @@ -import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; +import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; import { SendWithIdRequest } from "@bitwarden/common/src/tools/send/models/request/send-with-id.request"; import { CipherWithIdRequest } from "@bitwarden/common/src/vault/models/request/cipher-with-id.request"; diff --git a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts index a9727532051..2c803a627f3 100644 --- a/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/auth/key-rotation/user-key-rotation.service.spec.ts @@ -1,7 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; +import { OrganizationUserResetPasswordWithIdRequest } from "@bitwarden/admin-console/common"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { WebauthnRotateCredentialRequest } from "@bitwarden/common/auth/models/request/webauthn-rotate-credential.request"; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index 97a17a5997f..13b704b5466 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -1,9 +1,9 @@ import { FakeGlobalStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; import { MockProxy, mock } from "jest-mock-extended"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -35,7 +35,7 @@ describe("AcceptOrganizationInviteService", () => { let policyService: MockProxy; let logService: MockProxy; let organizationApiService: MockProxy; - let organizationUserService: MockProxy; + let organizationUserApiService: MockProxy; let i18nService: MockProxy; let globalStateProvider: FakeGlobalStateProvider; let globalState: FakeGlobalState; @@ -49,7 +49,7 @@ describe("AcceptOrganizationInviteService", () => { policyService = mock(); logService = mock(); organizationApiService = mock(); - organizationUserService = mock(); + organizationUserApiService = mock(); i18nService = mock(); globalStateProvider = new FakeGlobalStateProvider(); globalState = globalStateProvider.getFake(ORGANIZATION_INVITE); @@ -63,7 +63,7 @@ describe("AcceptOrganizationInviteService", () => { policyService, logService, organizationApiService, - organizationUserService, + organizationUserApiService, i18nService, globalStateProvider, ); @@ -85,10 +85,10 @@ describe("AcceptOrganizationInviteService", () => { const result = await sut.validateAndAcceptInvite(invite); expect(result).toBe(true); - expect(organizationUserService.postOrganizationUserAcceptInit).toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAcceptInit).toHaveBeenCalled(); expect(apiService.refreshIdentityToken).toHaveBeenCalled(); expect(globalState.nextMock).toHaveBeenCalledWith(null); - expect(organizationUserService.postOrganizationUserAccept).not.toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAccept).not.toHaveBeenCalled(); expect(authService.logOut).not.toHaveBeenCalled(); }); @@ -133,10 +133,10 @@ describe("AcceptOrganizationInviteService", () => { const result = await sut.validateAndAcceptInvite(invite); expect(result).toBe(true); - expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled(); expect(apiService.refreshIdentityToken).toHaveBeenCalled(); expect(globalState.nextMock).toHaveBeenCalledWith(null); - expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); expect(authService.logOut).not.toHaveBeenCalled(); }); @@ -161,8 +161,8 @@ describe("AcceptOrganizationInviteService", () => { const result = await sut.validateAndAcceptInvite(invite); expect(result).toBe(true); - expect(organizationUserService.postOrganizationUserAccept).toHaveBeenCalled(); - expect(organizationUserService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAccept).toHaveBeenCalled(); + expect(organizationUserApiService.postOrganizationUserAcceptInit).not.toHaveBeenCalled(); expect(authService.logOut).not.toHaveBeenCalled(); }); }); diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts index d1ffa61f6a9..a7798d480fb 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.ts @@ -1,13 +1,13 @@ import { Injectable } from "@angular/core"; import { BehaviorSubject, firstValueFrom, 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { + OrganizationUserApiService, OrganizationUserAcceptRequest, OrganizationUserAcceptInitRequest, -} from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; +} from "@bitwarden/admin-console/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; @@ -58,7 +58,7 @@ export class AcceptOrganizationInviteService { private readonly policyService: PolicyService, private readonly logService: LogService, private readonly organizationApiService: OrganizationApiServiceAbstraction, - private readonly organizationUserService: OrganizationUserService, + private readonly organizationUserApiService: OrganizationUserApiService, private readonly i18nService: I18nService, private readonly globalStateProvider: GlobalStateProvider, ) { @@ -121,7 +121,7 @@ export class AcceptOrganizationInviteService { private async acceptAndInitOrganization(invite: OrganizationInvite): Promise { await this.prepareAcceptAndInitRequest(invite).then((request) => - this.organizationUserService.postOrganizationUserAcceptInit( + this.organizationUserApiService.postOrganizationUserAcceptInit( invite.organizationId, invite.organizationUserId, request, @@ -156,7 +156,7 @@ export class AcceptOrganizationInviteService { private async accept(invite: OrganizationInvite): Promise { await this.prepareAcceptRequest(invite).then((request) => - this.organizationUserService.postOrganizationUserAccept( + this.organizationUserApiService.postOrganizationUserAccept( invite.organizationId, invite.organizationUserId, request, diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 2c0a0471a1f..460af8623e6 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from "@angular/common"; import { APP_INITIALIZER, NgModule, Optional, SkipSelf } from "@angular/core"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { SECURE_STORAGE, @@ -24,7 +25,6 @@ import { import { InternalUserDecryptionOptionsServiceAbstraction } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service"; @@ -203,7 +203,7 @@ const safeProviders: SafeProvider[] = [ KdfConfigService, InternalMasterPasswordServiceAbstraction, OrganizationApiServiceAbstraction, - OrganizationUserService, + OrganizationUserApiService, InternalUserDecryptionOptionsServiceAbstraction, ], }), 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 070d39dc17d..9dc8a3c0df1 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 @@ -13,9 +13,11 @@ import { } from "rxjs"; import { first } from "rxjs/operators"; +import { + OrganizationUserApiService, + OrganizationUserUserDetailsResponse, +} from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses/organization-user.response"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -106,7 +108,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { private collectionAdminService: CollectionAdminService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private dialogService: DialogService, private changeDetectorRef: ChangeDetectorRef, ) { @@ -155,7 +157,7 @@ export class CollectionDialogComponent implements OnInit, OnDestroy { collections: this.collectionAdminService.getAll(orgId), groups: groups$, // Collection(s) needed to map readonlypermission for (potential) access selector disabled state - users: this.organizationUserService.getAllUsers(orgId, { includeCollections: true }), + users: this.organizationUserApiService.getAllUsers(orgId, { includeCollections: true }), }) .pipe(takeUntil(this.formGroup.controls.selectedOrg.valueChanges), takeUntil(this.destroy$)) .subscribe(({ organization, collections: allCollections, groups, users }) => { 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 2ff3e953ae7..3b7db72a09d 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,11 +1,13 @@ import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; import { combineLatest, map, Observable, Subject, takeUntil } from "rxjs"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordEnrollmentRequest, +} from "@bitwarden/admin-console/common"; 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; 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"; @@ -45,7 +47,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { private policyService: PolicyService, private logService: LogService, private organizationApiService: OrganizationApiServiceAbstraction, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private dialogService: DialogService, private resetPasswordService: OrganizationUserResetPasswordService, @@ -153,7 +155,7 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { this.dialogService, { organization: org }, this.resetPasswordService, - this.organizationUserService, + this.organizationUserApiService, this.platformUtilsService, this.i18nService, this.syncService, @@ -166,11 +168,12 @@ export class OrganizationOptionsComponent implements OnInit, OnDestroy { const request = new OrganizationUserResetPasswordEnrollmentRequest(); request.masterPasswordHash = "ignored"; request.resetPasswordKey = null; - this.actionPromise = this.organizationUserService.putOrganizationUserResetPasswordEnrollment( - this.organization.id, - this.organization.userId, - request, - ); + this.actionPromise = + this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( + this.organization.id, + this.organization.userId, + request, + ); try { await this.actionPromise; this.platformUtilsService.showToast( 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 0d55a30e620..76e90097d19 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 @@ -3,8 +3,8 @@ import { Component, Inject, OnDestroy } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { combineLatest, of, Subject, switchMap, takeUntil } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -60,7 +60,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy { private formBuilder: FormBuilder, private organizationService: OrganizationService, private groupService: GroupService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private platformUtilsService: PlatformUtilsService, private i18nService: I18nService, private collectionAdminService: CollectionAdminService, @@ -79,7 +79,7 @@ export class BulkCollectionsDialogComponent implements OnDestroy { combineLatest([ organization$, groups$, - this.organizationUserService.getAllUsers(this.params.organizationId), + this.organizationUserApiService.getAllUsers(this.params.organizationId), ]) .pipe(takeUntil(this.destroy$)) .subscribe(([organization, groups, users]) => { 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 aed4c7b4bca..f9652fe08ca 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -30,14 +30,16 @@ import { tap, } from "rxjs/operators"; +import { + OrganizationUserApiService, + OrganizationUserUserDetailsResponse, +} from "@bitwarden/admin-console/common"; import { SearchPipe } from "@bitwarden/angular/pipes/search.pipe"; import { ModalService } from "@bitwarden/angular/services/modal.service"; 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 { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserUserDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { EventType } from "@bitwarden/common/enums"; @@ -215,7 +217,7 @@ export class VaultComponent implements OnInit, OnDestroy { private totpService: TotpService, private apiService: ApiService, private collectionService: CollectionService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, protected configService: ConfigService, private toastService: ToastService, private accountService: AccountService, @@ -395,7 +397,7 @@ export class VaultComponent implements OnInit, OnDestroy { // This will be passed into the usersCanManage call this.orgRevokedUsers = ( - await this.organizationUserService.getAllUsers(await firstValueFrom(organizationId$)) + await this.organizationUserApiService.getAllUsers(await firstValueFrom(organizationId$)) ).data.filter((user: OrganizationUserUserDetailsResponse) => { return user.status === -1; }); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index f44b67d9267..5829e2f6ab6 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -5,7 +5,7 @@ "module": "ES2020", "resolveJsonModule": true, "paths": { - "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], diff --git a/bitwarden_license/bit-cli/src/service-container.ts b/bitwarden_license/bit-cli/src/service-container.ts index 0238829a132..716c045fd16 100644 --- a/bitwarden_license/bit-cli/src/service-container.ts +++ b/bitwarden_license/bit-cli/src/service-container.ts @@ -18,7 +18,7 @@ export class ServiceContainer extends OssServiceContainer { this.organizationAuthRequestService = new OrganizationAuthRequestService( this.organizationAuthRequestApiService, this.cryptoService, - this.organizationUserService, + this.organizationUserApiService, ); } } diff --git a/bitwarden_license/bit-cli/tsconfig.json b/bitwarden_license/bit-cli/tsconfig.json index 20d136df2a5..4012daac542 100644 --- a/bitwarden_license/bit-cli/tsconfig.json +++ b/bitwarden_license/bit-cli/tsconfig.json @@ -14,6 +14,7 @@ "paths": { "@bitwarden/cli/*": ["../../apps/cli/src/*"], "@bitwarden/common/spec": ["../../libs/common/spec"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], "@bitwarden/common/*": ["../../libs/common/src/*"], diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts index ee3e0d73f41..a8e6445d331 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.spec.ts @@ -1,7 +1,9 @@ import { MockProxy, mock } from "jest-mock-extended"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordDetailsResponse, +} from "@bitwarden/admin-console/common"; import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -14,17 +16,17 @@ import { PendingAuthRequestView } from "./pending-auth-request.view"; describe("OrganizationAuthRequestService", () => { let organizationAuthRequestApiService: MockProxy; let cryptoService: MockProxy; - let organizationUserService: MockProxy; + let organizationUserApiService: MockProxy; let organizationAuthRequestService: OrganizationAuthRequestService; beforeEach(() => { organizationAuthRequestApiService = mock(); cryptoService = mock(); - organizationUserService = mock(); + organizationUserApiService = mock(); organizationAuthRequestService = new OrganizationAuthRequestService( organizationAuthRequestApiService, cryptoService, - organizationUserService, + organizationUserApiService, ); }); @@ -113,7 +115,7 @@ describe("OrganizationAuthRequestService", () => { OrganizationUserResetPasswordDetailsResponse, ); - organizationUserService.getManyOrganizationUserAccountRecoveryDetails.mockResolvedValueOnce( + organizationUserApiService.getManyOrganizationUserAccountRecoveryDetails.mockResolvedValueOnce( organizationUserResetPasswordDetailsResponse, ); @@ -155,7 +157,7 @@ describe("OrganizationAuthRequestService", () => { encryptedPrivateKey: "encryptedPrivateKey", }); - organizationUserService.getOrganizationUserResetPasswordDetails.mockResolvedValue( + organizationUserApiService.getOrganizationUserResetPasswordDetails.mockResolvedValue( organizationUserResetPasswordDetailsResponse, ); diff --git a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts index 245baf7e722..edba399b8b2 100644 --- a/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts +++ b/bitwarden_license/bit-common/src/admin-console/auth-requests/organization-auth-request.service.ts @@ -1,5 +1,7 @@ -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordDetailsResponse } from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordDetailsResponse, +} from "@bitwarden/admin-console/common"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; @@ -13,7 +15,7 @@ export class OrganizationAuthRequestService { constructor( private organizationAuthRequestApiService: OrganizationAuthRequestApiService, private cryptoService: CryptoService, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, ) {} async listPendingRequests(organizationId: string): Promise { @@ -30,7 +32,7 @@ export class OrganizationAuthRequestService { ): Promise { const organizationUserIds = authRequests.map((r) => r.organizationUserId); const details = - await this.organizationUserService.getManyOrganizationUserAccountRecoveryDetails( + await this.organizationUserApiService.getManyOrganizationUserAccountRecoveryDetails( organizationId, organizationUserIds, ); @@ -61,7 +63,7 @@ export class OrganizationAuthRequestService { } async approvePendingRequest(organizationId: string, authRequest: PendingAuthRequestView) { - const details = await this.organizationUserService.getOrganizationUserResetPasswordDetails( + const details = await this.organizationUserApiService.getOrganizationUserResetPasswordDetails( organizationId, authRequest.organizationUserId, ); diff --git a/bitwarden_license/bit-common/tsconfig.json b/bitwarden_license/bit-common/tsconfig.json index 4b57d593b15..21f57001ed7 100644 --- a/bitwarden_license/bit-common/tsconfig.json +++ b/bitwarden_license/bit-common/tsconfig.json @@ -5,7 +5,7 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth": ["../../libs/auth/src"], "@bitwarden/billing": ["../../libs/billing/src"], diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index de7642aa347..34c7bba7d0c 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -2,12 +2,12 @@ import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute } from "@angular/router"; import { BehaviorSubject, Subject, switchMap, takeUntil, tap } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; import { OrganizationAuthRequestApiService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request-api.service"; import { OrganizationAuthRequestService } from "@bitwarden/bit-common/admin-console/auth-requests/organization-auth-request.service"; import { PendingAuthRequestView } from "@bitwarden/bit-common/admin-console/auth-requests/pending-auth-request.view"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -30,7 +30,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared/shared.module"; }), safeProvider({ provide: OrganizationAuthRequestService, - deps: [OrganizationAuthRequestApiService, CryptoService, OrganizationUserService], + deps: [OrganizationAuthRequestApiService, CryptoService, OrganizationUserApiService], }), ] satisfies SafeProvider[], imports: [SharedModule, NoItemsModule, LooseComponentsModule], diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts index d4a179091aa..986cf4d30ee 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-confirm-dialog.component.ts @@ -1,11 +1,11 @@ import { DIALOG_DATA, DialogConfig } from "@angular/cdk/dialog"; import { Component, Inject } from "@angular/core"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { OrganizationUserBulkPublicKeyResponse, OrganizationUserBulkResponse, -} from "@bitwarden/common/admin-console/abstractions/organization-user/responses"; +} from "@bitwarden/admin-console/common"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ProviderUserStatusType } from "@bitwarden/common/admin-console/enums"; import { ProviderUserBulkConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk-confirm.request"; import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request"; diff --git a/bitwarden_license/bit-web/tsconfig.json b/bitwarden_license/bit-web/tsconfig.json index 8bc6ea46e49..e05ae8018f5 100644 --- a/bitwarden_license/bit-web/tsconfig.json +++ b/bitwarden_license/bit-web/tsconfig.json @@ -5,7 +5,7 @@ "module": "ES2020", "resolveJsonModule": true, "paths": { - "@bitwarden/admin-console": ["../../libs/admin-console/src"], + "@bitwarden/admin-console/common": ["../../libs/admin-console/src/common"], "@bitwarden/angular/*": ["../../libs/angular/src/*"], "@bitwarden/auth/common": ["../../libs/auth/src/common"], "@bitwarden/auth/angular": ["../../libs/auth/src/angular"], diff --git a/libs/admin-console/src/common/index.ts b/libs/admin-console/src/common/index.ts new file mode 100644 index 00000000000..0af54f8ffbf --- /dev/null +++ b/libs/admin-console/src/common/index.ts @@ -0,0 +1 @@ +export * from "./organization-user"; diff --git a/libs/admin-console/src/common/organization-user/abstractions/index.ts b/libs/admin-console/src/common/organization-user/abstractions/index.ts new file mode 100644 index 00000000000..01cd189b3dd --- /dev/null +++ b/libs/admin-console/src/common/organization-user/abstractions/index.ts @@ -0,0 +1 @@ +export * from "./organization-user-api.service"; diff --git a/libs/common/src/admin-console/abstractions/organization-user/organization-user.service.ts b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts similarity index 98% rename from libs/common/src/admin-console/abstractions/organization-user/organization-user.service.ts rename to libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts index aada830f954..ea5d2185ee2 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/organization-user.service.ts +++ b/libs/admin-console/src/common/organization-user/abstractions/organization-user-api.service.ts @@ -1,4 +1,4 @@ -import { ListResponse } from "../../../models/response/list.response"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; import { OrganizationUserAcceptInitRequest, @@ -9,19 +9,19 @@ import { OrganizationUserResetPasswordEnrollmentRequest, OrganizationUserResetPasswordRequest, OrganizationUserUpdateRequest, -} from "./requests"; +} from "../models/requests"; import { OrganizationUserBulkPublicKeyResponse, OrganizationUserBulkResponse, OrganizationUserDetailsResponse, OrganizationUserResetPasswordDetailsResponse, OrganizationUserUserDetailsResponse, -} from "./responses"; +} from "../models/responses"; /** * Service for interacting with Organization Users via the API */ -export abstract class OrganizationUserService { +export abstract class OrganizationUserApiService { /** * Retrieve a single organization user by Id * @param organizationId - Identifier for the user's organization diff --git a/libs/admin-console/src/common/organization-user/index.ts b/libs/admin-console/src/common/organization-user/index.ts new file mode 100644 index 00000000000..6ddcc4f1988 --- /dev/null +++ b/libs/admin-console/src/common/organization-user/index.ts @@ -0,0 +1,3 @@ +export * from "./abstractions"; +export * from "./services"; +export * from "./models"; diff --git a/libs/admin-console/src/common/organization-user/models/index.ts b/libs/admin-console/src/common/organization-user/models/index.ts new file mode 100644 index 00000000000..a84f3da17e0 --- /dev/null +++ b/libs/admin-console/src/common/organization-user/models/index.ts @@ -0,0 +1,2 @@ +export * from "./requests"; +export * from "./responses"; diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/index.ts b/libs/admin-console/src/common/organization-user/models/requests/index.ts similarity index 90% rename from libs/common/src/admin-console/abstractions/organization-user/requests/index.ts rename to libs/admin-console/src/common/organization-user/models/requests/index.ts index 28c00f1bb3b..b0715620613 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/requests/index.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/index.ts @@ -6,3 +6,4 @@ export * from "./organization-user-invite.request"; export * from "./organization-user-reset-password.request"; export * from "./organization-user-reset-password-enrollment.request"; export * from "./organization-user-update.request"; +export * from "./organization-user-bulk.request"; diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-accept-init.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts similarity index 55% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-accept-init.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts index cff51451ed6..20d87a774e6 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-accept-init.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept-init.request.ts @@ -1,4 +1,4 @@ -import { OrganizationKeysRequest } from "../../../models/request/organization-keys.request"; +import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request"; export class OrganizationUserAcceptInitRequest { token: string; diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-accept.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts similarity index 100% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-accept.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-accept.request.ts diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-bulk-confirm.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk-confirm.request.ts similarity index 100% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-bulk-confirm.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk-confirm.request.ts diff --git a/libs/common/src/admin-console/services/organization-user/requests/organization-user-bulk.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk.request.ts similarity index 100% rename from libs/common/src/admin-console/services/organization-user/requests/organization-user-bulk.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-bulk.request.ts diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-confirm.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts similarity index 100% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-confirm.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-confirm.request.ts diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts new file mode 100644 index 00000000000..1793beccbef --- /dev/null +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-invite.request.ts @@ -0,0 +1,12 @@ +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; + +export class OrganizationUserInviteRequest { + emails: string[] = []; + type: OrganizationUserType; + accessSecretsManager: boolean; + collections: SelectionReadOnlyRequest[] = []; + groups: string[]; + permissions: PermissionsApi; +} diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-reset-password-enrollment.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts similarity index 70% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-reset-password-enrollment.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts index ab655466f82..4526b227d92 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-reset-password-enrollment.request.ts +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password-enrollment.request.ts @@ -1,4 +1,4 @@ -import { SecretVerificationRequest } from "../../../../auth/models/request/secret-verification.request"; +import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request"; export class OrganizationUserResetPasswordEnrollmentRequest extends SecretVerificationRequest { resetPasswordKey: string; diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-reset-password.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts similarity index 100% rename from libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-reset-password.request.ts rename to libs/admin-console/src/common/organization-user/models/requests/organization-user-reset-password.request.ts diff --git a/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts b/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts new file mode 100644 index 00000000000..283af4f081f --- /dev/null +++ b/libs/admin-console/src/common/organization-user/models/requests/organization-user-update.request.ts @@ -0,0 +1,11 @@ +import { OrganizationUserType } from "@bitwarden/common/admin-console/enums"; +import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; +import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request"; + +export class OrganizationUserUpdateRequest { + type: OrganizationUserType; + accessSecretsManager: boolean; + collections: SelectionReadOnlyRequest[] = []; + groups: string[] = []; + permissions: PermissionsApi; +} diff --git a/libs/common/src/admin-console/abstractions/organization-user/responses/index.ts b/libs/admin-console/src/common/organization-user/models/responses/index.ts similarity index 100% rename from libs/common/src/admin-console/abstractions/organization-user/responses/index.ts rename to libs/admin-console/src/common/organization-user/models/responses/index.ts diff --git a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk-public-key.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk-public-key.response.ts similarity index 80% rename from libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk-public-key.response.ts rename to libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk-public-key.response.ts index 7edb2f1436c..7996980bbfb 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk-public-key.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk-public-key.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "../../../../models/response/base.response"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; export class OrganizationUserBulkPublicKeyResponse extends BaseResponse { id: string; diff --git a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk.response.ts similarity index 76% rename from libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk.response.ts rename to libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk.response.ts index 40b2877275e..ea39db18e24 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user-bulk.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user-bulk.response.ts @@ -1,4 +1,4 @@ -import { BaseResponse } from "../../../../models/response/base.response"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; export class OrganizationUserBulkResponse extends BaseResponse { id: string; diff --git a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts similarity index 85% rename from libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts rename to libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts index b59166bfea9..7323855f69f 100644 --- a/libs/common/src/admin-console/abstractions/organization-user/responses/organization-user.response.ts +++ b/libs/admin-console/src/common/organization-user/models/responses/organization-user.response.ts @@ -1,8 +1,11 @@ -import { BaseResponse } from "../../../../models/response/base.response"; -import { KdfType } from "../../../../platform/enums"; -import { OrganizationUserStatusType, OrganizationUserType } from "../../../enums"; -import { PermissionsApi } from "../../../models/api/permissions.api"; -import { SelectionReadOnlyResponse } from "../../../models/response/selection-read-only.response"; +import { + OrganizationUserStatusType, + OrganizationUserType, +} from "@bitwarden/common/admin-console/enums"; +import { PermissionsApi } from "@bitwarden/common/admin-console/models/api/permissions.api"; +import { SelectionReadOnlyResponse } from "@bitwarden/common/admin-console/models/response/selection-read-only.response"; +import { BaseResponse } from "@bitwarden/common/models/response/base.response"; +import { KdfType } from "@bitwarden/common/platform/enums"; export class OrganizationUserResponse extends BaseResponse { id: string; diff --git a/libs/common/src/admin-console/services/organization-user/organization-user.service.implementation.ts b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts similarity index 94% rename from libs/common/src/admin-console/services/organization-user/organization-user.service.implementation.ts rename to libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts index e3687691b6b..40824550d44 100644 --- a/libs/common/src/admin-console/services/organization-user/organization-user.service.implementation.ts +++ b/libs/admin-console/src/common/organization-user/services/default-organization-user-api.service.ts @@ -1,6 +1,7 @@ -import { ApiService } from "../../../abstractions/api.service"; -import { ListResponse } from "../../../models/response/list.response"; -import { OrganizationUserService } from "../../abstractions/organization-user/organization-user.service"; +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { ListResponse } from "@bitwarden/common/models/response/list.response"; + +import { OrganizationUserApiService } from "../abstractions"; import { OrganizationUserAcceptInitRequest, OrganizationUserAcceptRequest, @@ -10,18 +11,17 @@ import { OrganizationUserResetPasswordEnrollmentRequest, OrganizationUserResetPasswordRequest, OrganizationUserUpdateRequest, -} from "../../abstractions/organization-user/requests"; + OrganizationUserBulkRequest, +} from "../models/requests"; import { OrganizationUserBulkPublicKeyResponse, OrganizationUserBulkResponse, OrganizationUserDetailsResponse, OrganizationUserResetPasswordDetailsResponse, OrganizationUserUserDetailsResponse, -} from "../../abstractions/organization-user/responses"; +} from "../models/responses"; -import { OrganizationUserBulkRequest } from "./requests"; - -export class OrganizationUserServiceImplementation implements OrganizationUserService { +export class DefaultOrganizationUserApiService implements OrganizationUserApiService { constructor(private apiService: ApiService) {} async getOrganizationUser( diff --git a/libs/admin-console/src/common/organization-user/services/index.ts b/libs/admin-console/src/common/organization-user/services/index.ts new file mode 100644 index 00000000000..6135236d6a6 --- /dev/null +++ b/libs/admin-console/src/common/organization-user/services/index.ts @@ -0,0 +1 @@ +export * from "./default-organization-user-api.service"; diff --git a/libs/admin-console/src/index.ts b/libs/admin-console/src/index.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/libs/angular/src/auth/components/base-login-decryption-options.component.ts b/libs/angular/src/auth/components/base-login-decryption-options.component.ts index 6487c0cf847..c237c98c0bb 100644 --- a/libs/angular/src/auth/components/base-login-decryption-options.component.ts +++ b/libs/angular/src/auth/components/base-login-decryption-options.component.ts @@ -17,6 +17,7 @@ import { take, } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { LoginEmailServiceAbstraction, UserDecryptionOptions, @@ -24,7 +25,6 @@ import { } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; @@ -95,7 +95,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { protected loginEmailService: LoginEmailServiceAbstraction, protected organizationApiService: OrganizationApiServiceAbstraction, protected cryptoService: CryptoService, - protected organizationUserService: OrganizationUserService, + protected organizationUserApiService: OrganizationUserApiService, protected apiService: ApiService, protected i18nService: I18nService, protected validationService: ValidationService, diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index e9662c71076..ea4c2fb9267 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -3,11 +3,13 @@ import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom, of } from "rxjs"; import { filter, first, switchMap, tap } from "rxjs/operators"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordEnrollmentRequest, +} from "@bitwarden/admin-console/common"; import { InternalUserDecryptionOptionsServiceAbstraction } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; @@ -68,7 +70,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements private route: ActivatedRoute, stateService: StateService, private organizationApiService: OrganizationApiServiceAbstraction, - private organizationUserService: OrganizationUserService, + private organizationUserApiService: OrganizationUserApiService, private userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, private ssoLoginService: SsoLoginServiceAbstraction, dialogService: DialogService, @@ -219,7 +221,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements resetRequest.masterPasswordHash = masterPasswordHash; resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; - return this.organizationUserService.putOrganizationUserResetPasswordEnrollment( + return this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( this.orgId, this.userId, resetRequest, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 851e02c8e04..7b9c33f3d0a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,6 +1,10 @@ import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; import { Subject } from "rxjs"; +import { + OrganizationUserApiService, + DefaultOrganizationUserApiService, +} from "@bitwarden/admin-console/common"; import { SetPasswordJitService, DefaultSetPasswordJitService, @@ -43,7 +47,6 @@ import { OrgDomainServiceAbstraction, } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction"; import { OrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/abstractions/organization-management-preferences/organization-management-preferences.service"; -import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { InternalPolicyService, @@ -56,7 +59,6 @@ import { OrganizationService } from "@bitwarden/common/admin-console/services/or import { OrgDomainApiService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain-api.service"; import { OrgDomainService } from "@bitwarden/common/admin-console/services/organization-domain/org-domain.service"; import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/common/admin-console/services/organization-management-preferences/default-organization-management-preferences.service"; -import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation"; import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service"; @@ -934,8 +936,8 @@ const safeProviders: SafeProvider[] = [ useExisting: InternalOrganizationServiceAbstraction, }), safeProvider({ - provide: OrganizationUserService, - useClass: OrganizationUserServiceImplementation, + provide: OrganizationUserApiService, + useClass: DefaultOrganizationUserApiService, deps: [ApiServiceAbstraction], }), safeProvider({ @@ -945,7 +947,7 @@ const safeProviders: SafeProvider[] = [ OrganizationApiServiceAbstraction, AccountServiceAbstraction, CryptoServiceAbstraction, - OrganizationUserService, + OrganizationUserApiService, I18nServiceAbstraction, ], }), @@ -1273,7 +1275,7 @@ const safeProviders: SafeProvider[] = [ KdfConfigServiceAbstraction, InternalMasterPasswordServiceAbstraction, OrganizationApiServiceAbstraction, - OrganizationUserService, + OrganizationUserApiService, InternalUserDecryptionOptionsServiceAbstraction, ], }), diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts index 26b6b0e529f..f47e217d0ec 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.spec.ts @@ -1,13 +1,13 @@ import { MockProxy, mock } from "jest-mock-extended"; import { BehaviorSubject, of } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; import { FakeUserDecryptionOptions as UserDecryptionOptions, InternalUserDecryptionOptionsServiceAbstraction, } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationKeysResponse } from "@bitwarden/common/admin-console/models/response/organization-keys.response"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; @@ -37,7 +37,7 @@ describe("DefaultSetPasswordJitService", () => { let kdfConfigService: MockProxy; let masterPasswordService: MockProxy; let organizationApiService: MockProxy; - let organizationUserService: MockProxy; + let organizationUserApiService: MockProxy; let userDecryptionOptionsService: MockProxy; beforeEach(() => { @@ -47,7 +47,7 @@ describe("DefaultSetPasswordJitService", () => { kdfConfigService = mock(); masterPasswordService = mock(); organizationApiService = mock(); - organizationUserService = mock(); + organizationUserApiService = mock(); userDecryptionOptionsService = mock(); sut = new DefaultSetPasswordJitService( @@ -57,7 +57,7 @@ describe("DefaultSetPasswordJitService", () => { kdfConfigService, masterPasswordService, organizationApiService, - organizationUserService, + organizationUserApiService, userDecryptionOptionsService, ); }); @@ -170,7 +170,7 @@ describe("DefaultSetPasswordJitService", () => { cryptoService.userKey$.mockReturnValue(of(userKey)); cryptoService.rsaEncrypt.mockResolvedValue(userKeyEncString); - organizationUserService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue( + organizationUserApiService.putOrganizationUserResetPasswordEnrollment.mockResolvedValue( undefined, ); } @@ -211,7 +211,9 @@ describe("DefaultSetPasswordJitService", () => { expect(apiService.setPassword).toHaveBeenCalledWith(setPasswordRequest); expect(organizationApiService.getKeys).toHaveBeenCalledWith(orgId); expect(cryptoService.rsaEncrypt).toHaveBeenCalledWith(userKey.key, orgPublicKey); - expect(organizationUserService.putOrganizationUserResetPasswordEnrollment).toHaveBeenCalled(); + expect( + organizationUserApiService.putOrganizationUserResetPasswordEnrollment, + ).toHaveBeenCalled(); }); it("when handling reset password auto enroll, it should throw an error if organization keys are not found", async () => { @@ -224,7 +226,7 @@ describe("DefaultSetPasswordJitService", () => { // Act and Assert await expect(sut.setPassword(credentials)).rejects.toThrow(); expect( - organizationUserService.putOrganizationUserResetPasswordEnrollment, + organizationUserApiService.putOrganizationUserResetPasswordEnrollment, ).not.toHaveBeenCalled(); }); }); diff --git a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts index a5c196b5c7e..968ba60dec3 100644 --- a/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts +++ b/libs/auth/src/angular/set-password-jit/default-set-password-jit.service.ts @@ -1,10 +1,12 @@ import { firstValueFrom } from "rxjs"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordEnrollmentRequest, +} from "@bitwarden/admin-console/common"; import { InternalUserDecryptionOptionsServiceAbstraction } 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 { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/admin-console/abstractions/organization-user/requests"; import { KdfConfigService } from "@bitwarden/common/auth/abstractions/kdf-config.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; @@ -31,7 +33,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { protected kdfConfigService: KdfConfigService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, protected organizationApiService: OrganizationApiServiceAbstraction, - protected organizationUserService: OrganizationUserService, + protected organizationUserApiService: OrganizationUserApiService, protected userDecryptionOptionsService: InternalUserDecryptionOptionsServiceAbstraction, ) {} @@ -161,7 +163,7 @@ export class DefaultSetPasswordJitService implements SetPasswordJitService { resetRequest.masterPasswordHash = masterKeyHash; resetRequest.resetPasswordKey = encryptedUserKey.encryptedString; - await this.organizationUserService.putOrganizationUserResetPasswordEnrollment( + await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( orgId, userId, resetRequest, diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-invite.request.ts b/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-invite.request.ts deleted file mode 100644 index dd82b730c5f..00000000000 --- a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-invite.request.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { OrganizationUserType } from "../../../enums"; -import { PermissionsApi } from "../../../models/api/permissions.api"; -import { SelectionReadOnlyRequest } from "../../../models/request/selection-read-only.request"; - -export class OrganizationUserInviteRequest { - emails: string[] = []; - type: OrganizationUserType; - accessSecretsManager: boolean; - collections: SelectionReadOnlyRequest[] = []; - groups: string[]; - permissions: PermissionsApi; -} diff --git a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-update.request.ts b/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-update.request.ts deleted file mode 100644 index 00201549d35..00000000000 --- a/libs/common/src/admin-console/abstractions/organization-user/requests/organization-user-update.request.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { OrganizationUserType } from "../../../enums"; -import { PermissionsApi } from "../../../models/api/permissions.api"; -import { SelectionReadOnlyRequest } from "../../../models/request/selection-read-only.request"; - -export class OrganizationUserUpdateRequest { - type: OrganizationUserType; - accessSecretsManager: boolean; - collections: SelectionReadOnlyRequest[] = []; - groups: string[] = []; - permissions: PermissionsApi; -} diff --git a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts index 39e5747ade3..1f497dd8c5f 100644 --- a/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts +++ b/libs/common/src/admin-console/models/response/provider/provider-user-bulk-public-key.response.ts @@ -1,3 +1,3 @@ -import { OrganizationUserBulkPublicKeyResponse } from "../../../abstractions/organization-user/responses"; +import { OrganizationUserBulkPublicKeyResponse } from "@bitwarden/admin-console/common"; export class ProviderUserBulkPublicKeyResponse extends OrganizationUserBulkPublicKeyResponse {} diff --git a/libs/common/src/admin-console/services/organization-user/requests/index.ts b/libs/common/src/admin-console/services/organization-user/requests/index.ts deleted file mode 100644 index 4daafb4c6a9..00000000000 --- a/libs/common/src/admin-console/services/organization-user/requests/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./organization-user-bulk.request"; diff --git a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts index 0f6a8c3911d..cf6577cb76c 100644 --- a/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts +++ b/libs/common/src/auth/models/request/update-tde-offboarding-password.request.ts @@ -1,4 +1,4 @@ -import { OrganizationUserResetPasswordRequest } from "../../../admin-console/abstractions/organization-user/requests"; +import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTdeOffboardingPasswordRequest extends OrganizationUserResetPasswordRequest { masterPasswordHint: string; diff --git a/libs/common/src/auth/models/request/update-temp-password.request.ts b/libs/common/src/auth/models/request/update-temp-password.request.ts index 84bcc597f43..47e76d5e9d8 100644 --- a/libs/common/src/auth/models/request/update-temp-password.request.ts +++ b/libs/common/src/auth/models/request/update-temp-password.request.ts @@ -1,4 +1,4 @@ -import { OrganizationUserResetPasswordRequest } from "../../../admin-console/abstractions/organization-user/requests"; +import { OrganizationUserResetPasswordRequest } from "@bitwarden/admin-console/common"; export class UpdateTempPasswordRequest extends OrganizationUserResetPasswordRequest { masterPasswordHint: string; diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts index 19b29f05932..575b3a6ee79 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.spec.ts @@ -1,9 +1,10 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; +import { OrganizationUserApiService } from "@bitwarden/admin-console/common"; + import { UserId } from "../../../../common/src/types/guid"; import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "../../admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationAutoEnrollStatusResponse } from "../../admin-console/models/response/organization-auto-enroll-status.response"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; @@ -17,7 +18,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { let organizationApiService: MockProxy; let accountService: MockProxy; let cryptoService: MockProxy; - let organizationUserService: MockProxy; + let organizationUserApiService: MockProxy; let i18nService: MockProxy; let service: PasswordResetEnrollmentServiceImplementation; @@ -26,13 +27,13 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { accountService = mock(); accountService.activeAccount$ = activeAccountSubject; cryptoService = mock(); - organizationUserService = mock(); + organizationUserApiService = mock(); i18nService = mock(); service = new PasswordResetEnrollmentServiceImplementation( organizationApiService, accountService, cryptoService, - organizationUserService, + organizationUserApiService, i18nService, ); }); @@ -100,7 +101,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { await service.enroll("orgId"); expect( - organizationUserService.putOrganizationUserResetPasswordEnrollment, + organizationUserApiService.putOrganizationUserResetPasswordEnrollment, ).toHaveBeenCalledWith( "orgId", "userId", @@ -122,7 +123,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => { await service.enroll("orgId", "userId", { key: "key" } as any); expect( - organizationUserService.putOrganizationUserResetPasswordEnrollment, + organizationUserApiService.putOrganizationUserResetPasswordEnrollment, ).toHaveBeenCalledWith( "orgId", "userId", diff --git a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts index aeb978bfd90..65718d96694 100644 --- a/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts +++ b/libs/common/src/auth/services/password-reset-enrollment.service.implementation.ts @@ -1,8 +1,11 @@ import { firstValueFrom, map } from "rxjs"; +import { + OrganizationUserApiService, + OrganizationUserResetPasswordEnrollmentRequest, +} from "@bitwarden/admin-console/common"; + import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction"; -import { OrganizationUserService } from "../../admin-console/abstractions/organization-user/organization-user.service"; -import { OrganizationUserResetPasswordEnrollmentRequest } from "../../admin-console/abstractions/organization-user/requests"; import { CryptoService } from "../../platform/abstractions/crypto.service"; import { I18nService } from "../../platform/abstractions/i18n.service"; import { Utils } from "../../platform/misc/utils"; @@ -17,7 +20,7 @@ export class PasswordResetEnrollmentServiceImplementation protected organizationApiService: OrganizationApiServiceAbstraction, protected accountService: AccountService, protected cryptoService: CryptoService, - protected organizationUserService: OrganizationUserService, + protected organizationUserApiService: OrganizationUserApiService, protected i18nService: I18nService, ) {} @@ -49,7 +52,7 @@ export class PasswordResetEnrollmentServiceImplementation const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest(); resetRequest.resetPasswordKey = encryptedKey.encryptedString; - await this.organizationUserService.putOrganizationUserResetPasswordEnrollment( + await this.organizationUserApiService.putOrganizationUserResetPasswordEnrollment( organizationId, userId, resetRequest, diff --git a/libs/shared/tsconfig.libs.json b/libs/shared/tsconfig.libs.json index 7801a8909f6..647b8a9c55f 100644 --- a/libs/shared/tsconfig.libs.json +++ b/libs/shared/tsconfig.libs.json @@ -3,7 +3,7 @@ "compilerOptions": { "resolveJsonModule": true, "paths": { - "@bitwarden/admin-console": ["../admin-console/src"], + "@bitwarden/admin-console/common": ["../admin-console/src/common"], "@bitwarden/angular/*": ["../angular/src/*"], "@bitwarden/auth/common": ["../auth/src/common"], "@bitwarden/auth/angular": ["../auth/src/angular"], diff --git a/tsconfig.json b/tsconfig.json index 79edf0da2e1..46829a9c30f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "baseUrl": ".", "resolveJsonModule": true, "paths": { - "@bitwarden/admin-console": ["./libs/admin-console/src"], + "@bitwarden/admin-console/common": ["./libs/admin-console/src/common"], "@bitwarden/angular/*": ["./libs/angular/src/*"], "@bitwarden/auth/common": ["./libs/auth/src/common"], "@bitwarden/auth/angular": ["./libs/auth/src/angular"], From 0173b5192ea062008dc04047b7e3f98517125652 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:33:35 +1000 Subject: [PATCH 18/28] [PM-11623] Refactor access-selector stories (#10874) * Split access-selector stories into separate stories and files * Tweak existing stories to better represent actual use cases in our app * Add jsdoc comments * Add stories --- .../access-selector-dialog.stories.ts | 74 +++++ .../access-selector-reactive.stories.ts | 64 ++++ .../access-selector.stories.ts | 293 +++++++----------- .../access-selector/storybook-utils.ts | 44 +++ 4 files changed, 287 insertions(+), 188 deletions(-) create mode 100644 apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts create mode 100644 apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-reactive.stories.ts create mode 100644 apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts new file mode 100644 index 00000000000..efe666dae2e --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-dialog.stories.ts @@ -0,0 +1,74 @@ +import { Meta, StoryObj } from "@storybook/angular"; + +import { AccessSelectorComponent, PermissionMode } from "./access-selector.component"; +import { AccessItemType, AccessItemValue } from "./access-selector.models"; +import { default as baseComponentDefinition } from "./access-selector.stories"; +import { actionsData, itemsFactory } from "./storybook-utils"; + +/** + * Displays the Access Selector in a dialog. + */ +export default { + title: "Web/Organizations/Access Selector/Dialog", + decorators: baseComponentDefinition.decorators, +} as Meta; + +type Story = StoryObj; + +const render: Story["render"] = (args) => ({ + props: { + items: [], + valueChanged: actionsData.onValueChanged, + initialValue: [], + ...args, + }, + template: ` + + Access selector + + + + + + + + + + `, +}); + +const dialogAccessItems = itemsFactory(10, AccessItemType.Collection); + +export const Dialog: Story = { + args: { + permissionMode: PermissionMode.Edit, + showMemberRoles: false, + showGroupColumn: true, + columnHeader: "Collection", + selectorLabelText: "Select Collections", + selectorHelpText: "Some helper text describing what this does", + emptySelectionText: "No collections added", + disabled: false, + initialValue: [] as any[], + items: dialogAccessItems, + }, + render, +}; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-reactive.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-reactive.stories.ts new file mode 100644 index 00000000000..ec7c378f19c --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector-reactive.stories.ts @@ -0,0 +1,64 @@ +import { FormBuilder, FormControl, FormGroup } from "@angular/forms"; +import { Meta, StoryObj } from "@storybook/angular"; + +import { AccessSelectorComponent, PermissionMode } from "./access-selector.component"; +import { AccessItemType, AccessItemValue } from "./access-selector.models"; +import { default as baseComponentDefinition } from "./access-selector.stories"; +import { actionsData, itemsFactory } from "./storybook-utils"; + +/** + * Displays the Access Selector embedded in a reactive form. + */ +export default { + title: "Web/Organizations/Access Selector/Reactive form", + decorators: baseComponentDefinition.decorators, + argTypes: { + formObj: { table: { disable: true } }, + }, +} as Meta; + +type FormObj = { formObj: FormGroup<{ formItems: FormControl }> }; +type Story = StoryObj; + +const fb = new FormBuilder(); + +const render: Story["render"] = (args) => ({ + props: { + items: [], + onSubmit: actionsData.onSubmit, + ...args, + }, + template: ` +
+ + +
+`, +}); + +const sampleMembers = itemsFactory(10, AccessItemType.Member); +const sampleGroups = itemsFactory(6, AccessItemType.Group); + +export const ReactiveForm: Story = { + args: { + formObj: fb.group({ formItems: [[{ id: "1g", type: AccessItemType.Group }]] }), + permissionMode: PermissionMode.Edit, + showMemberRoles: false, + columnHeader: "Groups/Members", + selectorLabelText: "Select groups and members", + selectorHelpText: + "Permissions set for a member will replace permissions set by that member's group", + emptySelectionText: "No members or groups added", + items: sampleGroups.concat(sampleMembers), + }, + render, +}; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts index 3e551a84753..095be1df966 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.stories.ts @@ -1,13 +1,8 @@ import { importProvidersFrom } from "@angular/core"; -import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms"; -import { action } from "@storybook/addon-actions"; +import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { applicationConfig, Meta, moduleMetadata, StoryObj } from "@storybook/angular"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - OrganizationUserStatusType, - OrganizationUserType, -} from "@bitwarden/common/admin-console/enums"; import { AvatarModule, BadgeModule, @@ -21,10 +16,20 @@ import { import { PreloadedEnglishI18nModule } from "../../../../../core/tests"; -import { AccessSelectorComponent } from "./access-selector.component"; -import { AccessItemType, AccessItemView, CollectionPermission } from "./access-selector.models"; +import { AccessSelectorComponent, PermissionMode } from "./access-selector.component"; +import { AccessItemType, AccessItemValue, CollectionPermission } from "./access-selector.models"; +import { actionsData, itemsFactory } from "./storybook-utils"; import { UserTypePipe } from "./user-type.pipe"; +/** + * The Access Selector is used to view and edit: + * - member and group access to collections + * - members assigned to groups + * + * It is highly configurable in order to display these relationships from each perspective. For example, you can + * manage member-group relationships from the perspective of a particular member (showing all their groups) or a + * particular group (showing all its members). + */ export default { title: "Web/Organizations/Access Selector", decorators: [ @@ -49,65 +54,16 @@ export default { providers: [importProvidersFrom(PreloadedEnglishI18nModule)], }), ], - parameters: {}, - argTypes: { - formObj: { table: { disable: true } }, - }, } as Meta; -// TODO: This is a workaround since this story does weird things. -type Story = StoryObj; - -const actionsData = { - onValueChanged: action("onValueChanged"), - onSubmit: action("onSubmit"), -}; - -/** - * Factory to help build semi-realistic looking items - * @param n - The number of items to build - * @param type - Which type to build - */ -const itemsFactory = (n: number, type: AccessItemType) => { - return [...Array(n)].map((_: unknown, id: number) => { - const item: AccessItemView = { - id: id.toString(), - type: type, - } as AccessItemView; - - switch (item.type) { - case AccessItemType.Collection: - item.labelName = item.listName = `Collection ${id}`; - item.id = item.id + "c"; - item.parentGrouping = "Collection Parent Group " + ((id % 2) + 1); - break; - case AccessItemType.Group: - item.labelName = item.listName = `Group ${id}`; - item.id = item.id + "g"; - break; - case AccessItemType.Member: - item.id = item.id + "m"; - item.email = `member${id}@email.com`; - item.status = id % 3 == 0 ? 0 : 2; - item.labelName = item.status == 2 ? `Member ${id}` : item.email; - item.listName = item.status == 2 ? `${item.labelName} (${item.email})` : item.email; - item.role = id % 5; - break; - } - - return item; - }); -}; +type Story = StoryObj; const sampleMembers = itemsFactory(10, AccessItemType.Member); const sampleGroups = itemsFactory(6, AccessItemType.Group); -// TODO: These renders are badly handled but storybook has made it more difficult to use multiple renders in a single story. -const StandaloneAccessSelectorRender = (args: any) => ({ +const render: Story["render"] = (args) => ({ props: { - items: [], valueChanged: actionsData.onValueChanged, - initialValue: [], ...args, }, template: ` @@ -127,49 +83,8 @@ const StandaloneAccessSelectorRender = (args: any) => ({ `, }); -const DialogAccessSelectorRender = (args: any) => ({ - props: { - items: [], - valueChanged: actionsData.onValueChanged, - initialValue: [], - ...args, - }, - template: ` - - Access selector - - - - - - - - - - `, -}); - -const dialogAccessItems = itemsFactory(10, AccessItemType.Collection); - -const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).concat([ +const memberCollectionAccessItems = itemsFactory(5, AccessItemType.Collection).concat([ + // These represent collection access via a group { id: "c1-group1", type: AccessItemType.Collection, @@ -190,25 +105,25 @@ const memberCollectionAccessItems = itemsFactory(3, AccessItemType.Collection).c }, ]); -export const Dialog: Story = { - args: { - permissionMode: "edit", - showMemberRoles: false, - showGroupColumn: true, - columnHeader: "Collection", - selectorLabelText: "Select Collections", - selectorHelpText: "Some helper text describing what this does", - emptySelectionText: "No collections added", - disabled: false, - initialValue: [] as any[], - items: dialogAccessItems, - }, - render: DialogAccessSelectorRender, -}; +// Simulate the current user not having permission to change access to this collection +// TODO: currently the member dialog duplicates the AccessItemValue.permission on the +// AccessItemView.readonlyPermission, this will be refactored to reduce this duplication: +// https://bitwarden.atlassian.net/browse/PM-11590 +memberCollectionAccessItems[4].readonly = true; +memberCollectionAccessItems[4].readonlyPermission = CollectionPermission.Manage; +/** + * Displays a member's collection access. + * + * This is currently used in the **Member dialog -> Collections tab**. Note that it includes collection access that the + * member has via a group. + * + * This is also used in the **Groups dialog -> Collections tab** to show a group's collection access and in this + * case the Group column is hidden. + */ export const MemberCollectionAccess: Story = { args: { - permissionMode: "edit", + permissionMode: PermissionMode.Edit, showMemberRoles: false, showGroupColumn: true, columnHeader: "Collection", @@ -216,22 +131,41 @@ export const MemberCollectionAccess: Story = { selectorHelpText: "Some helper text describing what this does", emptySelectionText: "No collections added", disabled: false, - initialValue: [], + initialValue: [ + { + id: "4c", + type: AccessItemType.Collection, + permission: CollectionPermission.Manage, + }, + { + id: "2c", + type: AccessItemType.Collection, + permission: CollectionPermission.Edit, + }, + ], items: memberCollectionAccessItems, }, - render: StandaloneAccessSelectorRender, + render, }; +/** + * Displays the groups a member is assigned to. + * + * This is currently used in the **Member dialog -> Groups tab**. + */ export const MemberGroupAccess: Story = { args: { - permissionMode: "readonly", + permissionMode: PermissionMode.Hidden, showMemberRoles: false, columnHeader: "Groups", selectorLabelText: "Select Groups", selectorHelpText: "Some helper text describing what this does", emptySelectionText: "No groups added", disabled: false, - initialValue: [{ id: "3g" }, { id: "0g" }], + initialValue: [ + { id: "3g", type: AccessItemType.Group }, + { id: "0g", type: AccessItemType.Group }, + ], items: itemsFactory(4, AccessItemType.Group).concat([ { id: "admin", @@ -241,27 +175,40 @@ export const MemberGroupAccess: Story = { }, ]), }, - render: StandaloneAccessSelectorRender, + render, }; +/** + * Displays the members assigned to a group. + * + * This is currently used in the **Group dialog -> Members tab**. + */ export const GroupMembersAccess: Story = { args: { - permissionMode: "hidden", + permissionMode: PermissionMode.Hidden, showMemberRoles: true, columnHeader: "Members", selectorLabelText: "Select Members", selectorHelpText: "Some helper text describing what this does", emptySelectionText: "No members added", disabled: false, - initialValue: [{ id: "2m" }, { id: "0m" }], + initialValue: [ + { id: "2m", type: AccessItemType.Member }, + { id: "0m", type: AccessItemType.Member }, + ], items: sampleMembers, }, - render: StandaloneAccessSelectorRender, + render, }; +/** + * Displays the members and groups assigned to a collection. + * + * This is currently used in the **Collection dialog -> Access tab**. + */ export const CollectionAccess: Story = { args: { - permissionMode: "edit", + permissionMode: PermissionMode.Edit, showMemberRoles: false, columnHeader: "Groups/Members", selectorLabelText: "Select groups and members", @@ -270,68 +217,38 @@ export const CollectionAccess: Story = { emptySelectionText: "No members or groups added", disabled: false, initialValue: [ - { id: "3g", permission: CollectionPermission.EditExceptPass }, - { id: "0m", permission: CollectionPermission.View }, + { id: "3g", type: AccessItemType.Group, permission: CollectionPermission.EditExceptPass }, + { id: "0m", type: AccessItemType.Member, permission: CollectionPermission.View }, + { id: "7m", type: AccessItemType.Member, permission: CollectionPermission.Manage }, ], - items: sampleGroups.concat(sampleMembers).concat([ - { - id: "admin-group", - type: AccessItemType.Group, - listName: "Admin Group", - labelName: "Admin Group", - readonly: true, - }, - { - id: "admin-member", - type: AccessItemType.Member, - listName: "Admin Member (admin@email.com)", - labelName: "Admin Member", - status: OrganizationUserStatusType.Confirmed, - role: OrganizationUserType.Admin, - email: "admin@email.com", - readonly: true, - }, - ]), - }, - render: StandaloneAccessSelectorRender, -}; - -const fb = new FormBuilder(); - -const ReactiveFormAccessSelectorRender = (args: any) => ({ - props: { - items: [], - onSubmit: actionsData.onSubmit, - ...args, - }, - template: ` -
- - -
-`, -}); - -export const ReactiveForm: Story = { - args: { - formObj: fb.group({ formItems: [[{ id: "1g" }]] }), - permissionMode: "edit", - showMemberRoles: false, - columnHeader: "Groups/Members", - selectorLabelText: "Select groups and members", - selectorHelpText: - "Permissions set for a member will replace permissions set by that member's group", - emptySelectionText: "No members or groups added", items: sampleGroups.concat(sampleMembers), }, - render: ReactiveFormAccessSelectorRender, + render, +}; + +// TODO: currently the collection dialog duplicates the AccessItemValue.permission on the +// AccessItemView.readonlyPermission, this will be refactored to reduce this duplication: +// https://bitwarden.atlassian.net/browse/PM-11590 +const disabledMembers = itemsFactory(3, AccessItemType.Member); +disabledMembers[1].readonlyPermission = CollectionPermission.Manage; +disabledMembers[2].readonlyPermission = CollectionPermission.View; + +const disabledGroups = itemsFactory(2, AccessItemType.Group); +disabledGroups[0].readonlyPermission = CollectionPermission.ViewExceptPass; + +/** + * Displays the members and groups assigned to a collection when the control is in a disabled state. + */ +export const DisabledCollectionAccess: Story = { + args: { + ...CollectionAccess.args, + disabled: true, + items: disabledGroups.concat(disabledMembers), + initialValue: [ + { id: "1m", type: AccessItemType.Member, permission: CollectionPermission.Manage }, + { id: "2m", type: AccessItemType.Member, permission: CollectionPermission.View }, + { id: "0g", type: AccessItemType.Group, permission: CollectionPermission.ViewExceptPass }, + ], + }, + render, }; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts new file mode 100644 index 00000000000..fb8bdef1d8c --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/storybook-utils.ts @@ -0,0 +1,44 @@ +import { action } from "@storybook/addon-actions"; + +import { AccessItemType, AccessItemView } from "./access-selector.models"; + +export const actionsData = { + onValueChanged: action("onValueChanged"), + onSubmit: action("onSubmit"), +}; + +/** + * Factory to help build semi-realistic looking items + * @param n - The number of items to build + * @param type - Which type to build + */ +export const itemsFactory = (n: number, type: AccessItemType) => { + return [...Array(n)].map((_: unknown, id: number) => { + const item: AccessItemView = { + id: id.toString(), + type: type, + } as AccessItemView; + + switch (item.type) { + case AccessItemType.Collection: + item.labelName = item.listName = `Collection ${id}`; + item.id = item.id + "c"; + item.parentGrouping = "Collection Parent Group " + ((id % 2) + 1); + break; + case AccessItemType.Group: + item.labelName = item.listName = `Group ${id}`; + item.id = item.id + "g"; + break; + case AccessItemType.Member: + item.id = item.id + "m"; + item.email = `member${id}@email.com`; + item.status = id % 3 == 0 ? 0 : 2; + item.labelName = item.status == 2 ? `Member ${id}` : item.email; + item.listName = item.status == 2 ? `${item.labelName} (${item.email})` : item.email; + item.role = id % 5; + break; + } + + return item; + }); +}; From 8aa81dec3b665839076e1924e63fe60fa5b45cd4 Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:33:54 -0500 Subject: [PATCH 19/28] [PM-11388] [Defect] Section headers are missing for View {Item} on web (#10838) * Add Personal Details header. * Add missing translation strings. --- apps/web/src/locales/en/messages.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 293a8cd5052..a03e6332026 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -48,6 +48,30 @@ "loginCredentials": { "message": "Login credentials" }, + "personalDetails": { + "message": "Personal details" + }, + "identification": { + "message": "Identification" + }, + "contactInfo": { + "message": "Contact info" + }, + "cardDetails": { + "message": "Card details" + }, + "cardBrandDetails": { + "message": "$BRAND$ details", + "placeholders": { + "brand": { + "content": "$1", + "example": "Visa" + } + } + }, + "itemHistory": { + "message": "Item history" + }, "authenticatorKey": { "message": "Authenticator key" }, From 5ba39651e27fe6b2463a027be534768c41a7453c Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Wed, 4 Sep 2024 17:38:56 -0500 Subject: [PATCH 20/28] [PM-10833] [Defect] Add items to new dropdown in admin console (#10828) * Add cipher types to "new" dropdown in admin console. * Keep feature flag code consistent. * Add missing menu divider. --- .../app/vault/org-vault/vault-header/vault-header.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html index a85d700194e..f583055687c 100644 --- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html +++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.html @@ -131,6 +131,7 @@ {{ "note" | i18n }} +
From 7cfd6577ac85c939bb0d72bb46b3a6cc4bdf8bc0 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 5 Sep 2024 04:26:12 -0700 Subject: [PATCH 27/28] fix typography to match design (#10888) --- .../send-list-items-container.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html index 586e62cb611..7745e4d6f88 100644 --- a/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html +++ b/libs/tools/send/send-ui/src/send-list-items-container/send-list-items-container.component.html @@ -1,6 +1,6 @@ -

+

{{ headerText }}

{{ sends.length }} From 32903a21f941b8ba31cf6fe7097a0dce2f6b2141 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Thu, 5 Sep 2024 10:16:06 -0400 Subject: [PATCH 28/28] [CL-322] Remove tw-sr-only from form field labels (#10868) --- .../manage/entity-events.component.html | 44 +++++++++---------- .../src/app/auth/two-factor.component.html | 2 +- .../app/settings/domain-rules.component.html | 2 +- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.html b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.html index d296514e357..68582e8b2ea 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.html +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.html @@ -6,31 +6,27 @@
-
- - - - -
+ + {{ "from" | i18n }} + + - -
- - - - -
+ + {{ "to" | i18n }} + +