From 963e339e4f55fbe407ab419f9a948765bc73491e Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Fri, 30 Aug 2024 15:38:37 -0400 Subject: [PATCH 01/64] PM-11429 Users with Except PW permissions will not get Copy Password Option Vault V2 (#10831) --- .../item-copy-action/item-copy-actions.component.html | 8 +++++++- libs/vault/src/services/copy-cipher-field.service.spec.ts | 6 ------ libs/vault/src/services/copy-cipher-field.service.ts | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html index 487168539b9..f4444a10aeb 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/item-copy-action/item-copy-actions.component.html @@ -13,7 +13,13 @@ - + + + + + + + + + + + + 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 new file mode 100644 index 00000000000..1ec0f52aa6d --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash-list-items-container/trash-list-items-container.component.ts @@ -0,0 +1,107 @@ +import { CommonModule } from "@angular/common"; +import { Component, Input } from "@angular/core"; +import { Router } from "@angular/router"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { + DialogService, + IconButtonModule, + ItemModule, + MenuModule, + SectionComponent, + SectionHeaderComponent, + ToastService, +} from "@bitwarden/components"; +import { PasswordRepromptService } from "@bitwarden/vault"; + +@Component({ + selector: "app-trash-list-items-container", + templateUrl: "trash-list-items-container.component.html", + standalone: true, + imports: [ + CommonModule, + ItemModule, + JslibModule, + SectionComponent, + SectionHeaderComponent, + MenuModule, + IconButtonModule, + ], +}) +export class TrashListItemsContainerComponent { + /** + * The list of trashed items to display. + */ + @Input() + ciphers: CipherView[] = []; + + @Input() + headerText: string; + + constructor( + private cipherService: CipherService, + private logService: LogService, + private toastService: ToastService, + private i18nService: I18nService, + private dialogService: DialogService, + private passwordRepromptService: PasswordRepromptService, + private router: Router, + ) {} + + async restore(cipher: CipherView) { + try { + await this.cipherService.restoreWithServer(cipher.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("restoredItem"), + }); + } catch (e) { + this.logService.error(e); + } + } + + async delete(cipher: CipherView) { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); + + if (!repromptPassed) { + return; + } + + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "deleteItem" }, + content: { key: "permanentlyDeleteItemConfirmation" }, + type: "warning", + }); + + if (!confirmed) { + return; + } + + try { + await this.cipherService.deleteWithServer(cipher.id); + this.toastService.showToast({ + variant: "success", + title: null, + message: this.i18nService.t("deletedItem"), + }); + } catch (e) { + this.logService.error(e); + } + } + + async onViewCipher(cipher: CipherView) { + const repromptPassed = await this.passwordRepromptService.passwordRepromptCheck(cipher); + if (!repromptPassed) { + return; + } + + await this.router.navigate(["/view-cipher"], { + queryParams: { cipherId: cipher.id, type: cipher.type }, + }); + } +} diff --git a/apps/browser/src/vault/popup/settings/trash.component.html b/apps/browser/src/vault/popup/settings/trash.component.html new file mode 100644 index 00000000000..ab3b6716504 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash.component.html @@ -0,0 +1,33 @@ + + + + + + + + + {{ "trashWarning" | i18n }} + + + + + + + + + + {{ "noItemsInTrash" | i18n }} + + + {{ "noItemsInTrashDesc" | i18n }} + + + + + diff --git a/apps/browser/src/vault/popup/settings/trash.component.ts b/apps/browser/src/vault/popup/settings/trash.component.ts new file mode 100644 index 00000000000..b6f77ef6a52 --- /dev/null +++ b/apps/browser/src/vault/popup/settings/trash.component.ts @@ -0,0 +1,37 @@ +import { CommonModule } from "@angular/common"; +import { Component } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { CalloutModule, NoItemsModule } from "@bitwarden/components"; +import { VaultIcons } from "@bitwarden/vault"; + +import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; +import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component"; +import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; +import { VaultListItemsContainerComponent } from "../components/vault-v2/vault-list-items-container/vault-list-items-container.component"; +import { VaultPopupItemsService } from "../services/vault-popup-items.service"; + +import { TrashListItemsContainerComponent } from "./trash-list-items-container/trash-list-items-container.component"; + +@Component({ + templateUrl: "trash.component.html", + standalone: true, + imports: [ + CommonModule, + JslibModule, + PopupPageComponent, + PopupHeaderComponent, + PopOutComponent, + VaultListItemsContainerComponent, + TrashListItemsContainerComponent, + CalloutModule, + NoItemsModule, + ], +}) +export class TrashComponent { + protected deletedCiphers$ = this.vaultPopupItemsService.deletedCiphers$; + + protected emptyTrashIcon = VaultIcons.EmptyTrash; + + constructor(private vaultPopupItemsService: VaultPopupItemsService) {} +} diff --git a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html index 10243bdaa9f..03dd1182fbb 100644 --- a/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html +++ b/apps/browser/src/vault/popup/settings/vault-settings-v2.component.html @@ -24,6 +24,12 @@ + + + {{ "trash" | i18n }} + + + diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.html b/apps/web/src/app/vault/components/vault-items/vault-items.component.html index 2f294a758db..c72daeffff4 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.html +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.html @@ -47,7 +47,7 @@ (click)="bulkEditCollectionAccess()" > - {{ "access" | i18n }} + {{ "editAccess" | i18n }} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 76d16036579..293a8cd5052 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9025,5 +9025,8 @@ }, "additionalContentAvailable": { "message": "Additional content is available" + }, + "editAccess": { + "message": "Edit access" } } From c5a267baadc70f294467a6d7f48809431e308a54 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Sat, 31 Aug 2024 12:26:11 -0700 Subject: [PATCH 06/64] [PM-11000] AnonLayout Icon/Logo theme refactor (#10549) * update base anon-layout logo/icon * update ExtensionAnonLayout logo/icon based on theme * remove hard-coded fill * remove solarizedDark class --------- Co-authored-by: Bernd Schoolmann --- ...extension-anon-layout-wrapper.component.ts | 19 ++------- .../extension-anon-layout-wrapper.stories.ts | 9 ---- .../extension-bitwarden-logo.icon.ts | 21 +--------- .../anon-layout-wrapper.stories.ts | 9 ---- .../anon-layout/anon-layout.component.ts | 42 ++++--------------- .../anon-layout/anon-layout.stories.ts | 9 +--- .../src/angular/icons/bitwarden-logo.icon.ts | 16 ++----- .../angular/icons/bitwarden-shield.icon.ts | 12 ++---- libs/components/src/tw-theme.css | 8 ++++ libs/components/tailwind.config.base.js | 1 + 10 files changed, 29 insertions(+), 117 deletions(-) diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts index 7a5b156a506..350b4a8a84d 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component.ts @@ -1,7 +1,7 @@ import { CommonModule } from "@angular/common"; import { Component, OnDestroy, OnInit } from "@angular/core"; import { ActivatedRoute, Data, NavigationEnd, Router, RouterModule } from "@angular/router"; -import { Subject, filter, firstValueFrom, switchMap, takeUntil, tap } from "rxjs"; +import { Subject, filter, switchMap, takeUntil, tap } from "rxjs"; import { AnonLayoutComponent, @@ -9,7 +9,6 @@ import { AnonLayoutWrapperDataService, } from "@bitwarden/auth/angular"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { Icon, IconModule } from "@bitwarden/components"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -17,10 +16,7 @@ import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-heade import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component"; import { CurrentAccountComponent } from "../account-switching/current-account.component"; -import { - ExtensionBitwardenLogoPrimary, - ExtensionBitwardenLogoWhite, -} from "./extension-bitwarden-logo.icon"; +import { ExtensionBitwardenLogo } from "./extension-bitwarden-logo.icon"; export interface ExtensionAnonLayoutWrapperData extends AnonLayoutWrapperData { showAcctSwitcher?: boolean; @@ -56,14 +52,13 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { protected maxWidth: "md" | "3xl"; protected theme: string; - protected logo: Icon; + protected logo = ExtensionBitwardenLogo; constructor( private router: Router, private route: ActivatedRoute, private i18nService: I18nService, private extensionAnonLayoutWrapperDataService: AnonLayoutWrapperDataService, - private themeStateService: ThemeStateService, ) {} async ngOnInit(): Promise { @@ -73,14 +68,6 @@ export class ExtensionAnonLayoutWrapperComponent implements OnInit, OnDestroy { // Listen for page changes and update the page data appropriately this.listenForPageDataChanges(); this.listenForServiceDataChanges(); - - this.theme = await firstValueFrom(this.themeStateService.selectedTheme$); - - if (this.theme === "dark") { - this.logo = ExtensionBitwardenLogoWhite; - } else { - this.logo = ExtensionBitwardenLogoPrimary; - } } private listenForPageDataChanges() { diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts index 44060f991ff..beb07f3523a 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.stories.ts @@ -22,8 +22,6 @@ import { } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { UserId } from "@bitwarden/common/types/guid"; import { ButtonModule, I18nMockService } from "@bitwarden/components"; @@ -47,7 +45,6 @@ const decorators = (options: { applicationVersion?: string; clientType?: ClientType; hostName?: string; - themeType?: ThemeType; }) => { return [ componentWrapperDecorator( @@ -120,12 +117,6 @@ const decorators = (options: { getClientType: () => options.clientType || ClientType.Web, } as Partial, }, - { - provide: ThemeStateService, - useValue: { - selectedTheme$: of(options.themeType || ThemeType.Light), - } as Partial, - }, { provide: I18nService, useFactory: () => { diff --git a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts index 569edaae978..51d748e1fbb 100644 --- a/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts +++ b/apps/browser/src/auth/popup/extension-anon-layout-wrapper/extension-bitwarden-logo.icon.ts @@ -1,6 +1,6 @@ import { svgIcon } from "@bitwarden/components"; -export const ExtensionBitwardenLogoPrimary = svgIcon` +export const ExtensionBitwardenLogo = svgIcon` - -`; - -export const ExtensionBitwardenLogoWhite = svgIcon` - - `; diff --git a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts index c05f491acd2..87e26bd2df1 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout-wrapper.stories.ts @@ -15,8 +15,6 @@ import { Environment, } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ThemeType } from "@bitwarden/common/platform/enums"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ButtonModule } from "@bitwarden/components"; // FIXME: remove `/apps` import from `/libs` @@ -40,7 +38,6 @@ const decorators = (options: { applicationVersion?: string; clientType?: ClientType; hostName?: string; - themeType?: ThemeType; }) => { return [ componentWrapperDecorator( @@ -84,12 +81,6 @@ const decorators = (options: { getClientType: () => options.clientType || ClientType.Web, } as Partial, }, - { - provide: ThemeStateService, - useValue: { - selectedTheme$: of(options.themeType || ThemeType.Light), - } as Partial, - }, ], }), applicationConfig({ diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.ts b/libs/auth/src/angular/anon-layout/anon-layout.component.ts index fc3026dad34..a40fafc5db9 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.ts @@ -5,13 +5,11 @@ import { firstValueFrom } from "rxjs"; import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { IconModule, Icon } from "../../../../components/src/icon"; import { SharedModule } from "../../../../components/src/shared"; import { TypographyModule } from "../../../../components/src/typography"; -import { BitwardenLogoPrimary, BitwardenLogoWhite } from "../icons"; -import { BitwardenShieldPrimary, BitwardenShieldWhite } from "../icons/bitwarden-shield.icon"; +import { BitwardenLogo, BitwardenShield } from "../icons"; @Component({ standalone: true, @@ -34,20 +32,17 @@ export class AnonLayoutComponent implements OnInit, OnChanges { */ @Input() maxWidth: "md" | "3xl" = "md"; - protected logo: Icon; - + protected logo = BitwardenLogo; protected year = "2024"; protected clientType: ClientType; protected hostname: string; protected version: string; - protected theme: string; protected hideYearAndVersion = false; constructor( private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, - private themeStateService: ThemeStateService, ) { this.year = new Date().getFullYear().toString(); this.clientType = this.platformUtilsService.getClientType(); @@ -56,41 +51,18 @@ export class AnonLayoutComponent implements OnInit, OnChanges { async ngOnInit() { this.maxWidth = this.maxWidth ?? "md"; - - this.theme = await firstValueFrom(this.themeStateService.selectedTheme$); - - if (this.theme === "dark") { - this.logo = BitwardenLogoWhite; - } else { - this.logo = BitwardenLogoPrimary; - } - - await this.updateIcon(this.theme); - this.hostname = (await firstValueFrom(this.environmentService.environment$)).getHostname(); this.version = await this.platformUtilsService.getApplicationVersion(); + + // If there is no icon input, then use the default icon + if (this.icon == null) { + this.icon = BitwardenShield; + } } async ngOnChanges(changes: SimpleChanges) { - if (changes.icon) { - const theme = await firstValueFrom(this.themeStateService.selectedTheme$); - await this.updateIcon(theme); - } - if (changes.maxWidth) { this.maxWidth = changes.maxWidth.currentValue ?? "md"; } } - - private async updateIcon(theme: string) { - if (this.icon == null) { - if (theme === "dark") { - this.icon = BitwardenShieldWhite; - } - - if (theme !== "dark") { - this.icon = BitwardenShieldPrimary; - } - } - } } diff --git a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts index edf6c8d70a1..110bda7ce81 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.stories.ts +++ b/libs/auth/src/angular/anon-layout/anon-layout.stories.ts @@ -1,11 +1,10 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; -import { BehaviorSubject, of } from "rxjs"; +import { BehaviorSubject } from "rxjs"; import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service"; import { ButtonModule } from "../../../../components/src/button"; import { I18nMockService } from "../../../../components/src/utils/i18n-mock.service"; @@ -47,12 +46,6 @@ export default { }).asObservable(), }, }, - { - provide: ThemeStateService, - useValue: { - selectedTheme$: of("light"), - }, - }, ], }), ], diff --git a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts index b9094befff1..2a1ae48526b 100644 --- a/libs/auth/src/angular/icons/bitwarden-logo.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-logo.icon.ts @@ -1,17 +1,9 @@ import { svgIcon } from "@bitwarden/components"; -export const BitwardenLogoPrimary = svgIcon` - +export const BitwardenLogo = svgIcon` + Bitwarden - - - -`; - -export const BitwardenLogoWhite = svgIcon` - - Bitwarden - - + + `; diff --git a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts index a81a9906a6b..86e3a0bb1b2 100644 --- a/libs/auth/src/angular/icons/bitwarden-shield.icon.ts +++ b/libs/auth/src/angular/icons/bitwarden-shield.icon.ts @@ -1,13 +1,7 @@ import { svgIcon } from "@bitwarden/components"; -export const BitwardenShieldPrimary = svgIcon` - - - -`; - -export const BitwardenShieldWhite = svgIcon` - - +export const BitwardenShield = svgIcon` + + `; diff --git a/libs/components/src/tw-theme.css b/libs/components/src/tw-theme.css index 00ab2ff717e..6234ba380bc 100644 --- a/libs/components/src/tw-theme.css +++ b/libs/components/src/tw-theme.css @@ -49,6 +49,8 @@ --color-text-code: 192 17 118; --color-text-headers: 2 15 102; + --color-marketing-logo: 23 93 220; + --tw-ring-offset-color: #ffffff; } @@ -95,6 +97,8 @@ --color-text-code: 240 141 199; --color-text-headers: 226 227 228; + --color-marketing-logo: 255 255 255; + --tw-ring-offset-color: #1f242e; } @@ -134,6 +138,8 @@ --color-text-alt2: 255 255 255; --color-text-code: 219 177 211; + --color-marketing-logo: 255 255 255; + --tw-ring-offset-color: #434c5e; } @@ -173,6 +179,8 @@ --color-text-alt2: 255 255 255; --color-text-code: 240 141 199; + --color-marketing-logo: 255 255 255; + --tw-ring-offset-color: #002b36; } diff --git a/libs/components/tailwind.config.base.js b/libs/components/tailwind.config.base.js index 88e7549780f..236baed74c8 100644 --- a/libs/components/tailwind.config.base.js +++ b/libs/components/tailwind.config.base.js @@ -69,6 +69,7 @@ module.exports = { alt3: rgba("--color-background-alt3"), alt4: rgba("--color-background-alt4"), }, + "marketing-logo": rgba("--color-marketing-logo"), }, textColor: { main: rgba("--color-text-main"), From b339c01c20db6c6714e78294bfd90a43c9442087 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Sat, 31 Aug 2024 13:19:48 -0700 Subject: [PATCH 07/64] remove position on browser (#10810) --- .../auth/src/angular/anon-layout/anon-layout.component.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/auth/src/angular/anon-layout/anon-layout.component.html b/libs/auth/src/angular/anon-layout/anon-layout.component.html index 082edf40630..39570dabc8b 100644 --- a/libs/auth/src/angular/anon-layout/anon-layout.component.html +++ b/libs/auth/src/angular/anon-layout/anon-layout.component.html @@ -1,6 +1,10 @@
From 5436072a751e9dc785c7ba180697ea936480be7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 07:21:17 -0400 Subject: [PATCH 08/64] [deps] Autofill: Update wait-on to v8 (#10849) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 12 ++++++------ package.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index c3eeb5d529b..a9e95fec1f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -179,7 +179,7 @@ "typescript": "5.1.6", "url": "0.11.3", "util": "0.12.5", - "wait-on": "7.2.0", + "wait-on": "8.0.0", "webpack": "5.94.0", "webpack-cli": "5.1.4", "webpack-dev-server": "5.0.4", @@ -38305,14 +38305,14 @@ } }, "node_modules/wait-on": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", - "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-8.0.0.tgz", + "integrity": "sha512-fNE5SXinLr2Bt7cJvjvLg2PcXfqznlqRvtE3f8AqYdRZ9BhE+XpsCp1mwQbRoO7s1q7uhAuCw0Ro3mG/KdZjEw==", "dev": true, "license": "MIT", "dependencies": { - "axios": "^1.6.1", - "joi": "^17.11.0", + "axios": "^1.7.4", + "joi": "^17.13.3", "lodash": "^4.17.21", "minimist": "^1.2.8", "rxjs": "^7.8.1" diff --git a/package.json b/package.json index 478c93c52d3..722fd6c3e00 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "typescript": "5.1.6", "url": "0.11.3", "util": "0.12.5", - "wait-on": "7.2.0", + "wait-on": "8.0.0", "webpack": "5.94.0", "webpack-cli": "5.1.4", "webpack-dev-server": "5.0.4", From 5dac4b94e154f3c0cd5e6d35bb176f43fad6ffed Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:13:53 +0200 Subject: [PATCH 09/64] [deps] Platform: Update argon2 to v0.41.1 (#9819) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- apps/desktop/src/package-lock.json | 23 +++++++++++++---------- apps/desktop/src/package.json | 2 +- package-lock.json | 25 ++++++++++++++----------- package.json | 2 +- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 8fe0284ed7d..2fe4f520611 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -58,7 +58,7 @@ "dependencies": { "@koa/multer": "3.0.2", "@koa/router": "12.0.1", - "argon2": "0.40.1", + "argon2": "0.41.1", "big-integer": "1.6.51", "browser-hrtime": "1.1.8", "chalk": "4.1.2", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index a37c185d170..62ba98626f1 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -10,7 +10,7 @@ "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.40.1" + "argon2": "0.41.1" } }, "../desktop_native/napi": { @@ -35,25 +35,28 @@ } }, "node_modules/argon2": { - "version": "0.40.1", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", - "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", + "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@phc/format": "^1.0.0", - "node-addon-api": "^7.1.0", - "node-gyp-build": "^4.8.0" + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" }, "engines": { "node": ">=16.17.0" } }, "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", + "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-gyp-build": { "version": "4.8.2", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index df1f7f70d41..49eced14692 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -13,6 +13,6 @@ }, "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.40.1" + "argon2": "0.41.1" } } diff --git a/package-lock.json b/package-lock.json index a9e95fec1f9..605362af059 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", - "argon2": "0.40.1", + "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.51", "bootstrap": "4.6.0", @@ -201,7 +201,7 @@ "dependencies": { "@koa/multer": "3.0.2", "@koa/router": "12.0.1", - "argon2": "0.40.1", + "argon2": "0.41.1", "big-integer": "1.6.51", "browser-hrtime": "1.1.8", "chalk": "4.1.2", @@ -11792,15 +11792,15 @@ "license": "MIT" }, "node_modules/argon2": { - "version": "0.40.1", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", - "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", + "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@phc/format": "^1.0.0", - "node-addon-api": "^7.1.0", - "node-gyp-build": "^4.8.0" + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" }, "engines": { "node": ">=16.17.0" @@ -28464,10 +28464,13 @@ "license": "MIT" }, "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", + "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } }, "node_modules/node-api-version": { "version": "0.2.0", diff --git a/package.json b/package.json index 722fd6c3e00..b00afa49a07 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", - "argon2": "0.40.1", + "argon2": "0.41.1", "argon2-browser": "1.18.0", "big-integer": "1.6.51", "bootstrap": "4.6.0", From 76021b48171b5471a4872cb9d9404c50fbc2fe05 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:59:28 +0100 Subject: [PATCH 10/64] Fix mSecure importer and added tests (#10698) --- .../spec/msecure-csv-importer.spec.ts | 113 ++++++++++++++++++ .../src/importers/msecure-csv-importer.ts | 51 ++++++-- 2 files changed, 157 insertions(+), 7 deletions(-) create mode 100644 libs/importer/spec/msecure-csv-importer.spec.ts diff --git a/libs/importer/spec/msecure-csv-importer.spec.ts b/libs/importer/spec/msecure-csv-importer.spec.ts new file mode 100644 index 00000000000..903be3bcb18 --- /dev/null +++ b/libs/importer/spec/msecure-csv-importer.spec.ts @@ -0,0 +1,113 @@ +import { CipherType } from "@bitwarden/common/vault/enums"; + +import { MSecureCsvImporter } from "../src/importers/msecure-csv-importer"; + +describe("MSecureCsvImporter.parse", () => { + let importer: MSecureCsvImporter; + beforeEach(() => { + importer = new MSecureCsvImporter(); + }); + + it("should correctly parse credit card entries as Secret Notes", async () => { + const mockCsvData = + `myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|,`.trim(); + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("myCreditCard"); + expect(cipher.type).toBe(CipherType.Card); + expect(cipher.card.number).toBe("41111111111111111"); + expect(cipher.card.expiration).toBe("05 / 2026"); + expect(cipher.card.code).toBe("123"); + expect(cipher.card.cardholderName).toBe("John Doe"); + expect(cipher.card.brand).toBe("Visa"); + }); + + it("should correctly parse login entries", async () => { + const mockCsvData = ` + Bitwarden|810974637,Login,,,Website|2|bitwarden.com,Username|7|bitwarden user,Password|8|bitpassword, + `.trim(); + + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("Bitwarden"); + expect(cipher.type).toBe(CipherType.Login); + expect(cipher.login.username).toBe("bitwarden user"); + expect(cipher.login.password).toBe("bitpassword"); + expect(cipher.login.uris[0].uri).toContain("bitwarden.com"); + }); + + it("should correctly parse login entries with notes", async () => { + const mockCsvData = + `Example|188987444,Login,,This is a note |,Website|2|example2.com,Username|7|username || lol,Password|8|this is a password,`.trim(); + + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("Example"); + expect(cipher.type).toBe(CipherType.Login); + expect(cipher.login.username).toBe("username || lol"); + expect(cipher.login.password).toBe("this is a password"); + expect(cipher.login.uris[0].uri).toContain("example2.com"); + expect(cipher.notes).toBe("This is a note |"); + }); + + it("should correctly parse login entries with a tag", async () => { + const mockCsvData = ` + Website with a tag|1401978655,Login,tag holding it,,Website|2|johndoe.com,Username|7|JohnDoeWebsite,Password|8|JohnDoePassword, + `.trim(); + + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("Website with a tag"); + expect(cipher.type).toBe(CipherType.Login); + expect(cipher.login.username).toBe("JohnDoeWebsite"); + expect(cipher.login.password).toBe("JohnDoePassword"); + expect(cipher.login.uris[0].uri).toContain("johndoe.com"); + expect(cipher.notes).toBeNull(); + expect(result.folders[0].name).toContain("tag holding it"); + }); + + it("should handle multiple entries correctly", async () => { + const mockCsvData = + `myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|, +Bitwarden|810974637,Login,,,Website|2|bitwarden.com,Username|7|bitwarden user,Password|8|bitpassword, +Example|188987444,Login,,This is a note |,Website|2|example2.com,Username|7|username || lol,Password|8|this is a password, +Website with a tag|1401978655,Login,tag holding it,,Website|2|johndoe.com,Username|7|JohnDoeWebsite,Password|8|JohnDoePassword,`.trim(); + + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(4); + + // Check first entry (Credit Card) + const cipher1 = result.ciphers[0]; + expect(cipher1.name).toBe("myCreditCard"); + expect(cipher1.type).toBe(CipherType.Card); + + // Check second entry (Login - Bitwarden) + const cipher2 = result.ciphers[1]; + expect(cipher2.name).toBe("Bitwarden"); + expect(cipher2.type).toBe(CipherType.Login); + + // Check third entry (Login with note - Example) + const cipher3 = result.ciphers[2]; + expect(cipher3.name).toBe("Example"); + expect(cipher3.type).toBe(CipherType.Login); + + // Check fourth entry (Login with tag - Website with a tag) + const cipher4 = result.ciphers[3]; + expect(cipher4.name).toBe("Website with a tag"); + expect(cipher4.type).toBe(CipherType.Login); + }); +}); diff --git a/libs/importer/src/importers/msecure-csv-importer.ts b/libs/importer/src/importers/msecure-csv-importer.ts index 788dfd1d4e2..e953ce3a8db 100644 --- a/libs/importer/src/importers/msecure-csv-importer.ts +++ b/libs/importer/src/importers/msecure-csv-importer.ts @@ -9,7 +9,7 @@ import { Importer } from "./importer"; export class MSecureCsvImporter extends BaseImporter implements Importer { parse(data: string): Promise { const result = new ImportResult(); - const results = this.parseCsv(data, false); + const results = this.parseCsv(data, false, { delimiter: "," }); if (results == null) { result.success = false; return Promise.resolve(result); @@ -21,17 +21,43 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { } const folderName = - this.getValueOrDefault(value[0], "Unassigned") !== "Unassigned" ? value[0] : null; + this.getValueOrDefault(value[2], "Unassigned") !== "Unassigned" ? value[2] : null; this.processFolder(result, folderName); const cipher = this.initLoginCipher(); - cipher.name = this.getValueOrDefault(value[2], "--"); + cipher.name = this.getValueOrDefault(value[0].split("|")[0], "--"); if (value[1] === "Web Logins" || value[1] === "Login") { - cipher.login.uris = this.makeUriArray(value[4]); - cipher.login.username = this.getValueOrDefault(value[5]); - cipher.login.password = this.getValueOrDefault(value[6]); + cipher.login.username = this.getValueOrDefault(this.splitValueRetainingLastPart(value[5])); + cipher.login.uris = this.makeUriArray(this.splitValueRetainingLastPart(value[4])); + cipher.login.password = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6])); cipher.notes = !this.isNullOrWhitespace(value[3]) ? value[3].split("\\n").join("\n") : null; + } else if (value[1] === "Credit Card") { + cipher.type = CipherType.Card; + cipher.card.number = this.getValueOrDefault(this.splitValueRetainingLastPart(value[4])); + + const [month, year] = this.getValueOrDefault( + this.splitValueRetainingLastPart(value[5]), + ).split("/"); + cipher.card.expMonth = month.trim(); + cipher.card.expYear = year.trim(); + cipher.card.code = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6])); + cipher.card.cardholderName = this.getValueOrDefault( + this.splitValueRetainingLastPart(value[7]), + ); + cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9])); + cipher.notes = + this.getValueOrDefault(value[8].split("|")[0]) + + ": " + + this.getValueOrDefault(this.splitValueRetainingLastPart(value[8]), "") + + "\n" + + this.getValueOrDefault(value[10].split("|")[0]) + + ": " + + this.getValueOrDefault(this.splitValueRetainingLastPart(value[10]), "") + + "\n" + + this.getValueOrDefault(value[11].split("|")[0]) + + ": " + + this.getValueOrDefault(this.splitValueRetainingLastPart(value[11]), ""); } else if (value.length > 3) { cipher.type = CipherType.SecureNote; cipher.secureNote = new SecureNoteView(); @@ -43,7 +69,11 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { } } - if (!this.isNullOrWhitespace(value[1]) && cipher.type !== CipherType.Login) { + if ( + !this.isNullOrWhitespace(value[1]) && + cipher.type !== CipherType.Login && + cipher.type !== CipherType.Card + ) { cipher.name = value[1] + ": " + cipher.name; } @@ -58,4 +88,11 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { result.success = true; return Promise.resolve(result); } + + // mSecure returns values separated by "|" where after the second separator is the value + // like "Password|8|myPassword", we want to keep the "myPassword" but also ensure that if + // the value contains any "|" it works fine + private splitValueRetainingLastPart(value: string) { + return value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop(); + } } From 60fca9c11833c297b0f3f67f6ffa892c1dccb95b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 3 Sep 2024 14:06:23 +0200 Subject: [PATCH 11/64] Revert "[deps] Platform: Update argon2 to v0.41.1 (#9819)" (#10858) This reverts commit 5dac4b94e154f3c0cd5e6d35bb176f43fad6ffed. --- apps/cli/package.json | 2 +- apps/desktop/src/package-lock.json | 23 ++++++++++------------- apps/desktop/src/package.json | 2 +- package-lock.json | 25 +++++++++++-------------- package.json | 2 +- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 2fe4f520611..8fe0284ed7d 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -58,7 +58,7 @@ "dependencies": { "@koa/multer": "3.0.2", "@koa/router": "12.0.1", - "argon2": "0.41.1", + "argon2": "0.40.1", "big-integer": "1.6.51", "browser-hrtime": "1.1.8", "chalk": "4.1.2", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 62ba98626f1..a37c185d170 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -10,7 +10,7 @@ "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "argon2": "0.40.1" } }, "../desktop_native/napi": { @@ -35,28 +35,25 @@ } }, "node_modules/argon2": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", - "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", + "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@phc/format": "^1.0.0", - "node-addon-api": "^8.1.0", - "node-gyp-build": "^4.8.1" + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" }, "engines": { "node": ">=16.17.0" } }, "node_modules/node-addon-api": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", - "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" }, "node_modules/node-gyp-build": { "version": "4.8.2", diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 49eced14692..df1f7f70d41 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -13,6 +13,6 @@ }, "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi", - "argon2": "0.41.1" + "argon2": "0.40.1" } } diff --git a/package-lock.json b/package-lock.json index 605362af059..a9e95fec1f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", - "argon2": "0.41.1", + "argon2": "0.40.1", "argon2-browser": "1.18.0", "big-integer": "1.6.51", "bootstrap": "4.6.0", @@ -201,7 +201,7 @@ "dependencies": { "@koa/multer": "3.0.2", "@koa/router": "12.0.1", - "argon2": "0.41.1", + "argon2": "0.40.1", "big-integer": "1.6.51", "browser-hrtime": "1.1.8", "chalk": "4.1.2", @@ -11792,15 +11792,15 @@ "license": "MIT" }, "node_modules/argon2": { - "version": "0.41.1", - "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", - "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", + "version": "0.40.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.40.1.tgz", + "integrity": "sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==", "hasInstallScript": true, "license": "MIT", "dependencies": { "@phc/format": "^1.0.0", - "node-addon-api": "^8.1.0", - "node-gyp-build": "^4.8.1" + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" }, "engines": { "node": ">=16.17.0" @@ -28464,13 +28464,10 @@ "license": "MIT" }, "node_modules/node-addon-api": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", - "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" }, "node_modules/node-api-version": { "version": "0.2.0", diff --git a/package.json b/package.json index b00afa49a07..722fd6c3e00 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", "@ng-select/ng-select": "11.2.0", - "argon2": "0.41.1", + "argon2": "0.40.1", "argon2-browser": "1.18.0", "big-integer": "1.6.51", "bootstrap": "4.6.0", From 5f5e4498e84391f8e848bbece7b43ec7a71cc8b2 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 3 Sep 2024 16:56:55 +0200 Subject: [PATCH 12/64] [PM-11593] Fix backgroundbrowserbiometricservice initialization (#10861) * Fix backgroundbrowserbiometricservice initialization * Cleanup according to review --- apps/browser/src/background/main.background.ts | 6 +++++- .../services/background-browser-biometrics.service.ts | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index cf393e0a44c..0da55cbda5f 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -401,6 +401,8 @@ export default class MainBackground { const logoutCallback = async (logoutReason: LogoutReason, userId?: UserId) => await this.logout(logoutReason, userId); + const runtimeNativeMessagingBackground = () => this.nativeMessagingBackground; + const refreshAccessTokenErrorCallback = () => { // Send toast to popup this.messagingService.send("showToast", { @@ -616,7 +618,9 @@ export default class MainBackground { this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider); - this.biometricsService = new BackgroundBrowserBiometricsService(this.nativeMessagingBackground); + this.biometricsService = new BackgroundBrowserBiometricsService( + runtimeNativeMessagingBackground, + ); this.kdfConfigService = new KdfConfigService(this.stateProvider); diff --git a/apps/browser/src/platform/services/background-browser-biometrics.service.ts b/apps/browser/src/platform/services/background-browser-biometrics.service.ts index 41ae15972cd..0cd48c45938 100644 --- a/apps/browser/src/platform/services/background-browser-biometrics.service.ts +++ b/apps/browser/src/platform/services/background-browser-biometrics.service.ts @@ -6,20 +6,20 @@ import { BrowserBiometricsService } from "./browser-biometrics.service"; @Injectable() export class BackgroundBrowserBiometricsService extends BrowserBiometricsService { - constructor(private nativeMessagingBackground: NativeMessagingBackground) { + constructor(private nativeMessagingBackground: () => NativeMessagingBackground) { super(); } async authenticateBiometric(): Promise { - const responsePromise = this.nativeMessagingBackground.getResponse(); - await this.nativeMessagingBackground.send({ command: "biometricUnlock" }); + const responsePromise = this.nativeMessagingBackground().getResponse(); + await this.nativeMessagingBackground().send({ command: "biometricUnlock" }); const response = await responsePromise; return response.response === "unlocked"; } async isBiometricUnlockAvailable(): Promise { - const responsePromise = this.nativeMessagingBackground.getResponse(); - await this.nativeMessagingBackground.send({ command: "biometricUnlockAvailable" }); + const responsePromise = this.nativeMessagingBackground().getResponse(); + await this.nativeMessagingBackground().send({ command: "biometricUnlockAvailable" }); const response = await responsePromise; return response.response === "available"; } From 87a1742a433248c73056f78cd8754789a9fba4eb Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:24:14 -0500 Subject: [PATCH 13/64] lowercase header for add/edit v2 (#10770) --- .../components/vault-v2/add-edit/add-edit-v2.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index b1e95afb535..7664c7e0ca1 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -312,13 +312,13 @@ export class AddEditV2Component implements OnInit { switch (type) { case CipherType.Login: - return this.i18nService.t(partOne, this.i18nService.t("typeLogin")); + return this.i18nService.t(partOne, this.i18nService.t("typeLogin").toLocaleLowerCase()); case CipherType.Card: - return this.i18nService.t(partOne, this.i18nService.t("typeCard")); + return this.i18nService.t(partOne, this.i18nService.t("typeCard").toLocaleLowerCase()); case CipherType.Identity: - return this.i18nService.t(partOne, this.i18nService.t("typeIdentity")); + return this.i18nService.t(partOne, this.i18nService.t("typeIdentity").toLocaleLowerCase()); case CipherType.SecureNote: - return this.i18nService.t(partOne, this.i18nService.t("note")); + return this.i18nService.t(partOne, this.i18nService.t("note").toLocaleLowerCase()); } } } From 1951b70fd1c781937b256e1f71ed20c0d1ee0e88 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Tue, 3 Sep 2024 10:25:10 -0500 Subject: [PATCH 14/64] implement bitLink for password history (#10769) --- .../src/cipher-view/item-history/item-history-v2.component.html | 1 + .../src/cipher-view/item-history/item-history-v2.component.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index 4d86645c8bc..a03639dee61 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -27,6 +27,7 @@

Date: Tue, 3 Sep 2024 10:30:46 -0500 Subject: [PATCH 15/64] Use alt background for view dialog. (#10763) --- apps/web/src/app/vault/individual-vault/view.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/individual-vault/view.component.html b/apps/web/src/app/vault/individual-vault/view.component.html index a70f1be49d7..d1caf76192d 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.html +++ b/apps/web/src/app/vault/individual-vault/view.component.html @@ -1,4 +1,4 @@ - +
{{ cipherTypeString }} From b27dc44298436a8c6cc64d5681cd5a78b4a8fd95 Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:40:11 -0700 Subject: [PATCH 16/64] [PM-11136] Convert LoginEmailService email property to state provider (#10624) * convert email property to state provider * update tests * assign loginEmail to variable before passing in * remove nav logic in ngOnInit --- apps/browser/src/auth/popup/home.component.ts | 4 +-- .../browser/src/auth/popup/login.component.ts | 13 ++++---- apps/web/src/app/auth/hint.component.ts | 4 +-- ...base-login-decryption-options.component.ts | 4 +-- .../src/auth/components/hint.component.ts | 5 ++-- .../login-via-auth-request.component.ts | 10 ++----- .../src/auth/components/login.component.ts | 4 +-- .../abstractions/login-email.service.ts | 19 ++++++------ .../login-email/login-email.service.spec.ts | 18 +++++------ .../login-email/login-email.service.ts | 30 ++++++++++++------- .../src/platform/state/state-definitions.ts | 1 + 11 files changed, 58 insertions(+), 54 deletions(-) diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts index 505931ad0f1..cd9dfc3702b 100644 --- a/apps/browser/src/auth/popup/home.component.ts +++ b/apps/browser/src/auth/popup/home.component.ts @@ -41,7 +41,7 @@ export class HomeComponent implements OnInit, OnDestroy { ) {} async ngOnInit(): Promise { - const email = this.loginEmailService.getEmail(); + const email = await firstValueFrom(this.loginEmailService.loginEmail$); const rememberEmail = this.loginEmailService.getRememberEmail(); if (email != null) { @@ -93,7 +93,7 @@ export class HomeComponent implements OnInit, OnDestroy { async setLoginEmailValues() { // Note: Browser saves email settings here instead of the login component this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); - this.loginEmailService.setEmail(this.formGroup.value.email); + await this.loginEmailService.setLoginEmail(this.formGroup.value.email); await this.loginEmailService.saveEmailSettings(); } } diff --git a/apps/browser/src/auth/popup/login.component.ts b/apps/browser/src/auth/popup/login.component.ts index 6e73199969a..09bfdbbc240 100644 --- a/apps/browser/src/auth/popup/login.component.ts +++ b/apps/browser/src/auth/popup/login.component.ts @@ -1,4 +1,4 @@ -import { Component, NgZone } from "@angular/core"; +import { Component, NgZone, OnInit } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -31,7 +31,7 @@ import { flagEnabled } from "../../platform/flags"; selector: "app-login", templateUrl: "login.component.html", }) -export class LoginComponent extends BaseLoginComponent { +export class LoginComponent extends BaseLoginComponent implements OnInit { showPasswordless = false; constructor( devicesApiService: DevicesApiServiceAbstraction, @@ -83,13 +83,14 @@ export class LoginComponent extends BaseLoginComponent { }; super.successRoute = "/tabs/vault"; this.showPasswordless = flagEnabled("showPasswordless"); + } + async ngOnInit(): Promise { if (this.showPasswordless) { - this.formGroup.controls.email.setValue(this.loginEmailService.getEmail()); + const loginEmail = await firstValueFrom(this.loginEmailService.loginEmail$); + this.formGroup.controls.email.setValue(loginEmail); this.formGroup.controls.rememberEmail.setValue(this.loginEmailService.getRememberEmail()); - // 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.validateEmail(); + await this.validateEmail(); } } diff --git a/apps/web/src/app/auth/hint.component.ts b/apps/web/src/app/auth/hint.component.ts index 42744546234..753bdb342f9 100644 --- a/apps/web/src/app/auth/hint.component.ts +++ b/apps/web/src/app/auth/hint.component.ts @@ -44,8 +44,8 @@ export class HintComponent extends BaseHintComponent implements OnInit { ); } - ngOnInit(): void { - super.ngOnInit(); + async ngOnInit(): Promise { + await super.ngOnInit(); this.emailFormControl.setValue(this.email); } 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 80088bf7f91..6487c0cf847 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 @@ -251,12 +251,12 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy { return; } - this.loginEmailService.setEmail(this.data.userEmail); + this.loginEmailService.setLoginEmail(this.data.userEmail); await this.router.navigate(["/login-with-device"]); } async requestAdminApproval() { - this.loginEmailService.setEmail(this.data.userEmail); + this.loginEmailService.setLoginEmail(this.data.userEmail); await this.router.navigate(["/admin-approval-requested"]); } diff --git a/libs/angular/src/auth/components/hint.component.ts b/libs/angular/src/auth/components/hint.component.ts index 7a152efbb9f..f7ae1e4c182 100644 --- a/libs/angular/src/auth/components/hint.component.ts +++ b/libs/angular/src/auth/components/hint.component.ts @@ -1,5 +1,6 @@ import { Directive, OnInit } from "@angular/core"; import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -27,8 +28,8 @@ export class HintComponent implements OnInit { protected toastService: ToastService, ) {} - ngOnInit(): void { - this.email = this.loginEmailService.getEmail() ?? ""; + async ngOnInit(): Promise { + this.email = (await firstValueFrom(this.loginEmailService.loginEmail$)) ?? ""; } async submit() { diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request.component.ts index 452b5ceee1e..a89952e024f 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request.component.ts @@ -93,13 +93,6 @@ export class LoginViaAuthRequestComponent ) { super(environmentService, i18nService, platformUtilsService, toastService); - // TODO: I don't know why this is necessary. - // Why would the existence of the email depend on the navigation? - const navigation = this.router.getCurrentNavigation(); - if (navigation) { - this.email = this.loginEmailService.getEmail(); - } - // Gets signalR push notification // Only fires on approval to prevent enumeration this.authRequestService.authRequestPushNotification$ @@ -118,6 +111,7 @@ export class LoginViaAuthRequestComponent } async ngOnInit() { + this.email = await firstValueFrom(this.loginEmailService.loginEmail$); this.userAuthNStatus = await this.authService.getAuthStatus(); const matchOptions: IsActiveMatchOptions = { @@ -165,7 +159,7 @@ export class LoginViaAuthRequestComponent } else { // Standard auth request // TODO: evaluate if we can remove the setting of this.email in the constructor - this.email = this.loginEmailService.getEmail(); + this.email = await firstValueFrom(this.loginEmailService.loginEmail$); if (!this.email) { this.toastService.showToast({ diff --git a/libs/angular/src/auth/components/login.component.ts b/libs/angular/src/auth/components/login.component.ts index 501d753a976..3b927a05716 100644 --- a/libs/angular/src/auth/components/login.component.ts +++ b/libs/angular/src/auth/components/login.component.ts @@ -304,7 +304,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit, private async loadEmailSettings() { // Try to load from memory first - const email = this.loginEmailService.getEmail(); + const email = await firstValueFrom(this.loginEmailService.loginEmail$); const rememberEmail = this.loginEmailService.getRememberEmail(); if (email) { this.formGroup.controls.email.setValue(email); @@ -321,7 +321,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit, } protected async saveEmailSettings() { - this.loginEmailService.setEmail(this.formGroup.value.email); + this.loginEmailService.setLoginEmail(this.formGroup.value.email); this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); await this.loginEmailService.saveEmailSettings(); } diff --git a/libs/auth/src/common/abstractions/login-email.service.ts b/libs/auth/src/common/abstractions/login-email.service.ts index d4fbbaff840..496d890f162 100644 --- a/libs/auth/src/common/abstractions/login-email.service.ts +++ b/libs/auth/src/common/abstractions/login-email.service.ts @@ -1,29 +1,28 @@ import { Observable } from "rxjs"; export abstract class LoginEmailServiceAbstraction { + /** + * An observable that monitors the loginEmail in memory. + * The loginEmail is the email that is being used in the current login process. + */ + loginEmail$: Observable; /** * An observable that monitors the storedEmail on disk. * This will return null if an account is being added. */ storedEmail$: Observable; /** - * Gets the current email being used in the login process from memory. - * @returns A string of the email. + * Sets the loginEmail in memory. + * The loginEmail is the email that is being used in the current login process. */ - getEmail: () => string; - /** - * Sets the current email being used in the login process in memory. - * @param email The email to be set. - */ - setEmail: (email: string) => void; + setLoginEmail: (email: string) => Promise; /** * Gets from memory whether or not the email should be stored on disk when `saveEmailSettings` is called. * @returns A boolean stating whether or not the email should be stored on disk. */ getRememberEmail: () => boolean; /** - * Sets in memory whether or not the email should be stored on disk when - * `saveEmailSettings` is called. + * Sets in memory whether or not the email should be stored on disk when `saveEmailSettings` is called. */ setRememberEmail: (value: boolean) => void; /** diff --git a/libs/auth/src/common/services/login-email/login-email.service.spec.ts b/libs/auth/src/common/services/login-email/login-email.service.spec.ts index 55e54c82f6e..8bb9b962eaf 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.spec.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.spec.ts @@ -43,7 +43,7 @@ describe("LoginEmailService", () => { describe("storedEmail$", () => { it("returns the stored email when not adding an account", async () => { - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); @@ -53,7 +53,7 @@ describe("LoginEmailService", () => { }); it("returns the stored email when not adding an account and the user has just logged in", async () => { - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); @@ -66,7 +66,7 @@ describe("LoginEmailService", () => { }); it("returns null when adding an account", async () => { - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); @@ -83,7 +83,7 @@ describe("LoginEmailService", () => { describe("saveEmailSettings", () => { it("saves the email when not adding an account", async () => { - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); @@ -95,7 +95,7 @@ describe("LoginEmailService", () => { it("clears the email when not adding an account and rememberEmail is false", async () => { storedEmailState.stateSubject.next("initialEmail@bitwarden.com"); - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(false); await sut.saveEmailSettings(); @@ -110,7 +110,7 @@ describe("LoginEmailService", () => { ["OtherUserId" as UserId]: AuthenticationStatus.Locked, }); - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); @@ -127,7 +127,7 @@ describe("LoginEmailService", () => { ["OtherUserId" as UserId]: AuthenticationStatus.Locked, }); - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(false); await sut.saveEmailSettings(); @@ -140,11 +140,11 @@ describe("LoginEmailService", () => { it("does not clear the email and rememberEmail after saving", async () => { // Browser uses these values to maintain the email between login and 2fa components so // we do not want to clear them too early. - sut.setEmail("userEmail@bitwarden.com"); + await sut.setLoginEmail("userEmail@bitwarden.com"); sut.setRememberEmail(true); await sut.saveEmailSettings(); - const result = sut.getEmail(); + const result = await firstValueFrom(sut.loginEmail$); expect(result).toBe("userEmail@bitwarden.com"); }); diff --git a/libs/auth/src/common/services/login-email/login-email.service.ts b/libs/auth/src/common/services/login-email/login-email.service.ts index 7793d3e7ff6..bb89b412c51 100644 --- a/libs/auth/src/common/services/login-email/login-email.service.ts +++ b/libs/auth/src/common/services/login-email/login-email.service.ts @@ -8,21 +8,28 @@ import { GlobalState, KeyDefinition, LOGIN_EMAIL_DISK, + LOGIN_EMAIL_MEMORY, StateProvider, } from "../../../../../common/src/platform/state"; import { LoginEmailServiceAbstraction } from "../../abstractions/login-email.service"; +export const LOGIN_EMAIL = new KeyDefinition(LOGIN_EMAIL_MEMORY, "loginEmail", { + deserializer: (value: string) => value, +}); + export const STORED_EMAIL = new KeyDefinition(LOGIN_EMAIL_DISK, "storedEmail", { deserializer: (value: string) => value, }); export class LoginEmailService implements LoginEmailServiceAbstraction { - private email: string | null; private rememberEmail: boolean; // True if an account is currently being added through account switching private readonly addingAccount$: Observable; + private readonly loginEmailState: GlobalState; + loginEmail$: Observable; + private readonly storedEmailState: GlobalState; storedEmail$: Observable; @@ -31,6 +38,7 @@ export class LoginEmailService implements LoginEmailServiceAbstraction { private authService: AuthService, private stateProvider: StateProvider, ) { + this.loginEmailState = this.stateProvider.getGlobal(LOGIN_EMAIL); this.storedEmailState = this.stateProvider.getGlobal(STORED_EMAIL); // In order to determine if an account is being added, we check if any account is not logged out @@ -46,6 +54,8 @@ export class LoginEmailService implements LoginEmailServiceAbstraction { }), ); + this.loginEmail$ = this.loginEmailState.state$; + this.storedEmail$ = this.storedEmailState.state$.pipe( switchMap(async (storedEmail) => { // When adding an account, we don't show the stored email @@ -57,12 +67,8 @@ export class LoginEmailService implements LoginEmailServiceAbstraction { ); } - getEmail() { - return this.email; - } - - setEmail(email: string) { - this.email = email; + async setLoginEmail(email: string) { + await this.loginEmailState.update((_) => email); } getRememberEmail() { @@ -76,25 +82,27 @@ export class LoginEmailService implements LoginEmailServiceAbstraction { // Note: only clear values on successful login or you are sure they are not needed. // Browser uses these values to maintain the email between login and 2fa components so // we do not want to clear them too early. - clearValues() { - this.email = null; + async clearValues() { + await this.setLoginEmail(null); this.rememberEmail = false; } async saveEmailSettings() { const addingAccount = await firstValueFrom(this.addingAccount$); + const email = await firstValueFrom(this.loginEmail$); + await this.storedEmailState.update((storedEmail) => { // If we're adding an account, only overwrite the stored email when rememberEmail is true if (addingAccount) { if (this.rememberEmail) { - return this.email; + return email; } return storedEmail; } // Saving with rememberEmail set to false will clear the stored email if (this.rememberEmail) { - return this.email; + return email; } return null; }); diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 32307203b29..47b7199b940 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -53,6 +53,7 @@ export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk"); export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", { web: "disk-local", }); +export const LOGIN_EMAIL_MEMORY = new StateDefinition("loginEmail", "memory"); export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory"); export const MASTER_PASSWORD_DISK = new StateDefinition("masterPassword", "disk"); export const MASTER_PASSWORD_MEMORY = new StateDefinition("masterPassword", "memory"); From 5f2eecd7beba1a17c10db3728d7051d182c40d62 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Tue, 3 Sep 2024 15:12:36 -0400 Subject: [PATCH 17/64] [PM-11350] Use shared expiration year normalization util function (#10735) * use shared expiration year normalization util function * use shared exp year normalization in web and desktop client * handle cases where input has leading zeroes * add utils tests * handle cases where input is all zeroes --- .../src/autofill/services/autofill.service.ts | 5 +- .../components/vault/add-edit.component.ts | 6 ++ .../vault/components/add-edit.component.ts | 6 ++ .../common/src/vault/models/view/card.view.ts | 12 +-- libs/common/src/vault/utils.spec.ts | 74 +++++++++++++++++++ libs/common/src/vault/utils.ts | 42 +++++++++++ libs/importer/src/importers/base-importer.ts | 4 +- .../card-details-section.component.ts | 5 +- 8 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 libs/common/src/vault/utils.spec.ts create mode 100644 libs/common/src/vault/utils.ts diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index 6f953e68b93..1002ca99225 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -29,6 +29,7 @@ import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-repromp import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FieldView } from "@bitwarden/common/vault/models/view/field.view"; import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { BrowserApi } from "../../platform/browser/browser-api"; import { ScriptInjectorService } from "../../platform/services/abstractions/script-injector.service"; @@ -1095,7 +1096,7 @@ export default class AutofillService implements AutofillServiceInterface { fillFields.expYear.maxLength === 4 ) { if (expYear.length === 2) { - expYear = "20" + expYear; + expYear = normalizeExpiryYearFormat(expYear); } } else if ( this.fieldAttrsContain(fillFields.expYear, "yy") || @@ -1121,7 +1122,7 @@ export default class AutofillService implements AutofillServiceInterface { let partYear: string = null; if (fullYear.length === 2) { partYear = fullYear; - fullYear = "20" + fullYear; + fullYear = normalizeExpiryYearFormat(fullYear); } else if (fullYear.length === 4) { partYear = fullYear.substr(2, 2); } diff --git a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts index 1a944d5599c..02654f37efe 100644 --- a/apps/browser/src/vault/popup/components/vault/add-edit.component.ts +++ b/apps/browser/src/vault/popup/components/vault/add-edit.component.ts @@ -23,6 +23,7 @@ import { CollectionService } from "@bitwarden/common/vault/abstractions/collecti import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -182,6 +183,11 @@ export class AddEditComponent extends BaseAddEditComponent implements OnInit { const { isFido2Session, sessionId, userVerification } = fido2SessionData; const inFido2PopoutWindow = BrowserPopupUtils.inPopout(window) && isFido2Session; + // normalize card expiry year on save + if (this.cipher.type === this.cipherType.Card) { + this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); + } + // TODO: Revert to use fido2 user verification service once user verification for passkeys is approved for production. // PM-4577 - https://github.com/bitwarden/clients/pull/8746 if ( diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index 909a905e9b0..96589fd2b07 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -37,6 +37,7 @@ 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 { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { DialogService } from "@bitwarden/components"; import { PasswordRepromptService } from "@bitwarden/vault"; @@ -330,6 +331,11 @@ export class AddEditComponent implements OnInit, OnDestroy { return this.restore(); } + // normalize card expiry year on save + if (this.cipher.type === this.cipherType.Card) { + this.cipher.card.expYear = normalizeExpiryYearFormat(this.cipher.card.expYear); + } + if (this.cipher.name == null || this.cipher.name === "") { this.platformUtilsService.showToast( "error", diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index d83b2c6f0a8..f3bf4e1fab2 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -2,6 +2,7 @@ import { Jsonify } from "type-fest"; import { CardLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; +import { normalizeExpiryYearFormat } from "../../utils"; import { ItemView } from "./item.view"; @@ -65,17 +66,16 @@ export class CardView extends ItemView { } get expiration(): string { - if (!this.expMonth && !this.expYear) { + const normalizedYear = normalizeExpiryYearFormat(this.expYear); + + if (!this.expMonth && !normalizedYear) { return null; } let exp = this.expMonth != null ? ("0" + this.expMonth).slice(-2) : "__"; - exp += " / " + (this.expYear != null ? this.formatYear(this.expYear) : "____"); - return exp; - } + exp += " / " + (normalizedYear || "____"); - private formatYear(year: string): string { - return year.length === 2 ? "20" + year : year; + return exp; } static fromJSON(obj: Partial>): CardView { diff --git a/libs/common/src/vault/utils.spec.ts b/libs/common/src/vault/utils.spec.ts new file mode 100644 index 00000000000..1cb185cffd3 --- /dev/null +++ b/libs/common/src/vault/utils.spec.ts @@ -0,0 +1,74 @@ +import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; + +function getExpiryYearValueFormats(currentCentury: string) { + return [ + [-12, `${currentCentury}12`], + [0, `${currentCentury}00`], + [2043, "2043"], // valid year with a length of four should be taken directly + [24, `${currentCentury}24`], + [3054, "3054"], // valid year with a length of four should be taken directly + [31423524543, `${currentCentury}43`], + [4, `${currentCentury}04`], + [null, null], + [undefined, null], + ["-12", `${currentCentury}12`], + ["", null], + ["0", `${currentCentury}00`], + ["00", `${currentCentury}00`], + ["000", `${currentCentury}00`], + ["0000", `${currentCentury}00`], + ["00000", `${currentCentury}00`], + ["0234234", `${currentCentury}34`], + ["04", `${currentCentury}04`], + ["2043", "2043"], // valid year with a length of four should be taken directly + ["24", `${currentCentury}24`], + ["3054", "3054"], // valid year with a length of four should be taken directly + ["31423524543", `${currentCentury}43`], + ["4", `${currentCentury}04`], + ["aaaa", null], + ["adgshsfhjsdrtyhsrth", null], + ["agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", `${currentCentury}45`], + ]; +} + +describe("normalizeExpiryYearFormat", () => { + const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); + + const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); + + expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { + it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { + const formattedValue = normalizeExpiryYearFormat(inputValue); + + expect(formattedValue).toEqual(expectedValue); + }); + }); + + describe("in the year 3107", () => { + const theDistantFuture = new Date(Date.UTC(3107, 1, 1)); + jest.spyOn(Date, "now").mockReturnValue(theDistantFuture.valueOf()); + + beforeAll(() => { + jest.useFakeTimers({ advanceTimers: true }); + jest.setSystemTime(theDistantFuture); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + + const currentCentury = `${new Date(Date.now()).getFullYear()}`.slice(0, 2); + expect(currentCentury).toBe("31"); + + const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury); + + expiryYearValueFormats.forEach(([inputValue, expectedValue]) => { + it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => { + const formattedValue = normalizeExpiryYearFormat(inputValue); + + expect(formattedValue).toEqual(expectedValue); + }); + }); + jest.clearAllTimers(); + }); +}); diff --git a/libs/common/src/vault/utils.ts b/libs/common/src/vault/utils.ts new file mode 100644 index 00000000000..7fed4abc12e --- /dev/null +++ b/libs/common/src/vault/utils.ts @@ -0,0 +1,42 @@ +type NonZeroIntegers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; +type Year = `${NonZeroIntegers}${NonZeroIntegers}${0 | NonZeroIntegers}${0 | NonZeroIntegers}`; + +/** + * Takes a string or number value and returns a string value formatted as a valid 4-digit year + * + * @export + * @param {(string | number)} yearInput + * @return {*} {(Year | null)} + */ +export function normalizeExpiryYearFormat(yearInput: string | number): Year | null { + // The input[type="number"] is returning a number, convert it to a string + // An empty field returns null, avoid casting `"null"` to a string + const yearInputIsEmpty = yearInput == null || yearInput === ""; + let expirationYear = yearInputIsEmpty ? null : `${yearInput}`; + + // Exit early if year is already formatted correctly or empty + if (yearInputIsEmpty || /^[1-9]{1}\d{3}$/.test(expirationYear)) { + return expirationYear as Year; + } + + expirationYear = expirationYear + // For safety, because even input[type="number"] will allow decimals + .replace(/[^\d]/g, "") + // remove any leading zero padding (leave the last leading zero if it ends the string) + .replace(/^[0]+(?=.)/, ""); + + if (expirationYear === "") { + expirationYear = null; + } + + // given the context of payment card expiry, a year character length of 3, or over 4 + // is more likely to be a mistake than an intentional value for the far past or far future. + if (expirationYear && expirationYear.length !== 4) { + const paddedYear = ("00" + expirationYear).slice(-2); + const currentCentury = `${new Date().getFullYear()}`.slice(0, 2); + + expirationYear = currentCentury + paddedYear; + } + + return expirationYear as Year | null; +} diff --git a/libs/importer/src/importers/base-importer.ts b/libs/importer/src/importers/base-importer.ts index f50044c6336..215210eda14 100644 --- a/libs/importer/src/importers/base-importer.ts +++ b/libs/importer/src/importers/base-importer.ts @@ -11,6 +11,7 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.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 { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { ImportResult } from "../models/import-result"; @@ -263,7 +264,8 @@ export abstract class BaseImporter { cipher.card.expMonth = expiryMatch.groups.month; const year: string = expiryMatch.groups.year; - cipher.card.expYear = year.length === 2 ? "20" + year : year; + cipher.card.expYear = normalizeExpiryYearFormat(year); + return true; } diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index a80954a0445..df45bcbcac0 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -7,6 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CardView } from "@bitwarden/common/vault/models/view/card.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; +import { normalizeExpiryYearFormat } from "@bitwarden/common/vault/utils"; import { CardComponent, FormFieldModule, @@ -101,9 +102,7 @@ export class CardDetailsSectionComponent implements OnInit { .pipe(takeUntilDestroyed()) .subscribe(({ cardholderName, number, brand, expMonth, expYear, code }) => { this.cipherFormContainer.patchCipher((cipher) => { - // The input[type="number"] is returning a number, convert it to a string - // An empty field returns null, avoid casting `"null"` to a string - const expirationYear = expYear !== null ? `${expYear}` : null; + const expirationYear = normalizeExpiryYearFormat(expYear); Object.assign(cipher.card, { cardholderName, From 77e03aee47071f7a4d41361c2be91012a639c7ea Mon Sep 17 00:00:00 2001 From: Bitwarden DevOps <106330231+bitwarden-devops-bot@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:09:02 -0400 Subject: [PATCH 18/64] Bumped client version(s) (#10873) --- apps/web/package.json | 2 +- package-lock.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 8d4b130f72b..391cd4b5cc6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/web-vault", - "version": "2024.8.1", + "version": "2024.8.2", "scripts": { "build:oss": "webpack", "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", diff --git a/package-lock.json b/package-lock.json index a9e95fec1f9..54e3a5cc6a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -246,7 +246,7 @@ }, "apps/web": { "name": "@bitwarden/web-vault", - "version": "2024.8.1" + "version": "2024.8.2" }, "libs/admin-console": { "name": "@bitwarden/admin-console", From 46835f0a58edf4ad5945e8dc5290ae15bb0f8069 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:44:00 -0400 Subject: [PATCH 19/64] [deps] DevOps: Update gh minor (#10847) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-browser.yml | 24 +++++----- .github/workflows/build-cli.yml | 16 +++---- .github/workflows/build-desktop.yml | 52 +++++++++++----------- .github/workflows/build-web.yml | 4 +- .github/workflows/release-desktop-beta.yml | 48 ++++++++++---------- .github/workflows/scan.yml | 2 +- 6 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.github/workflows/build-browser.yml b/.github/workflows/build-browser.yml index ae4f2f37ba8..610769859fe 100644 --- a/.github/workflows/build-browser.yml +++ b/.github/workflows/build-browser.yml @@ -173,63 +173,63 @@ jobs: working-directory: browser-source/apps/browser - name: Upload Opera artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: dist-opera-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-opera.zip if-no-files-found: error - name: Upload Opera MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: DO-NOT-USE-FOR-PROD-dist-opera-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-opera-mv3.zip if-no-files-found: error - name: Upload Chrome MV3 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: dist-chrome-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-chrome-mv3.zip if-no-files-found: error - name: Upload Chrome MV3 Beta artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: DO-NOT-USE-FOR-PROD-dist-chrome-MV3-beta-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-chrome-mv3-beta.zip if-no-files-found: error - name: Upload Firefox artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: dist-firefox-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-firefox.zip if-no-files-found: error - name: Upload Firefox MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: DO-NOT-USE-FOR-PROD-dist-firefox-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-firefox-mv3.zip if-no-files-found: error - name: Upload Edge artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: dist-edge-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-edge.zip if-no-files-found: error - name: Upload Edge MV3 artifact (DO NOT USE FOR PROD) - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: DO-NOT-USE-FOR-PROD-dist-edge-MV3-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/dist/dist-edge-mv3.zip if-no-files-found: error - name: Upload browser source - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: browser-source-${{ env._BUILD_NUMBER }}.zip path: browser-source.zip @@ -237,7 +237,7 @@ jobs: - name: Upload coverage artifact if: false - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: coverage-${{ env._BUILD_NUMBER }}.zip path: browser-source/apps/browser/coverage/coverage-${{ env._BUILD_NUMBER }}.zip @@ -352,7 +352,7 @@ jobs: ls -la - name: Upload Safari artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: dist-safari-${{ env._BUILD_NUMBER }}.zip path: apps/browser/dist/dist-safari.zip @@ -382,7 +382,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0 + uses: crowdin/github-action@30849777a3cba6ee9a09e24e195272b8287a0a5b # v1.20.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index fd864cf99a5..76d86b45500 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -130,14 +130,14 @@ jobs: matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt - name: Upload unix zip asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload unix checksum asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-${{ env.LOWER_RUNNER_OS }}-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -269,14 +269,14 @@ jobs: -t sha256 | Out-File -Encoding ASCII ./dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${env:_PACKAGE_VERSION}.txt - name: Upload windows zip asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-${{ env._PACKAGE_VERSION }}.zip if-no-files-found: error - name: Upload windows checksum asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/bw${{ matrix.license_type.artifact_prefix }}-windows-sha256-${{ env._PACKAGE_VERSION }}.txt @@ -284,7 +284,7 @@ jobs: - name: Upload Chocolatey asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg path: apps/cli/dist/chocolatey/bitwarden-cli.${{ env._PACKAGE_VERSION }}.nupkg @@ -295,7 +295,7 @@ jobs: - name: Upload NPM Build Directory asset if: matrix.license_type.build_prefix == 'bit' - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip path: apps/cli/bitwarden-cli-${{ env._PACKAGE_VERSION }}-npm-build.zip @@ -364,14 +364,14 @@ jobs: run: sudo snap remove bw - name: Upload snap asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/cli/dist/snap/bw_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload snap checksum asset - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt path: apps/cli/dist/snap/bw-snap-sha256-${{ env._PACKAGE_VERSION }}.txt diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml index a4dcf698faa..8ac65d257c6 100644 --- a/.github/workflows/build-desktop.yml +++ b/.github/workflows/build-desktop.yml @@ -193,42 +193,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release_channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-linux.yml @@ -351,91 +351,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release_channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release_channel }}.yml @@ -792,28 +792,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release_channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release_channel }}-mac.yml @@ -1009,7 +1009,7 @@ jobs: run: npm run pack:mac:mas - name: Upload .pkg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg @@ -1215,7 +1215,7 @@ jobs: zip -r Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip Bitwarden.app - name: Upload masdev artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip path: apps/desktop/dist/mas-dev-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-masdev-universal.zip @@ -1248,7 +1248,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0 + uses: crowdin/github-action@30849777a3cba6ee9a09e24e195272b8287a0a5b # v1.20.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index fbab45ddb72..d875078757c 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -130,7 +130,7 @@ jobs: run: zip -r web-${{ env._VERSION }}-${{ matrix.name }}.zip build - name: Upload ${{ matrix.name }} artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: web-${{ env._VERSION }}-${{ matrix.name }}.zip path: apps/web/web-${{ env._VERSION }}-${{ matrix.name }}.zip @@ -270,7 +270,7 @@ jobs: secrets: "crowdin-api-token" - name: Upload Sources - uses: crowdin/github-action@c953b17499daa6be3e5afbf7a63616fb02d8b18d # v1.19.0 + uses: crowdin/github-action@30849777a3cba6ee9a09e24e195272b8287a0a5b # v1.20.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} diff --git a/.github/workflows/release-desktop-beta.yml b/.github/workflows/release-desktop-beta.yml index 3f8bc45d51d..5a6a3d52361 100644 --- a/.github/workflows/release-desktop-beta.yml +++ b/.github/workflows/release-desktop-beta.yml @@ -159,42 +159,42 @@ jobs: run: npm run dist:lin - name: Upload .deb artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-amd64.deb if-no-files-found: error - name: Upload .rpm artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.rpm if-no-files-found: error - name: Upload .freebsd artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.freebsd if-no-files-found: error - name: Upload .snap artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap path: apps/desktop/dist/bitwarden_${{ env._PACKAGE_VERSION }}_amd64.snap if-no-files-found: error - name: Upload .AppImage artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x86_64.AppImage if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release-channel }}-linux.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-linux.yml @@ -300,91 +300,91 @@ jobs: -NewName bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z - name: Upload portable exe artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/Bitwarden-Portable-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload installer exe artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe path: apps/desktop/dist/nsis-web/Bitwarden-Installer-${{ env._PACKAGE_VERSION }}.exe if-no-files-found: error - name: Upload appx ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32.appx if-no-files-found: error - name: Upload store appx ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-ia32-store.appx if-no-files-found: error - name: Upload NSIS ia32 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-ia32.nsis.7z if-no-files-found: error - name: Upload appx x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64.appx if-no-files-found: error - name: Upload store appx x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-x64-store.appx if-no-files-found: error - name: Upload NSIS x64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-x64.nsis.7z if-no-files-found: error - name: Upload appx ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64.appx if-no-files-found: error - name: Upload store appx ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-arm64-store.appx if-no-files-found: error - name: Upload NSIS ARM64 artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z path: apps/desktop/dist/nsis-web/bitwarden-${{ env._PACKAGE_VERSION }}-arm64.nsis.7z if-no-files-found: error - name: Upload nupkg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: bitwarden.${{ env._PACKAGE_VERSION }}.nupkg path: apps/desktop/dist/chocolatey/bitwarden.${{ env._PACKAGE_VERSION }}.nupkg if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release-channel }}.yml path: apps/desktop/dist/nsis-web/${{ needs.setup.outputs.release-channel }}.yml @@ -708,28 +708,28 @@ jobs: run: npm run pack:mac - name: Upload .zip artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal-mac.zip if-no-files-found: error - name: Upload .dmg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg if-no-files-found: error - name: Upload .dmg blockmap artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap path: apps/desktop/dist/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.dmg.blockmap if-no-files-found: error - name: Upload auto-update artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: ${{ needs.setup.outputs.release-channel }}-mac.yml path: apps/desktop/dist/${{ needs.setup.outputs.release-channel }}-mac.yml @@ -916,7 +916,7 @@ jobs: APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} - name: Upload .pkg artifact - uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 + uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 with: name: Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg path: apps/desktop/dist/mas-universal/Bitwarden-${{ env._PACKAGE_VERSION }}-universal.pkg diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index d90e009bf36..dc62d3ed63a 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -47,7 +47,7 @@ jobs: --output-path . ${{ env.INCREMENTAL }} - name: Upload Checkmarx results to GitHub - uses: github/codeql-action/upload-sarif@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 with: sarif_file: cx_result.sarif From b90563aa504471cca2df16a5bb83cc78e54c0493 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 09:52:22 -0400 Subject: [PATCH 20/64] [deps] DevOps: Update sonarsource/sonarcloud-github-action action to v3 (#10851) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index dc62d3ed63a..076bfb46e80 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -67,7 +67,7 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Scan with SonarCloud - uses: sonarsource/sonarcloud-github-action@e44258b109568baa0df60ed515909fc6c72cba92 # v2.3.0 + uses: sonarsource/sonarcloud-github-action@eb211723266fe8e83102bac7361f0a05c3ac1d1b # v3.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 86fab07a376f3a5b0496f85d49cd21d982d88671 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 4 Sep 2024 10:22:06 -0400 Subject: [PATCH 21/64] Auth/PM-5099 Ensure consistent casing of email used for fingerprint generation in Auth Requests (#8571) * Created method for handilng email-address-based fingerprint. * Added test for new method. * Added returns to annotation --- .../src/auth/login/login-approval.component.ts | 7 ++++--- .../components/login-via-auth-request.component.ts | 14 ++++++++------ .../auth-request.service.abstraction.ts | 8 ++++++++ .../auth-request/auth-request.service.spec.ts | 12 ++++++++++++ .../services/auth-request/auth-request.service.ts | 4 ++++ 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/auth/login/login-approval.component.ts b/apps/desktop/src/auth/login/login-approval.component.ts index 39876f2945f..4bffc338b3e 100644 --- a/apps/desktop/src/auth/login/login-approval.component.ts +++ b/apps/desktop/src/auth/login/login-approval.component.ts @@ -79,9 +79,10 @@ export class LoginApprovalComponent implements OnInit, OnDestroy { this.email = await await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.email)), ); - this.fingerprintPhrase = ( - await this.cryptoService.getFingerprint(this.email, publicKey) - ).join("-"); + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( + this.email, + publicKey, + ); this.updateTimeText(); this.interval = setInterval(() => { diff --git a/libs/angular/src/auth/components/login-via-auth-request.component.ts b/libs/angular/src/auth/components/login-via-auth-request.component.ts index a89952e024f..ed9ed6ef706 100644 --- a/libs/angular/src/auth/components/login-via-auth-request.component.ts +++ b/libs/angular/src/auth/components/login-via-auth-request.component.ts @@ -210,9 +210,10 @@ export class LoginViaAuthRequestComponent const derivedPublicKeyArrayBuffer = await this.cryptoFunctionService.rsaExtractPublicKey( adminAuthReqStorable.privateKey, ); - this.fingerprintPhrase = ( - await this.cryptoService.getFingerprint(this.email, derivedPublicKeyArrayBuffer) - ).join("-"); + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( + this.email, + derivedPublicKeyArrayBuffer, + ); // Request denied if (adminAuthReqResponse.isAnswered && !adminAuthReqResponse.requestApproved) { @@ -259,9 +260,10 @@ export class LoginViaAuthRequestComponent length: 25, }); - this.fingerprintPhrase = ( - await this.cryptoService.getFingerprint(this.email, this.authRequestKeyPair.publicKey) - ).join("-"); + this.fingerprintPhrase = await this.authRequestService.getFingerprintPhrase( + this.email, + this.authRequestKeyPair.publicKey, + ); this.authRequest = new CreateAuthRequest( this.email, diff --git a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts index aa5f52a8c9c..371c32e42d3 100644 --- a/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts +++ b/libs/auth/src/common/abstractions/auth-request.service.abstraction.ts @@ -96,4 +96,12 @@ export abstract class AuthRequestServiceAbstraction { * @remark We should only be receiving approved push notifications to prevent enumeration. */ abstract sendAuthRequestPushNotification: (notification: AuthRequestPushNotification) => void; + + /** + * Creates a dash-delimited fingerprint for use in confirming the `AuthRequest` between the requesting and approving device. + * @param email The email address of the user. + * @param publicKey The public key for the user. + * @returns The dash-delimited fingerprint phrase. + */ + abstract getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise; } diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts index 885856517b8..14f807a7708 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.spec.ts @@ -27,6 +27,7 @@ describe("AuthRequestService", () => { const apiService = mock(); let mockPrivateKey: Uint8Array; + let mockPublicKey: Uint8Array; const mockUserId = Utils.newGuid() as UserId; beforeEach(() => { @@ -44,6 +45,7 @@ describe("AuthRequestService", () => { ); mockPrivateKey = new Uint8Array(64); + mockPublicKey = new Uint8Array(64); }); describe("authRequestPushNotification$", () => { @@ -262,4 +264,14 @@ describe("AuthRequestService", () => { expect(result.masterKeyHash).toEqual(mockDecryptedMasterKeyHash); }); }); + + describe("getFingerprintPhrase", () => { + it("returns the same fingerprint regardless of email casing", () => { + const email = "test@email.com"; + const emailUpperCase = email.toUpperCase(); + const phrase = sut.getFingerprintPhrase(email, mockPublicKey); + const phraseUpperCase = sut.getFingerprintPhrase(emailUpperCase, mockPublicKey); + expect(phrase).toEqual(phraseUpperCase); + }); + }); }); diff --git a/libs/auth/src/common/services/auth-request/auth-request.service.ts b/libs/auth/src/common/services/auth-request/auth-request.service.ts index 68302cae92d..eefee511f82 100644 --- a/libs/auth/src/common/services/auth-request/auth-request.service.ts +++ b/libs/auth/src/common/services/auth-request/auth-request.service.ts @@ -198,4 +198,8 @@ export class AuthRequestService implements AuthRequestServiceAbstraction { this.authRequestPushNotificationSubject.next(notification.id); } } + + async getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise { + return (await this.cryptoService.getFingerprint(email.toLowerCase(), publicKey)).join("-"); + } } From 192fd885d5a5cf0f939e47f557a2d53d0cc52d3e Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 4 Sep 2024 17:16:57 +0200 Subject: [PATCH 22/64] Return null in derivePublicKey if privateKey is null (#10882) --- libs/common/src/platform/services/crypto.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/common/src/platform/services/crypto.service.ts b/libs/common/src/platform/services/crypto.service.ts index e6860dadf3a..8ce2b5e1a0c 100644 --- a/libs/common/src/platform/services/crypto.service.ts +++ b/libs/common/src/platform/services/crypto.service.ts @@ -945,6 +945,10 @@ export class CryptoService implements CryptoServiceAbstraction { } private async derivePublicKey(privateKey: UserPrivateKey) { + if (privateKey == null) { + return null; + } + return (await this.cryptoFunctionService.rsaExtractPublicKey(privateKey)) as UserPublicKey; } 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 23/64] [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 24/64] 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 25/64] 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 26/64] [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 27/64] [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 28/64] 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 29/64] [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 30/64] [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 31/64] [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 32/64] [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 33/64] [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 34/64] 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 35/64] 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 36/64] [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 37/64] [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 38/64] [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 39/64] [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 40/64] [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 41/64] [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 42/64] [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 49/64] 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 50/64] [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 }} + +