From 4c68f61d47a2fc8deb4300d9beec568c4a11d555 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Tue, 13 May 2025 10:58:48 -0400 Subject: [PATCH 01/54] feat(CLI-SSO-Login): [Auth/PM-21116] CLI - SSO Login - Add SSO Org Identifier option (#14605) * Add --identifier option for SSO on CLI * Add option for identifier * Moved auto-submit after the setting of client arguments * Adjusted comment * Changed to pass in as SSO option * Renamed to orgSsoIdentifier for clarity * Added more changes to orgSsoIdentifier. --- apps/cli/src/auth/commands/login.command.ts | 7 +++++- apps/cli/src/program.ts | 5 +++- libs/auth/src/angular/sso/sso.component.ts | 16 ++++++------- .../sso-redirect/sso-url.service.spec.ts | 23 +++++++++++++++++++ .../services/sso-redirect/sso-url.service.ts | 6 +++++ 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 3ad71c62e66..cd5c8ef9bcd 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -106,6 +106,8 @@ export class LoginCommand { return Response.badRequest("client_secret is required."); } } else if (options.sso != null && this.canInteract) { + // If the optional Org SSO Identifier isn't provided, the option value is `true`. + const orgSsoIdentifier = options.sso === true ? null : options.sso; const passwordOptions: any = { type: "password", length: 64, @@ -119,7 +121,7 @@ export class LoginCommand { const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); try { - const ssoParams = await this.openSsoPrompt(codeChallenge, state); + const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier); ssoCode = ssoParams.ssoCode; orgIdentifier = ssoParams.orgIdentifier; } catch { @@ -664,6 +666,7 @@ export class LoginCommand { private async openSsoPrompt( codeChallenge: string, state: string, + orgSsoIdentifier: string, ): Promise<{ ssoCode: string; orgIdentifier: string }> { const env = await firstValueFrom(this.environmentService.environment$); @@ -712,6 +715,8 @@ export class LoginCommand { this.ssoRedirectUri, state, codeChallenge, + null, + orgSsoIdentifier, ); this.platformUtilsService.launchUri(webAppSsoUrl); }); diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index dca4effcdc9..d85f1b366e6 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -118,7 +118,10 @@ export class Program extends BaseProgram { .description("Log into a user account.") .option("--method ", "Two-step login method.") .option("--code ", "Two-step login code.") - .option("--sso", "Log in with Single-Sign On.") + .option( + "--sso [identifier]", + "Log in with Single-Sign On with optional organization identifier.", + ) .option("--apikey", "Log in with an Api Key.") .option("--passwordenv ", "Environment variable storing your password") .option( diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index a91a8ed20e9..968a05bf850 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -155,7 +155,14 @@ export class SsoComponent implements OnInit { return; } - // Detect if we have landed here but only have an SSO identifier in the URL. + // Detect if we are on the first portion of the SSO flow + // and have been sent here from another client with the info in query params. + // If so, we want to initialize the SSO flow with those values. + if (this.hasParametersFromOtherClientRedirect(qParams)) { + this.initializeFromRedirectFromOtherClient(qParams); + } + + // Detect if we have landed here with an SSO identifier in the URL. // This is used by integrations that want to "short-circuit" the login to send users // directly to their IdP to simulate IdP-initiated SSO, so we submit automatically. if (qParams.identifier != null) { @@ -165,13 +172,6 @@ export class SsoComponent implements OnInit { return; } - // Detect if we are on the first portion of the SSO flow - // and have been sent here from another client with the info in query params. - // If so, we want to initialize the SSO flow with those values. - if (this.hasParametersFromOtherClientRedirect(qParams)) { - this.initializeFromRedirectFromOtherClient(qParams); - } - // Try to determine the identifier using claimed domain or local state // persisted from the user's last login attempt. await this.initializeIdentifierFromEmailOrStorage(); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts index 074c3a1e0b1..632a2812cfe 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts @@ -92,4 +92,27 @@ describe("SsoUrlService", () => { ); expect(result).toBe(expectedUrl); }); + + it("should build CLI SSO URL with Org SSO Identifier correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Cli; + const redirectUri = "https://localhost:1000"; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + const orgSsoIdentifier = "test-org"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}&identifier=${encodeURIComponent(orgSsoIdentifier)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + orgSsoIdentifier, + ); + expect(result).toBe(expectedUrl); + }); }); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts index 667a27ad598..b2d6231db7c 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts @@ -11,6 +11,7 @@ export class SsoUrlService { * @param state A state value that will be peristed through the SSO flow * @param codeChallenge A challenge value that will be used to verify the SSO code after authentication * @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier + * @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO * @returns The URL for redirecting users to the web app SSO component */ buildSsoUrl( @@ -20,6 +21,7 @@ export class SsoUrlService { state: string, codeChallenge: string, email?: string, + orgSsoIdentifier?: string, ): string { let url = webAppUrl + @@ -36,6 +38,10 @@ export class SsoUrlService { url += "&email=" + encodeURIComponent(email); } + if (orgSsoIdentifier) { + url += "&identifier=" + encodeURIComponent(orgSsoIdentifier); + } + return url; } } From 9a7089594efac32006eacd1e013488447f1f4214 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 May 2025 16:59:56 +0200 Subject: [PATCH 02/54] [PM-19878] Add core-js to cli (#14696) * Add core-js to cli * Add core-js to bit-cli --- apps/cli/package.json | 1 + bitwarden_license/bit-cli/src/bw.ts | 2 ++ package-lock.json | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 82faa7d40e6..b01c96b23d1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -71,6 +71,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.40.0", "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", diff --git a/bitwarden_license/bit-cli/src/bw.ts b/bitwarden_license/bit-cli/src/bw.ts index ffbc186d9e0..2e4e945b9c8 100644 --- a/bitwarden_license/bit-cli/src/bw.ts +++ b/bitwarden_license/bit-cli/src/bw.ts @@ -1,3 +1,5 @@ +import "core-js/proposals/explicit-resource-management"; + import { program } from "commander"; import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs"; diff --git a/package-lock.json b/package-lock.json index 8466a4ba4c6..d1378d63ec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -207,6 +207,7 @@ "browser-hrtime": "1.1.8", "chalk": "4.1.2", "commander": "11.1.0", + "core-js": "3.40.0", "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", @@ -16960,7 +16961,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", "hasInstallScript": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" From 0750ff38f580e85e7ec6296e3883ac7c38676349 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Tue, 13 May 2025 11:21:07 -0400 Subject: [PATCH 03/54] Route to vault page when user doesn't have access (#14633) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../src/vault/guards/at-risk-passwords.guard.ts | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 361093b12ef..e1a3234a61f 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5321,5 +5321,8 @@ "message": "Learn more about SSH agent", "description": "Two part message", "example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent" + }, + "noPermissionsViewPage": { + "message": "You do not have permissions to view this page. Try logging in with a different account." } } diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts index 6bcdddfde81..fc302dd6c36 100644 --- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -1,6 +1,6 @@ import { inject } from "@angular/core"; -import { CanActivateFn } from "@angular/router"; -import { switchMap, tap } from "rxjs"; +import { CanActivateFn, Router } from "@angular/router"; +import { map, switchMap } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => { const taskService = inject(TaskService); const toastService = inject(ToastService); const i18nService = inject(I18nService); + const router = inject(Router); return accountService.activeAccount$.pipe( filterOutNullish(), switchMap((user) => taskService.tasksEnabled$(user.id)), - tap((tasksEnabled) => { + map((tasksEnabled) => { if (!tasksEnabled) { toastService.showToast({ variant: "error", title: "", - message: i18nService.t("accessDenied"), + message: i18nService.t("noPermissionsViewPage"), }); + + return router.createUrlTree(["/tabs/vault"]); } + return true; }), ); }; From 1f72dd7710a160058cd39316ac00d97cdbfe5aad Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Tue, 13 May 2025 11:30:36 -0400 Subject: [PATCH 04/54] PM-21286 add aria label to nudge settings badge (#14750) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../src/tools/popup/settings/settings-v2.component.html | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e1a3234a61f..dcdfe7df4d6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5258,6 +5258,9 @@ "secureDevicesBody": { "message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps." }, + "nudgeBadgeAria": { + "message": "1 notification" + }, "emptyVaultNudgeTitle": { "message": "Import existing passwords" }, diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 22e2d9a28d0..8d31ccf8371 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -23,6 +23,7 @@ *ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1 @@ -53,6 +54,7 @@ *ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1 @@ -83,6 +85,7 @@ *ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1 From 5fb46df3415aefced0b52f2db86c873962255448 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 13 May 2025 16:49:06 +0100 Subject: [PATCH 05/54] [PM 21106]Remove button not responsive for admin Console Remove Sponorship (#14743) * Resolve the remove button inactive * Resolve the lint error --- .../free-bitwarden-families.component.ts | 2 +- .../settings/sponsoring-org-row.component.ts | 2 +- .../src/services/jslib-services.module.ts | 2 +- libs/common/src/abstractions/api.service.ts | 1 - ...ion-sponsorship-api.service.abstraction.ts | 5 ++++ .../organization-sponsorship-api.service.ts | 23 ++++++++++++++++++- libs/common/src/services/api.service.ts | 12 ---------- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index eb6bfdf368b..b482007e30b 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -179,7 +179,7 @@ export class FreeBitwardenFamiliesComponent implements OnInit { return; } - await this.apiService.deleteRevokeSponsorship(this.organizationId); + await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.organizationId, true); this.toastService.showToast({ variant: "success", diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 33e6334c577..39a7531082a 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -109,7 +109,7 @@ export class SponsoringOrgRowComponent implements OnInit { return; } - await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id); + await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.sponsoringOrg.id); this.toastService.showToast({ variant: "success", title: null, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 470115ae3f0..3ffca776034 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1079,7 +1079,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationSponsorshipApiServiceAbstraction, useClass: OrganizationSponsorshipApiService, - deps: [ApiServiceAbstraction], + deps: [ApiServiceAbstraction, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: OrganizationBillingApiServiceAbstraction, diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 1e13a3064f4..e4453359015 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -475,7 +475,6 @@ export abstract class ApiService { getSponsorshipSyncStatus: ( sponsoredOrgId: string, ) => Promise; - deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; postPreValidateSponsorshipToken: ( sponsorshipToken: string, diff --git a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts index 7bd6aac17bd..5e69f57ca19 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts @@ -10,4 +10,9 @@ export abstract class OrganizationSponsorshipApiServiceAbstraction { sponsoringOrgId: string, friendlyName?: string, ): Promise; + + abstract deleteRevokeSponsorship: ( + sponsoringOrganizationId: string, + isAdminInitiated?: boolean, + ) => Promise; } diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts index 22ab3ec86ee..99440b10dec 100644 --- a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts +++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts @@ -1,12 +1,16 @@ import { ApiService } from "../../../abstractions/api.service"; import { ListResponse } from "../../../models/response/list.response"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction"; import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; export class OrganizationSponsorshipApiService implements OrganizationSponsorshipApiServiceAbstraction { - constructor(private apiService: ApiService) {} + constructor( + private apiService: ApiService, + private platformUtilsService: PlatformUtilsService, + ) {} async getOrganizationSponsorship( sponsoredOrgId: string, ): Promise> { @@ -33,4 +37,21 @@ export class OrganizationSponsorshipApiService return await this.apiService.send("POST", url, null, true, false); } + + async deleteRevokeSponsorship( + sponsoringOrganizationId: string, + isAdminInitiated: boolean = false, + ): Promise { + const basePath = "/organization/sponsorship/"; + const hostPath = this.platformUtilsService.isSelfHost() ? "self-hosted/" : ""; + const queryParam = `?isAdminInitiated=${isAdminInitiated}`; + + return await this.apiService.send( + "DELETE", + basePath + hostPath + sponsoringOrganizationId + queryParam, + null, + true, + false, + ); + } } diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index 5c4bcdedb26..639daa7c658 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -1621,18 +1621,6 @@ export class ApiService implements ApiServiceAbstraction { return new OrganizationSponsorshipSyncStatusResponse(response); } - async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { - return await this.send( - "DELETE", - "/organization/sponsorship/" + - (this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") + - sponsoringOrganizationId, - null, - true, - false, - ); - } - async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { return await this.send( "DELETE", From b3df8a6c13781a41ce5b82b83b35c90c5e32e0f2 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Tue, 13 May 2025 14:16:18 -0400 Subject: [PATCH 06/54] [PM-17091][PM-17043] Support system zoom in browser extension (#14435) --- .../popup/layout/popup-size.service.ts | 37 +++++++++++--- apps/browser/src/popup/app.component.ts | 3 ++ apps/browser/src/popup/scss/base.scss | 49 ++++++++++++------- 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts index 3ae9a633cab..69d3102d24e 100644 --- a/apps/browser/src/platform/popup/layout/popup-size.service.ts +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -50,17 +50,40 @@ export class PopupSizeService { PopupSizeService.setStyle(width); localStorage.setItem(PopupSizeService.LocalStorageKey, width); }); + } + async setHeight() { const isInChromeTab = await BrowserPopupUtils.isInTab(); + /** + * To support both browser default zoom and system default zoom, we need to take into account + * the full screen height. When system default zoom is >100%, window.innerHeight still outputs + * a height equivalent to what it would be at 100%, which can cause the extension window to + * render as too tall. So if the screen height is smaller than the max possible extension height, + * we should use that to set our extension height. Otherwise, we want to use the window.innerHeight + * to support browser zoom. + * + * This is basically a workaround for what we consider a bug with browsers reporting the wrong + * available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code + * checking the screen height. + */ + const MAX_EXT_HEIGHT = 600; + const extensionInnerHeight = window.innerHeight; + // Use a 100px offset when calculating screen height to account for browser container elements + const screenAvailHeight = window.screen.availHeight - 100; + const availHeight = + screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight; + if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) { - window.document.body.classList.add("body-full"); - } else if (window.innerHeight < 400) { - window.document.body.classList.add("body-xxs"); - } else if (window.innerHeight < 500) { - window.document.body.classList.add("body-xs"); - } else if (window.innerHeight < 600) { - window.document.body.classList.add("body-sm"); + window.document.documentElement.classList.add("body-full"); + } else if (availHeight < 300) { + window.document.documentElement.classList.add("body-3xs"); + } else if (availHeight < 400) { + window.document.documentElement.classList.add("body-xxs"); + } else if (availHeight < 500) { + window.document.documentElement.classList.add("body-xs"); + } else if (availHeight < 600) { + window.document.documentElement.classList.add("body-sm"); } } diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 49579f889b3..a480e1d6ba3 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -26,6 +26,7 @@ import { import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; +import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; @@ -71,6 +72,7 @@ export class AppComponent implements OnInit, OnDestroy { private biometricStateService: BiometricStateService, private biometricsService: BiometricsService, private deviceTrustToastService: DeviceTrustToastService, + private popupSizeService: PopupSizeService, ) { this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe(); } @@ -79,6 +81,7 @@ export class AppComponent implements OnInit, OnDestroy { initPopupClosedListener(); this.compactModeService.init(); + await this.popupSizeService.setHeight(); // Component states must not persist between closing and reopening the popup, otherwise they become dead objects // Clear them aggressively to make sure this doesn't occur diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 59893b5050d..80ada61f868 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -8,6 +8,34 @@ html { overflow: hidden; + min-height: 600px; + height: 100%; + + &.body-sm { + min-height: 500px; + } + + &.body-xs { + min-height: 400px; + } + + &.body-xxs { + min-height: 300px; + } + + &.body-3xs { + min-height: 240px; + } + + &.body-full { + min-height: unset; + width: 100%; + height: 100%; + + & body { + width: 100%; + } + } } html, @@ -20,9 +48,9 @@ body { body { width: 380px; - height: 600px; + height: 100%; position: relative; - min-height: 100vh; + min-height: inherit; overflow: hidden; color: $text-color; background-color: $background-color; @@ -31,23 +59,6 @@ body { color: themed("textColor"); background-color: themed("backgroundColor"); } - - &.body-sm { - height: 500px; - } - - &.body-xs { - height: 400px; - } - - &.body-xxs { - height: 300px; - } - - &.body-full { - width: 100%; - height: 100%; - } } h1, From 896c9bd583f772242accdd9a57dc12f1520b8d15 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 13 May 2025 13:59:03 -0500 Subject: [PATCH 07/54] [PM-20997] Part 1 of PM-20117 - Styling Changes (#14527) --- .../organization-layout.component.html | 1 + apps/web/src/locales/en/messages.json | 4 ++-- .../risk-insights/models/password-health.ts | 2 ++ .../services/risk-insights-report.service.ts | 2 ++ .../access-intelligence-routing.module.ts | 1 + .../all-applications.component.html | 5 ++--- .../all-applications.component.ts | 4 +++- .../app-table-row-scrollable.component.html | 4 ++++ .../risk-insights.component.html | 19 +++++++++++-------- 9 files changed, 28 insertions(+), 14 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index 4e7c2403893..24be9a16090 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -6,6 +6,7 @@ icon="bwi-filter" *ngIf="organization.useRiskInsights" [text]="'accessIntelligence' | i18n" + route="access-intelligence" > org.useRiskInsights)], diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html index c9d0ba11eee..6f8e738fdc3 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.html @@ -56,16 +56,15 @@ [formControl]="searchControl" > diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts index f53272845f2..c586882a1e0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/all-applications.component.ts @@ -26,6 +26,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { + IconButtonModule, Icons, NoItemsModule, SearchModule, @@ -53,6 +54,7 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" NoItemsModule, SharedModule, AppTableRowScrollableComponent, + IconButtonModule, ], }) export class AllApplicationsComponent implements OnInit { @@ -160,7 +162,7 @@ export class AllApplicationsComponent implements OnInit { this.toastService.showToast({ variant: "success", title: "", - message: this.i18nService.t("appsMarkedAsCritical"), + message: this.i18nService.t("applicationsMarkedAsCriticalSuccess"), }); } finally { this.selectedUrls.clear(); diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index b575d40076c..ff38ab5687e 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -2,6 +2,7 @@ + {{ "application" | i18n }} {{ "atRiskPasswords" | i18n }} {{ "totalPasswords" | i18n }} @@ -30,6 +31,9 @@ > + + + -
- {{ "accessIntelligence" | i18n }} -

{{ "riskInsights" | i18n }}

-
+
{{ "reviewAtRiskPasswords" | i18n }}
-
{{ "email" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
+
{{ "email" | i18n }}
+
+ {{ "atRiskPasswords" | i18n }} +
@@ -117,8 +116,12 @@ "atRiskApplicationsDescription" | i18n }}
-
{{ "application" | i18n }}
-
{{ "atRiskPasswords" | i18n }}
+
+ {{ "application" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
From 1cc06fd3b9ffbb1bfbfff74f286d849212aeccd7 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 13 May 2025 14:29:23 -0500 Subject: [PATCH 08/54] [PM-20999] Styling corrections to Access Intelligence - Part 2 (#14552) --- apps/web/src/locales/en/messages.json | 15 +++++ .../app-table-row-scrollable.component.html | 2 +- .../risk-insights.component.html | 63 +++++++++++-------- 3 files changed, 54 insertions(+), 26 deletions(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b5725d4eddf..cf2174cc1db 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -140,9 +140,15 @@ "atRiskMembersDescription": { "message": "These members are logging into applications with weak, exposed, or reused passwords." }, + "atRiskMembersDescriptionNone": { + "message": "These are no members logging into applications with weak, exposed, or reused passwords." + }, "atRiskApplicationsDescription": { "message": "These applications have weak, exposed, or reused passwords." }, + "atRiskApplicationsDescriptionNone": { + "message": "These are no applications with weak, exposed, or reused passwords." + }, "atRiskMembersDescriptionWithApp": { "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", "placeholders": { @@ -152,6 +158,15 @@ } } }, + "atRiskMembersDescriptionWithAppNone": { + "message": "There are no at risk members for $APPNAME$.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, "totalMembers": { "message": "Total members" }, diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html index ff38ab5687e..10dbb179519 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/app-table-row-scrollable.component.html @@ -89,7 +89,7 @@ diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html index fea0b32e959..f759e483bd0 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.html @@ -68,19 +68,24 @@ {{ - "atRiskMembersDescription" | i18n + (dataService.atRiskMemberDetails.length > 0 + ? "atRiskMembersDescription" + : "atRiskMembersDescriptionNone" + ) | i18n }} -
-
{{ "email" | i18n }}
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ member.email }}
-
{{ member.atRiskPasswordCount }}
+ +
+
{{ "email" | i18n }}
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ member.email }}
+
{{ member.atRiskPasswordCount }}
+
+
@@ -94,7 +99,10 @@
{{ - "atRiskMembersDescriptionWithApp" | i18n: dataService.appAtRiskMembers.applicationName + (dataService.appAtRiskMembers.members.length > 0 + ? "atRiskMembersDescriptionWithApp" + : "atRiskMembersDescriptionWithAppNone" + ) | i18n: dataService.appAtRiskMembers.applicationName }}
@@ -113,21 +121,26 @@ {{ - "atRiskApplicationsDescription" | i18n + (dataService.atRiskAppDetails.length > 0 + ? "atRiskApplicationsDescription" + : "atRiskApplicationsDescriptionNone" + ) | i18n }} -
-
- {{ "application" | i18n }} -
-
- {{ "atRiskPasswords" | i18n }} -
-
- -
-
{{ app.applicationName }}
-
{{ app.atRiskPasswordCount }}
+ +
+
+ {{ "application" | i18n }} +
+
+ {{ "atRiskPasswords" | i18n }} +
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
From bacd1fb999744fdc92b99ae5563333cb2336daef Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 13 May 2025 21:36:26 +0200 Subject: [PATCH 09/54] [PM-17407] [CL-665] Remove jQuery and Popper.js (#14621) Now that the last usages of ModalService is removed from the web portions we can finally remove jQuery and Popper.js. This PR also removes bootstrap js imports since it would drag in jQuery as a peer dependency. Note: Both dependencies still exists in the lockfile as they are peer dependencies of boostrap. --- .github/renovate.json5 | 3 -- apps/web/src/app/app.component.ts | 24 +---------- apps/web/src/app/core/core.module.ts | 7 ---- apps/web/src/app/core/modal.service.ts | 56 -------------------------- apps/web/src/main.ts | 4 -- bitwarden_license/bit-web/src/main.ts | 4 -- package-lock.json | 24 ++--------- package.json | 3 -- 8 files changed, 4 insertions(+), 121 deletions(-) delete mode 100644 apps/web/src/app/core/modal.service.ts diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e5cd47077fb..d0066ddd7ba 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -222,7 +222,6 @@ "@types/chrome", "@types/firefox-webext-browser", "@types/glob", - "@types/jquery", "@types/lowdb", "@types/node", "@types/node-forge", @@ -330,9 +329,7 @@ "autoprefixer", "bootstrap", "chromatic", - "jquery", "ngx-toastr", - "popper.js", "react", "react-dom", "remark-gfm", diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index 55e2595e0f7..b94ce004313 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -3,25 +3,20 @@ import { DOCUMENT } from "@angular/common"; import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NavigationEnd, Router } from "@angular/router"; -import * as jq from "jquery"; +import { Router } from "@angular/router"; import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs"; import { CollectionService } from "@bitwarden/admin-console/common"; import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service"; import { SearchService } from "@bitwarden/common/abstractions/search.service"; import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; -import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { VaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -29,11 +24,9 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { NotificationsService } from "@bitwarden/common/platform/notifications"; import { StateEventRunnerService } from "@bitwarden/common/platform/state"; -import { SyncService } from "@bitwarden/common/platform/sync"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { DialogService, ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; import { KeyService, BiometricStateService } from "@bitwarden/key-management"; import { PolicyListService } from "./admin-console/core/policy-list.service"; @@ -69,8 +62,6 @@ export class AppComponent implements OnDestroy, OnInit { @Inject(DOCUMENT) private document: Document, private broadcasterService: BroadcasterService, private folderService: InternalFolderService, - private syncService: SyncService, - private passwordGenerationService: PasswordGenerationServiceAbstraction, private cipherService: CipherService, private authService: AuthService, private router: Router, @@ -85,17 +76,13 @@ export class AppComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private stateService: StateService, private eventUploadService: EventUploadService, - private policyService: InternalPolicyService, protected policyListService: PolicyListService, - private keyConnectorService: KeyConnectorService, protected configService: ConfigService, private dialogService: DialogService, private biometricStateService: BiometricStateService, private stateEventRunnerService: StateEventRunnerService, private organizationService: InternalOrganizationServiceAbstraction, private accountService: AccountService, - private apiService: ApiService, - private appIdService: AppIdService, private processReloadService: ProcessReloadServiceAbstraction, private deviceTrustToastService: DeviceTrustToastService, ) { @@ -247,15 +234,6 @@ export class AppComponent implements OnDestroy, OnInit { }); }); - this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { - if (event instanceof NavigationEnd) { - const modals = Array.from(document.querySelectorAll(".modal")); - for (const modal of modals) { - (jq(modal) as any).modal("hide"); - } - } - }); - this.policyListService.addPolicies([ new TwoFactorAuthenticationPolicy(), new MasterPasswordPolicy(), diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 06a91895eb8..48e884f252c 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -26,7 +26,6 @@ import { WINDOW, } from "@bitwarden/angular/services/injection-tokens"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; -import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { RegistrationFinishService as RegistrationFinishServiceAbstraction, LoginComponentService, @@ -136,7 +135,6 @@ import { WebStorageServiceProvider } from "../platform/web-storage-service.provi import { EventService } from "./event.service"; import { InitService } from "./init.service"; import { ENV_URLS } from "./injection-tokens"; -import { ModalService } from "./modal.service"; import { RouterService } from "./router.service"; import { WebPlatformUtilsService } from "./web-platform-utils.service"; @@ -195,11 +193,6 @@ const safeProviders: SafeProvider[] = [ useClass: WebPlatformUtilsService, useAngularDecorators: true, }), - safeProvider({ - provide: ModalServiceAbstraction, - useClass: ModalService, - useAngularDecorators: true, - }), safeProvider({ provide: FileDownloadService, useClass: WebFileDownloadService, diff --git a/apps/web/src/app/core/modal.service.ts b/apps/web/src/app/core/modal.service.ts deleted file mode 100644 index 14ea6044f36..00000000000 --- a/apps/web/src/app/core/modal.service.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable, Injector } from "@angular/core"; -import * as jq from "jquery"; -import { first } from "rxjs/operators"; - -import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref"; -import { ModalService as BaseModalService } from "@bitwarden/angular/services/modal.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; - -@Injectable() -export class ModalService extends BaseModalService { - el: any = null; - modalOpen = false; - - constructor( - injector: Injector, - private messagingService: MessagingService, - ) { - super(injector); - } - - protected setupHandlers(modalRef: ModalRef) { - modalRef.onCreated.pipe(first()).subscribe(() => { - const modals = Array.from(document.querySelectorAll(".modal")); - if (modals.length > 0) { - this.el = jq(modals[0]); - this.el.modal("show"); - - this.el.on("show.bs.modal", () => { - modalRef.show(); - this.messagingService.send("modalShow"); - }); - this.el.on("shown.bs.modal", () => { - modalRef.shown(); - this.messagingService.send("modalShown"); - if (!Utils.isMobileBrowser) { - this.el.find("*[appAutoFocus]").focus(); - } - }); - this.el.on("hide.bs.modal", () => { - this.messagingService.send("modalClose"); - }); - this.el.on("hidden.bs.modal", () => { - modalRef.closed(); - this.messagingService.send("modalClosed"); - }); - } - }); - - modalRef.onClose.pipe(first()).subscribe(() => { - if (this.el != null) { - this.el.modal("hide"); - } - }); - } -} diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/bitwarden_license/bit-web/src/main.ts b/bitwarden_license/bit-web/src/main.ts index b202a170d26..572d3968f3d 100644 --- a/bitwarden_license/bit-web/src/main.ts +++ b/bitwarden_license/bit-web/src/main.ts @@ -1,10 +1,6 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; -import "bootstrap"; -import "jquery"; -import "popper.js"; - import { AppModule } from "./app/app.module"; if (process.env.NODE_ENV === "production") { diff --git a/package-lock.json b/package-lock.json index d1378d63ec3..ddcdd1c9df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,7 +45,6 @@ "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jquery": "3.7.1", "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", @@ -62,7 +61,6 @@ "open": "8.4.2", "papaparse": "5.5.2", "patch-package": "8.0.0", - "popper.js": "1.16.1", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", "qrious": "4.0.2", @@ -102,7 +100,6 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -11723,16 +11720,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/jquery": { - "version": "3.5.32", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.32.tgz", - "integrity": "sha512-b9Xbf4CkMqS02YH8zACqN1xzdxc3cO735Qe5AbSUFmyOiaWAbcpqh9Wna+Uk0vgACvoQHpWDg2rGdHkYPLmCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/sizzle": "*" - } - }, "node_modules/@types/jsdom": { "version": "21.1.7", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", @@ -12099,13 +12086,6 @@ "@types/send": "*" } }, - "node_modules/@types/sizzle": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", @@ -25233,7 +25213,8 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -31666,6 +31647,7 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" diff --git a/package.json b/package.json index 2993707313f..93361275494 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "@types/firefox-webext-browser": "120.0.4", "@types/inquirer": "8.2.10", "@types/jest": "29.5.12", - "@types/jquery": "3.5.32", "@types/jsdom": "21.1.7", "@types/koa": "2.15.0", "@types/koa__multer": "2.0.7", @@ -181,7 +180,6 @@ "form-data": "4.0.1", "https-proxy-agent": "7.0.6", "inquirer": "8.2.6", - "jquery": "3.7.1", "jsdom": "26.1.0", "jszip": "3.10.1", "koa": "2.16.1", @@ -198,7 +196,6 @@ "open": "8.4.2", "papaparse": "5.5.2", "patch-package": "8.0.0", - "popper.js": "1.16.1", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", "qrious": "4.0.2", From d50db0d0dded04deb261901a9672b193779d5198 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 13 May 2025 16:08:43 -0400 Subject: [PATCH 10/54] [PM-21441] Defect - Notification bar sometimes gets cut off in fill dev (#14764) * PM-21441 * revert default value --- .../overlay-notifications-content.service.ts | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index c21aaa37dd4..a2e1d6e49a0 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -17,7 +17,7 @@ export class OverlayNotificationsContentService private notificationBarIframeElement: HTMLIFrameElement | null = null; private currentNotificationBarType: string | null = null; private removeTabFromNotificationQueueTypes = new Set(["add", "change"]); - private notificationRefreshFlag: boolean; + private notificationRefreshFlag: boolean = false; private notificationBarElementStyles: Partial = { height: "82px", width: "430px", @@ -57,6 +57,7 @@ export class OverlayNotificationsContentService void sendExtensionMessage("checkNotificationQueue"); void sendExtensionMessage("notificationRefreshFlagValue").then((notificationRefreshFlag) => { this.notificationRefreshFlag = !!notificationRefreshFlag; + this.setNotificationRefreshBarHeight(); }); } @@ -223,15 +224,31 @@ export class OverlayNotificationsContentService this.notificationBarElement.id = "bit-notification-bar"; setElementStyles(this.notificationBarElement, this.notificationBarElementStyles, true); - - if (this.notificationRefreshFlag) { - setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); - } + this.setNotificationRefreshBarHeight(); this.notificationBarElement.appendChild(this.notificationBarIframeElement); } } + /** + * Sets the height of the notification bar based on the value of `notificationRefreshFlag`. + * If the flag is `true`, the bar is expanded to 400px and aligned right. + * If the flag is `false`, `null`, or `undefined`, it defaults to height of 82px. + * Skips if the notification bar element has not yet been created. + * + */ + private setNotificationRefreshBarHeight() { + const isNotificationV3 = !!this.notificationRefreshFlag; + + if (!this.notificationBarElement) { + return; + } + + if (isNotificationV3) { + setElementStyles(this.notificationBarElement, { height: "400px", right: "0" }, true); + } + } + /** * Sets up the message listener for the initialization of the notification bar. * This will send the initialization data to the notification bar iframe. From 393926beece21a23037f055bb75175dd6ff718df Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Tue, 13 May 2025 16:10:33 -0400 Subject: [PATCH 11/54] PM-21605 Remove Login text from error notification (#14767) --- .../content/components/notification/confirmation/body.ts | 1 + .../components/notification/confirmation/message.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts index caea38718fd..8286202b498 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/body.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/body.ts @@ -48,6 +48,7 @@ export function NotificationConfirmationBody({ ? NotificationConfirmationMessage({ buttonAria, buttonText, + error, itemName, message: confirmationMessage, messageDetails, diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 2bf8caecfff..8fdda593382 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -8,6 +8,7 @@ import { spacing, themes, typography } from "../../constants/styles"; export type NotificationConfirmationMessageProps = { buttonAria?: string; buttonText?: string; + error?: string; itemName?: string; message?: string; messageDetails?: string; @@ -18,6 +19,7 @@ export type NotificationConfirmationMessageProps = { export function NotificationConfirmationMessage({ buttonAria, buttonText, + error, itemName, message, messageDetails, @@ -29,7 +31,11 @@ export function NotificationConfirmationMessage({ ${message || buttonText ? html`
- ${itemName} + ${!error && itemName + ? html` + ${itemName} + ` + : nothing} Date: Tue, 13 May 2025 16:49:41 -0400 Subject: [PATCH 12/54] [PM-21395] Vault Nudges Bugs (#14737) * updates to empty vault and has items nudges --- apps/browser/src/_locales/en/messages.json | 10 +++- .../vault-v2/vault-v2.component.html | 6 ++- .../components/vault-v2/vault-v2.component.ts | 11 +++- .../spotlight/spotlight.component.html | 8 ++- .../spotlight/spotlight.component.ts | 2 +- .../empty-vault-nudge.service.ts | 5 +- .../has-items-nudge.service.ts | 50 +++++++++++++------ 7 files changed, 69 insertions(+), 23 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index dcdfe7df4d6..fa300b4253b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -5273,8 +5273,14 @@ "hasItemsVaultNudgeTitle": { "message": "Welcome to your vault!" }, - "hasItemsVaultNudgeBody": { - "message": "Autofill items for the current page\nFavorite items for easy access\nSearch your vault for something else" + "hasItemsVaultNudgeBodyOne": { + "message": "Autofill items for the current page" + }, + "hasItemsVaultNudgeBodyTwo": { + "message": "Favorite items for easy access" + }, + "hasItemsVaultNudgeBodyThree": { + "message": "Search your vault for something else" }, "newLoginNudgeTitle": { "message": "Save time with autofill" diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 894f27245b2..b46002d645e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -44,9 +44,13 @@
+
    +
  • {{ "hasItemsVaultNudgeBodyOne" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyTwo" | i18n }}
  • +
  • {{ "hasItemsVaultNudgeBodyThree" | i18n }}
  • +

{{ title }}

-

+

+
- + +

{{ "autofillOptions" | i18n }} @@ -38,4 +38,4 @@ - +

diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html index 485f8f79856..7fda078b066 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ getSectionHeading() }} @@ -71,4 +71,4 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html index 0dc5e3f6ac0..98cc6489bbd 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html +++ b/libs/vault/src/cipher-form/components/custom-fields/custom-fields.component.html @@ -1,4 +1,8 @@ - +

{{ "customFields" | i18n }}

@@ -116,4 +120,4 @@ - +
diff --git a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html index 40a8954b05a..5fd3e08f22d 100644 --- a/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html +++ b/libs/vault/src/cipher-form/components/item-details/item-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

diff --git a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html index e31be492f93..585f11c2ffe 100644 --- a/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html +++ b/libs/vault/src/cipher-form/components/login-details-section/login-details-section.component.html @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }} @@ -127,6 +127,6 @@ > - +

diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html index 4e1c0c5cfd9..b919ed69f0d 100644 --- a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html +++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }} @@ -35,4 +35,4 @@ - +

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 cc74d4e3a68..aa6d339dcd7 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 @@ -1,4 +1,4 @@ - +

{{ "additionalOptions" | i18n }}

@@ -18,4 +18,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html index a794946cb89..67ded3f8358 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html +++ b/libs/vault/src/cipher-view/attachments/attachments-v2-view.component.html @@ -1,4 +1,4 @@ - +

{{ "attachments" | i18n }}

@@ -21,4 +21,4 @@ - +
diff --git a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html index aa3e05b9aab..22049b2a72e 100644 --- a/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html +++ b/libs/vault/src/cipher-view/autofill-options/autofill-options-view.component.html @@ -1,4 +1,4 @@ - +

{{ "autofillOptions" | i18n }}

@@ -41,4 +41,4 @@ - +
diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index ff61addd7db..9d2fa45ba9e 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -1,4 +1,4 @@ - +

{{ setSectionTitle }}

@@ -93,4 +93,4 @@ > - +
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 2492ed0cd81..7c60d35965f 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 @@ -1,4 +1,4 @@ - +

{{ "customFields" | i18n }}

@@ -115,4 +115,4 @@
- + diff --git a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html index 5ba535d0436..32bf1befb66 100644 --- a/libs/vault/src/cipher-view/item-details/item-details-v2.component.html +++ b/libs/vault/src/cipher-view/item-details/item-details-v2.component.html @@ -1,4 +1,4 @@ - +

{{ "itemDetails" | i18n }}

@@ -80,4 +80,4 @@ - +
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 dc1168b7f01..256aec34b50 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 @@ -1,4 +1,4 @@ - +

{{ "loginCredentials" | i18n }}

@@ -164,4 +164,4 @@ > - +
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html index 20390c0a285..f7c28ceb3f0 100644 --- a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html +++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html @@ -1,4 +1,4 @@ - +

{{ "typeSshKey" | i18n }}

@@ -66,4 +66,4 @@ > - +
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 d2154abd098..1b0a1f48f05 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 @@ -1,4 +1,4 @@ - +

{{ "personalDetails" | i18n }}

@@ -64,9 +64,9 @@ > - +
- +

{{ "identification" | i18n }}

@@ -153,9 +153,9 @@ > - +
- +

{{ "contactInfo" | i18n }}

@@ -212,4 +212,4 @@ > - +
From 3e0cc7ca7f9d1576c9122a66bff3e7e20de234ba Mon Sep 17 00:00:00 2001 From: Vincent Salucci <26154748+vincentsalucci@users.noreply.github.com> Date: Wed, 14 May 2025 09:26:47 -0500 Subject: [PATCH 14/54] [PM-18627] Remove customization settings popover (#14713) * chore: remove customization popover strings, refs PM-18627 * chore: delete new settings callout ts/html component, refs PM-18627 * chore: remove new customization code from vault-v2 component, refs PM-18627: :q * chore: delete vault-page service, refs PM-18627 * chore: add state migration to remove data, refs PM-18627 --- apps/browser/src/_locales/en/messages.json | 9 --- .../new-settings-callout.component.html | 29 ------- .../new-settings-callout.component.ts | 81 ------------------- .../components/vault-v2/vault-page.service.ts | 35 -------- .../vault-v2/vault-v2.component.html | 1 - .../components/vault-v2/vault-v2.component.ts | 5 -- libs/common/src/state-migrations/migrate.ts | 6 +- ...mization-options-callout-dismissed.spec.ts | 50 ++++++++++++ ...customization-options-callout-dismissed.ts | 23 ++++++ 9 files changed, 77 insertions(+), 162 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts create mode 100644 libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts create mode 100644 libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index fa300b4253b..df35facff3c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2208,15 +2208,6 @@ "vaultTimeoutAction1": { "message": "Timeout action" }, - "newCustomizationOptionsCalloutTitle": { - "message": "New customization options" - }, - "newCustomizationOptionsCalloutContent": { - "message": "Customize your vault experience with quick copy actions, compact mode, and more!" - }, - "newCustomizationOptionsCalloutLink": { - "message": "View all Appearance settings" - }, "lock": { "message": "Lock", "description": "Verb form: to make secure or inaccessible by" diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html deleted file mode 100644 index 6cc60eed6d5..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.html +++ /dev/null @@ -1,29 +0,0 @@ - - - -
- {{ "newCustomizationOptionsCalloutContent" | i18n }} - - {{ "newCustomizationOptionsCalloutLink" | i18n }} - -
-
-
diff --git a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts b/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts deleted file mode 100644 index 713dc21c424..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/new-settings-callout/new-settings-callout.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { VaultProfileService } from "@bitwarden/angular/vault/services/vault-profile.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { UserId } from "@bitwarden/common/types/guid"; -import { VaultSettingsService } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service"; -import { ButtonModule, PopoverModule } from "@bitwarden/components"; - -import { VaultPopupCopyButtonsService } from "../../../services/vault-popup-copy-buttons.service"; -import { VaultPageService } from "../vault-page.service"; - -@Component({ - selector: "new-settings-callout", - templateUrl: "new-settings-callout.component.html", - standalone: true, - imports: [PopoverModule, JslibModule, CommonModule, ButtonModule], - providers: [VaultPageService], -}) -export class NewSettingsCalloutComponent implements OnInit, OnDestroy { - protected showNewCustomizationSettingsCallout = false; - protected activeUserId: UserId | null = null; - - constructor( - private accountService: AccountService, - private vaultProfileService: VaultProfileService, - private vaultPageService: VaultPageService, - private router: Router, - private logService: LogService, - private copyButtonService: VaultPopupCopyButtonsService, - private vaultSettingsService: VaultSettingsService, - ) {} - - async ngOnInit() { - this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - - const showQuickCopyActions = await firstValueFrom(this.copyButtonService.showQuickCopyActions$); - const clickItemsToAutofillVaultView = await firstValueFrom( - this.vaultSettingsService.clickItemsToAutofillVaultView$, - ); - - let profileCreatedDate: Date; - - try { - profileCreatedDate = await this.vaultProfileService.getProfileCreationDate(this.activeUserId); - } catch (e) { - this.logService.error("Error getting profile creation date", e); - // Default to before the cutoff date to ensure the callout is shown - profileCreatedDate = new Date("2024-12-24"); - } - - const hasCalloutBeenDismissed = await firstValueFrom( - this.vaultPageService.isCalloutDismissed(this.activeUserId), - ); - - this.showNewCustomizationSettingsCallout = - !showQuickCopyActions && - !clickItemsToAutofillVaultView && - !hasCalloutBeenDismissed && - profileCreatedDate < new Date("2024-12-25"); - } - - async goToAppearance() { - await this.router.navigate(["/appearance"]); - } - - async dismissCallout() { - if (this.activeUserId) { - await this.vaultPageService.dismissCallout(this.activeUserId); - } - } - - async ngOnDestroy() { - await this.dismissCallout(); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts deleted file mode 100644 index a7c52ed4c51..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-page.service.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { map, Observable } from "rxjs"; - -import { - BANNERS_DISMISSED_DISK, - StateProvider, - UserKeyDefinition, -} from "@bitwarden/common/platform/state"; -import { UserId } from "@bitwarden/common/types/guid"; - -export const NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY = new UserKeyDefinition( - BANNERS_DISMISSED_DISK, - "newCustomizationOptionsCalloutDismissed", - { - deserializer: (calloutDismissed) => calloutDismissed, - clearOn: [], // Do not clear dismissed callouts - }, -); - -@Injectable() -export class VaultPageService { - private stateProvider = inject(StateProvider); - - isCalloutDismissed(userId: UserId): Observable { - return this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .state$.pipe(map((dismissed) => !!dismissed)); - } - - async dismissCallout(userId: UserId): Promise { - await this.stateProvider - .getUser(userId, NEW_CUSTOMIZATION_OPTIONS_CALLOUT_DISMISSED_KEY) - .update(() => true); - } -} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index b46002d645e..43a96fc616e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -107,5 +107,4 @@ >
- diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 58a6ba0000b..ec4f3939204 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -56,9 +56,7 @@ import { NewItemDropdownV2Component, NewItemInitialValues, } from "./new-item-dropdown/new-item-dropdown-v2.component"; -import { NewSettingsCalloutComponent } from "./new-settings-callout/new-settings-callout.component"; import { VaultHeaderV2Component } from "./vault-header/vault-header-v2.component"; -import { VaultPageService } from "./vault-page.service"; import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from "."; @@ -90,12 +88,10 @@ enum VaultState { ScrollingModule, VaultHeaderV2Component, AtRiskPasswordCalloutComponent, - NewSettingsCalloutComponent, SpotlightComponent, RouterModule, TypographyModule, ], - providers: [VaultPageService], }) export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; @@ -152,7 +148,6 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { protected noResultsIcon = Icons.NoResults; protected VaultStateEnum = VaultState; - protected showNewCustomizationSettingsCallout = false; constructor( private vaultPopupItemsService: VaultPopupItemsService, diff --git a/libs/common/src/state-migrations/migrate.ts b/libs/common/src/state-migrations/migrate.ts index b409f52d936..bea79963b0b 100644 --- a/libs/common/src/state-migrations/migrate.ts +++ b/libs/common/src/state-migrations/migrate.ts @@ -69,12 +69,13 @@ import { MoveLastSyncDate } from "./migrations/68-move-last-sync-date"; import { MigrateIncorrectFolderKey } from "./migrations/69-migrate-incorrect-folder-key"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; import { RemoveAcBannersDismissed } from "./migrations/70-remove-ac-banner-dismissed"; +import { RemoveNewCustomizationOptionsCalloutDismissed } from "./migrations/71-remove-new-customization-options-callout-dismissed"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global"; import { MinVersionMigrator } from "./migrations/min-version"; export const MIN_VERSION = 3; -export const CURRENT_VERSION = 70; +export const CURRENT_VERSION = 71; export type MinVersion = typeof MIN_VERSION; export function createMigrationBuilder() { @@ -146,7 +147,8 @@ export function createMigrationBuilder() { .with(RemoveUnassignedItemsBannerDismissed, 66, 67) .with(MoveLastSyncDate, 67, 68) .with(MigrateIncorrectFolderKey, 68, 69) - .with(RemoveAcBannersDismissed, 69, CURRENT_VERSION); + .with(RemoveAcBannersDismissed, 69, 70) + .with(RemoveNewCustomizationOptionsCalloutDismissed, 70, CURRENT_VERSION); } export async function currentVersion( diff --git a/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts new file mode 100644 index 00000000000..f2c83346a62 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.spec.ts @@ -0,0 +1,50 @@ +import { runMigrator } from "../migration-helper.spec"; +import { IRREVERSIBLE } from "../migrator"; + +import { RemoveNewCustomizationOptionsCalloutDismissed } from "./71-remove-new-customization-options-callout-dismissed"; + +describe("RemoveNewCustomizationOptionsCalloutDismissed", () => { + const sut = new RemoveNewCustomizationOptionsCalloutDismissed(70, 71); + + describe("migrate", () => { + it("deletes new customization options callout dismissed from all users", async () => { + const output = await runMigrator(sut, { + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + user_user1_bannersDismissed_newCustomizationOptionsCalloutDismissed: true, + user_user2_bannersDismissed_newCustomizationOptionsCalloutDismissed: true, + }); + + expect(output).toEqual({ + global_account_accounts: { + user1: { + email: "user1@email.com", + name: "User 1", + emailVerified: true, + }, + user2: { + email: "user2@email.com", + name: "User 2", + emailVerified: true, + }, + }, + }); + }); + }); + + describe("rollback", () => { + it("is irreversible", async () => { + await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE); + }); + }); +}); diff --git a/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts new file mode 100644 index 00000000000..7260048daf6 --- /dev/null +++ b/libs/common/src/state-migrations/migrations/71-remove-new-customization-options-callout-dismissed.ts @@ -0,0 +1,23 @@ +import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; +import { IRREVERSIBLE, Migrator } from "../migrator"; + +export const SHOW_CALLOUT_KEY: KeyDefinitionLike = { + key: "newCustomizationOptionsCalloutDismissed", + stateDefinition: { name: "bannersDismissed" }, +}; + +export class RemoveNewCustomizationOptionsCalloutDismissed extends Migrator<70, 71> { + async migrate(helper: MigrationHelper): Promise { + await Promise.all( + (await helper.getAccounts()).map(async ({ userId }) => { + if (helper.getFromUser(userId, SHOW_CALLOUT_KEY) != null) { + await helper.removeFromUser(userId, SHOW_CALLOUT_KEY); + } + }), + ); + } + + async rollback(helper: MigrationHelper): Promise { + throw IRREVERSIBLE; + } +} From ad3121f5359900c16c172f3a33af31a7681a4647 Mon Sep 17 00:00:00 2001 From: SmithThe4th Date: Wed, 14 May 2025 10:30:01 -0400 Subject: [PATCH 15/54] [PM-12423] Migrate Cipher Decryption to Use SDK (#14206) * Created mappings for client domain object to SDK * Add abstract decrypt observable * Added todo for future consideration * Added implementation to cipher service * Added adapter and unit tests * Created cipher encryption abstraction and service * Register cipher encryption service * Added tests for the cipher encryption service * changed signature * Updated feature flag name * added new function to be used for decrypting ciphers * Added new encryptedKey field * added new function to be used for decrypting ciphers * Manually set fields * Added encrypted key in attachment view * Fixed test * Updated references to use decrypt with feature flag * Added dependency * updated package.json * lint fix * fixed tests * Fixed small mapping issues * Fixed test * Added function to decrypt fido2 key value * Added function to decrypt fido2 key value and updated test * updated to use sdk function without prociding the key * updated localdata sdk type change * decrypt attachment content using sdk * Fixed dependency issues * updated package.json * Refactored service to handle getting decrypted buffer using the legacy and sdk implementations * updated services and component to use refactored version * Updated decryptCiphersWithSdk to use decryptManyLegacy for batch decryption, ensuring the SDK is only called once per batch * Fixed merge conflicts * Fixed merge conflicts * Fixed merge conflicts * Fixed lint issues * Moved getDecryptedAttachmentBuffer to cipher service * Moved getDecryptedAttachmentBuffer to cipher service * ensure CipherView properties are null instead of undefined * Fixed test * ensure AttachmentView properties are null instead of undefined * Linked ticket in comment * removed unused orgKey --- .../background/notification.background.ts | 4 +- .../autofill/popup/fido2/fido2.component.ts | 8 +- .../browser/src/background/main.background.ts | 9 + .../assign-collections.component.ts | 7 +- .../open-attachments.component.spec.ts | 1 + .../open-attachments.component.ts | 4 +- .../vault-password-history-v2.component.ts | 4 +- .../view-v2/view-v2.component.spec.ts | 1 + .../vault-v2/view-v2/view-v2.component.ts | 4 +- .../admin-console/commands/share.command.ts | 8 +- apps/cli/src/commands/edit.command.ts | 15 +- apps/cli/src/commands/get.command.ts | 4 +- .../service-container/service-container.ts | 9 + apps/cli/src/vault/create.command.ts | 8 +- .../services/desktop-autofill.service.ts | 4 +- .../encrypted-message-handler.service.ts | 4 +- .../vault/app/vault/attachments.component.ts | 3 + .../src/vault/app/vault/view.component.ts | 3 +- .../vault-item-dialog.component.ts | 8 +- .../components/collections.component.ts | 4 +- .../angular/src/components/share.component.ts | 8 +- .../src/services/jslib-services.module.ts | 10 + .../vault/components/add-edit.component.ts | 4 +- .../vault/components/attachments.component.ts | 49 ++- .../components/password-history.component.ts | 4 +- .../src/vault/components/view.component.ts | 23 +- libs/common/spec/utils.ts | 14 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../fido2/fido2-authenticator.service.spec.ts | 8 + .../fido2/fido2-authenticator.service.ts | 4 +- .../abstractions/cipher-encryption.service.ts | 60 ++++ .../src/vault/abstractions/cipher.service.ts | 25 ++ .../models/api/cipher-permissions.api.ts | 17 + .../src/vault/models/data/cipher.data.ts | 2 +- .../vault/models/domain/attachment.spec.ts | 17 + .../src/vault/models/domain/attachment.ts | 18 + .../src/vault/models/domain/card.spec.ts | 17 + libs/common/src/vault/models/domain/card.ts | 18 + .../src/vault/models/domain/cipher.spec.ts | 167 ++++++++- libs/common/src/vault/models/domain/cipher.ts | 70 ++++ .../models/domain/fido2-credential.spec.ts | 39 ++ .../vault/models/domain/fido2-credential.ts | 25 ++ .../src/vault/models/domain/field.spec.ts | 24 +- libs/common/src/vault/models/domain/field.ts | 17 + .../src/vault/models/domain/identity.spec.ts | 28 ++ .../src/vault/models/domain/identity.ts | 30 ++ .../src/vault/models/domain/login-uri.spec.ts | 15 + .../src/vault/models/domain/login-uri.ts | 15 + .../src/vault/models/domain/login.spec.ts | 48 +++ libs/common/src/vault/models/domain/login.ts | 19 + .../src/vault/models/domain/password.spec.ts | 13 + .../src/vault/models/domain/password.ts | 14 + .../vault/models/domain/secure-note.spec.ts | 13 + .../src/vault/models/domain/secure-note.ts | 13 + .../src/vault/models/domain/ssh-key.spec.ts | 13 + .../common/src/vault/models/domain/ssh-key.ts | 15 + .../vault/models/view/attachment.view.spec.ts | 55 +++ .../src/vault/models/view/attachment.view.ts | 40 +++ .../common/src/vault/models/view/card.view.ts | 13 + .../src/vault/models/view/cipher.view.spec.ts | 132 ++++++- .../src/vault/models/view/cipher.view.ts | 68 +++- .../models/view/fido2-credential.view.ts | 27 ++ .../src/vault/models/view/field.view.ts | 19 + .../src/vault/models/view/identity.view.ts | 13 + .../vault/models/view/login-uri-view.spec.ts | 22 ++ .../src/vault/models/view/login-uri.view.ts | 17 + .../src/vault/models/view/login.view.spec.ts | 35 +- .../src/vault/models/view/login.view.ts | 25 ++ .../models/view/password-history.view.spec.ts | 23 ++ .../models/view/password-history.view.ts | 17 + .../src/vault/models/view/secure-note.view.ts | 13 + .../src/vault/models/view/ssh-key.view.ts | 17 + .../src/vault/services/cipher.service.spec.ts | 87 ++++- .../src/vault/services/cipher.service.ts | 89 ++++- .../default-cipher-encryption.service.spec.ts | 334 ++++++++++++++++++ .../default-cipher-encryption.service.ts | 190 ++++++++++ .../bitwarden/bitwarden-json-importer.ts | 4 +- .../individual-vault-export.service.spec.ts | 15 +- .../individual-vault-export.service.ts | 36 +- .../src/services/org-vault-export.service.ts | 9 +- .../cipher-attachments.component.spec.ts | 1 + .../cipher-attachments.component.ts | 8 +- .../services/default-cipher-form.service.ts | 14 +- .../download-attachment.component.spec.ts | 35 +- .../download-attachment.component.ts | 41 +-- 85 files changed, 2171 insertions(+), 218 deletions(-) create mode 100644 libs/common/src/vault/abstractions/cipher-encryption.service.ts create mode 100644 libs/common/src/vault/services/default-cipher-encryption.service.spec.ts create mode 100644 libs/common/src/vault/services/default-cipher-encryption.service.ts diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 52920ec67a8..a73141b7e4d 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -894,9 +894,7 @@ export default class NotificationBackground { private async getDecryptedCipherById(cipherId: string, userId: UserId) { const cipher = await this.cipherService.get(cipherId, userId); if (cipher != null && cipher.type === CipherType.Login) { - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } return null; } diff --git a/apps/browser/src/autofill/popup/fido2/fido2.component.ts b/apps/browser/src/autofill/popup/fido2/fido2.component.ts index 0471d460fd5..6b7d9120195 100644 --- a/apps/browser/src/autofill/popup/fido2/fido2.component.ts +++ b/apps/browser/src/autofill/popup/fido2/fido2.component.ts @@ -216,9 +216,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.cipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); @@ -237,9 +235,7 @@ export class Fido2Component implements OnInit, OnDestroy { this.ciphers = await Promise.all( message.existingCipherIds.map(async (cipherId) => { const cipher = await this.cipherService.get(cipherId, activeUserId); - return cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return this.cipherService.decrypt(cipher, activeUserId); }), ); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 85a9cd27c57..a724f857cd1 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -183,6 +183,7 @@ import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-st import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { InternalSendService as InternalSendServiceAbstraction } from "@bitwarden/common/tools/send/services/send.service.abstraction"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -199,6 +200,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -408,6 +410,7 @@ export default class MainBackground { endUserNotificationService: EndUserNotificationService; inlineMenuFieldQualificationService: InlineMenuFieldQualificationService; taskService: TaskService; + cipherEncryptionService: CipherEncryptionService; ipcContentScriptManagerService: IpcContentScriptManagerService; ipcService: IpcService; @@ -856,6 +859,11 @@ export default class MainBackground { this.bulkEncryptService = new FallbackBulkEncryptService(this.encryptService); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -871,6 +879,7 @@ export default class MainBackground { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( this.keyService, diff --git a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts index 27f3b7e5e18..7052be5ea62 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/assign-collections/assign-collections.component.ts @@ -11,7 +11,6 @@ import { CollectionService } from "@bitwarden/admin-console/common"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey, UserKey } from "@bitwarden/common/types/key"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -66,11 +65,7 @@ export class AssignCollections { route.queryParams.pipe( switchMap(async ({ cipherId }) => { const cipherDomain = await this.cipherService.get(cipherId, userId); - const key: UserKey | OrgKey = await this.cipherService.getKeyForCipherKeyDecryption( - cipherDomain, - userId, - ); - return cipherDomain.decrypt(key); + return await this.cipherService.decrypt(cipherDomain, userId); }), ), ), diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts index 66d9096cd5c..ec5c93feb9e 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.spec.ts @@ -81,6 +81,7 @@ describe("OpenAttachmentsComponent", () => { useValue: { get: getCipher, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts index 1bc7e22e6d5..9189ea51313 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/attachments/open-attachments/open-attachments.component.ts @@ -81,9 +81,7 @@ export class OpenAttachmentsComponent implements OnInit { this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); if (!cipher.organizationId) { this.cipherIsAPartOfFreeOrg = false; diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts index 5d315775b10..d0eef20f044 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-password-history-v2/vault-password-history-v2.component.ts @@ -69,8 +69,6 @@ export class PasswordHistoryV2Component implements OnInit { const activeUserId = activeAccount.id as UserId; const cipher = await this.cipherService.get(cipherId, activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 44874221a59..3222f39a162 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -82,6 +82,7 @@ describe("ViewV2Component", () => { getKeyForCipherKeyDecryption: jest.fn().mockResolvedValue({}), deleteWithServer: jest.fn().mockResolvedValue(undefined), softDeleteWithServer: jest.fn().mockResolvedValue(undefined), + decrypt: jest.fn().mockResolvedValue(mockCipher), }; beforeEach(async () => { diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index a834314560b..0a71caf5aee 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -203,9 +203,7 @@ export class ViewV2Component { async getCipherData(id: string, userId: UserId) { const cipher = await this.cipherService.get(id, userId); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, userId), - ); + return await this.cipherService.decrypt(cipher, userId); } async editCipher() { diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 6d9e6c8b6c0..540bc2659c9 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -59,15 +59,11 @@ export class ShareCommand { return Response.badRequest("This item already belongs to an organization."); } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); try { await this.cipherService.shareWithServer(cipherView, organizationId, req, activeUserId); const updatedCipher = await this.cipherService.get(cipher.id, activeUserId); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index 2d4a854135d..4dcf805661d 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -90,9 +90,7 @@ export class EditCommand { return Response.notFound(); } - let cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + let cipherView = await this.cipherService.decrypt(cipher, activeUserId); if (cipherView.isDeleted) { return Response.badRequest("You may not edit a deleted item. Use the restore command first."); } @@ -100,9 +98,7 @@ export class EditCommand { const encCipher = await this.cipherService.encrypt(cipherView, activeUserId); try { const updatedCipher = await this.cipherService.updateWithServer(encCipher); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -132,12 +128,7 @@ export class EditCommand { cipher, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption( - updatedCipher, - await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), - ), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { diff --git a/apps/cli/src/commands/get.command.ts b/apps/cli/src/commands/get.command.ts index 1bdbd051585..c3ba6044f8a 100644 --- a/apps/cli/src/commands/get.command.ts +++ b/apps/cli/src/commands/get.command.ts @@ -116,9 +116,7 @@ export class GetCommand extends DownloadCommand { if (Utils.isGuid(id)) { const cipher = await this.cipherService.get(id, activeUserId); if (cipher != null) { - decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + decCipher = await this.cipherService.decrypt(cipher, activeUserId); } } else if (id.trim() !== "") { let ciphers = await this.cipherService.getAllDecrypted(activeUserId); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index fe2f506f229..cdf6c4bbfda 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -139,12 +139,14 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider"; import { SendService } from "@bitwarden/common/tools/send/services/send.service"; import { UserId } from "@bitwarden/common/types/guid"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherAuthorizationService, DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -284,6 +286,7 @@ export class ServiceContainer { ssoUrlService: SsoUrlService; masterPasswordApiService: MasterPasswordApiServiceAbstraction; bulkEncryptService: FallbackBulkEncryptService; + cipherEncryptionService: CipherEncryptionService; constructor() { let p = null; @@ -679,6 +682,11 @@ export class ServiceContainer { this.accountService, ); + this.cipherEncryptionService = new DefaultCipherEncryptionService( + this.sdkService, + this.logService, + ); + this.cipherService = new CipherService( this.keyService, this.domainSettingsService, @@ -694,6 +702,7 @@ export class ServiceContainer { this.stateProvider, this.accountService, this.logService, + this.cipherEncryptionService, ); this.folderService = new FolderService( diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 5b34d2cb507..b1536e23748 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -93,9 +93,7 @@ export class CreateCommand { const cipher = await this.cipherService.encrypt(CipherExport.toView(req), activeUserId); try { const newCipher = await this.cipherService.createWithServer(cipher); - const decCipher = await newCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(newCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(newCipher, activeUserId); const res = new CipherResponse(decCipher); return Response.success(res); } catch (e) { @@ -162,9 +160,7 @@ export class CreateCommand { new Uint8Array(fileBuf).buffer, activeUserId, ); - const decCipher = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(updatedCipher, activeUserId); return Response.success(new CipherResponse(decCipher)); } catch (e) { return Response.error(e); diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts index e88e16c2ffc..d6dddf3b23f 100644 --- a/apps/desktop/src/autofill/services/desktop-autofill.service.ts +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -199,9 +199,7 @@ export class DesktopAutofillService implements OnDestroy { return; } - const decrypted = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decrypted = await this.cipherService.decrypt(cipher, activeUserId); const fido2Credential = decrypted.login.fido2Credentials?.[0]; if (!fido2Credential) { diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 591ff6fa8cf..37a8114c1d1 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -207,9 +207,7 @@ export class EncryptedMessageHandlerService { return { status: "failure" }; } - const cipherView = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipher, activeUserId); cipherView.name = credentialUpdatePayload.name; cipherView.login.password = credentialUpdatePayload.password; cipherView.login.username = credentialUpdatePayload.userName; diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index ea4f49b8431..a2cea5f2722 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -5,6 +5,7 @@ import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -33,6 +34,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService: BillingAccountProfileStateService, accountService: AccountService, toastService: ToastService, + configService: ConfigService, ) { super( cipherService, @@ -49,6 +51,7 @@ export class AttachmentsComponent extends BaseAttachmentsComponent { billingAccountProfileStateService, accountService, toastService, + configService, ); } } diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e5f677cbca6..e74b07445da 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -72,7 +72,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro accountService: AccountService, toastService: ToastService, cipherAuthorizationService: CipherAuthorizationService, - private configService: ConfigService, + configService: ConfigService, ) { super( cipherService, @@ -100,6 +100,7 @@ export class ViewComponent extends BaseViewComponent implements OnInit, OnDestro billingAccountProfileStateService, toastService, cipherAuthorizationService, + configService, ); } diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 10c35f861b9..aa457e97093 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -481,9 +481,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { activeUserId, ); - updatedCipherView = await updatedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(updatedCipher, activeUserId), - ); + updatedCipherView = await this.cipherService.decrypt(updatedCipher, activeUserId); } this.cipherFormComponent.patchCipher((currentCipher) => { @@ -520,9 +518,7 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { return; } const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await config.originalCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(config.originalCipher, activeUserId), - ); + return await this.cipherService.decrypt(config.originalCipher, activeUserId); } private updateTitle() { diff --git a/libs/angular/src/admin-console/components/collections.component.ts b/libs/angular/src/admin-console/components/collections.component.ts index 5f39966468f..8ae90705f92 100644 --- a/libs/angular/src/admin-console/components/collections.component.ts +++ b/libs/angular/src/admin-console/components/collections.component.ts @@ -50,9 +50,7 @@ export class CollectionsComponent implements OnInit { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); this.collectionIds = this.loadCipherCollections(); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.collections = await this.loadCollections(); this.collections.forEach((c) => ((c as any).checked = false)); diff --git a/libs/angular/src/components/share.component.ts b/libs/angular/src/components/share.component.ts index e785441b8e4..198cc7dc3a5 100644 --- a/libs/angular/src/components/share.component.ts +++ b/libs/angular/src/components/share.component.ts @@ -76,9 +76,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - this.cipher = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipherDomain, activeUserId); } filterCollections() { @@ -105,9 +103,7 @@ export class ShareComponent implements OnInit, OnDestroy { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipherDomain = await this.cipherService.get(this.cipherId, activeUserId); - const cipherView = await cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipherDomain, activeUserId), - ); + const cipherView = await this.cipherService.decrypt(cipherDomain, activeUserId); const orgs = await firstValueFrom(this.organizations$); const orgName = orgs.find((o) => o.id === this.organizationId)?.name ?? this.i18nService.t("organization"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 3ffca776034..920d35a1017 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -263,6 +263,7 @@ import { InternalSendService, SendService as SendServiceAbstraction, } from "@bitwarden/common/tools/send/services/send.service.abstraction"; +import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherFileUploadService as CipherFileUploadServiceAbstraction } from "@bitwarden/common/vault/abstractions/file-upload/cipher-file-upload.service"; import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction"; @@ -281,6 +282,7 @@ import { DefaultCipherAuthorizationService, } from "@bitwarden/common/vault/services/cipher-authorization.service"; import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; +import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service"; import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { FolderService } from "@bitwarden/common/vault/services/folder/folder.service"; @@ -509,6 +511,7 @@ const safeProviders: SafeProvider[] = [ stateProvider: StateProvider, accountService: AccountServiceAbstraction, logService: LogService, + cipherEncryptionService: CipherEncryptionService, ) => new CipherService( keyService, @@ -525,6 +528,7 @@ const safeProviders: SafeProvider[] = [ stateProvider, accountService, logService, + cipherEncryptionService, ), deps: [ KeyService, @@ -541,6 +545,7 @@ const safeProviders: SafeProvider[] = [ StateProvider, AccountServiceAbstraction, LogService, + CipherEncryptionService, ], }), safeProvider({ @@ -1528,6 +1533,11 @@ const safeProviders: SafeProvider[] = [ useClass: MasterPasswordApiService, deps: [ApiServiceAbstraction, LogService], }), + safeProvider({ + provide: CipherEncryptionService, + useClass: DefaultCipherEncryptionService, + deps: [SdkService, LogService], + }), ]; @NgModule({ diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts index b9defa8383d..b04adc1fdfb 100644 --- a/libs/angular/src/vault/components/add-edit.component.ts +++ b/libs/angular/src/vault/components/add-edit.component.ts @@ -269,9 +269,7 @@ export class AddEditComponent implements OnInit, OnDestroy { if (this.cipher == null) { if (this.editMode) { const cipher = await this.loadCipher(activeUserId); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); // Adjust Cipher Name if Cloning if (this.cloneMode) { diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 9e9450c587e..e4b01d3aac1 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -9,13 +9,13 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; @@ -56,6 +56,7 @@ export class AttachmentsComponent implements OnInit { protected billingAccountProfileStateService: BillingAccountProfileStateService, protected accountService: AccountService, protected toastService: ToastService, + protected configService: ConfigService, ) {} async ngOnInit() { @@ -88,9 +89,7 @@ export class AttachmentsComponent implements OnInit { const activeUserId = await firstValueFrom(getUserId(this.accountService.activeAccount$)); this.formPromise = this.saveCipherAttachment(files[0], activeUserId); this.cipherDomain = await this.formPromise; - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); this.toastService.showToast({ variant: "success", title: null, @@ -130,9 +129,7 @@ export class AttachmentsComponent implements OnInit { const updatedCipher = await this.deletePromises[attachment.id]; const cipher = new Cipher(updatedCipher); - this.cipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(cipher, activeUserId); this.toastService.showToast({ variant: "success", @@ -197,12 +194,14 @@ export class AttachmentsComponent implements OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, @@ -228,9 +227,7 @@ export class AttachmentsComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); this.cipherDomain = await this.loadCipher(activeUserId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); const canAccessPremium = await firstValueFrom( this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId), @@ -276,15 +273,17 @@ export class AttachmentsComponent implements OnInit { try { // 2. Resave - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getUserId), ); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipherDomain.id as CipherId, + attachment, + response, + activeUserId, + ); + this.cipherDomain = await this.cipherService.saveAttachmentRawWithServer( this.cipherDomain, attachment.fileName, @@ -292,9 +291,7 @@ export class AttachmentsComponent implements OnInit { activeUserId, admin, ); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, activeUserId); // 3. Delete old this.deletePromises[attachment.id] = this.deleteCipherAttachment( diff --git a/libs/angular/src/vault/components/password-history.component.ts b/libs/angular/src/vault/components/password-history.component.ts index 4df9f4bd24d..acb89b82191 100644 --- a/libs/angular/src/vault/components/password-history.component.ts +++ b/libs/angular/src/vault/components/password-history.component.ts @@ -42,9 +42,7 @@ export class PasswordHistoryComponent implements OnInit { protected async init() { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); const cipher = await this.cipherService.get(this.cipherId, activeUserId); - const decCipher = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const decCipher = await this.cipherService.decrypt(cipher, activeUserId); this.history = decCipher.passwordHistory == null ? [] : decCipher.passwordHistory; } } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 9d5a8fe9e62..8915cb6b671 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -34,13 +34,13 @@ import { EventType } from "@bitwarden/common/enums"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { CollectionId, UserId } from "@bitwarden/common/types/guid"; +import { CipherId, CollectionId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { TotpService } from "@bitwarden/common/vault/abstractions/totp.service"; @@ -137,6 +137,7 @@ export class ViewComponent implements OnDestroy, OnInit { private billingAccountProfileStateService: BillingAccountProfileStateService, protected toastService: ToastService, private cipherAuthorizationService: CipherAuthorizationService, + protected configService: ConfigService, ) {} ngOnInit() { @@ -458,19 +459,19 @@ export class ViewComponent implements OnDestroy, OnInit { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(this.cipher.organizationId); - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + this.fileDownloadService.download({ fileName: attachment.fileName, blobData: decBuf, }); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { + } catch { this.toastService.showToast({ variant: "error", title: null, diff --git a/libs/common/spec/utils.ts b/libs/common/spec/utils.ts index 51db65d0ce0..2b9b2567895 100644 --- a/libs/common/spec/utils.ts +++ b/libs/common/spec/utils.ts @@ -64,6 +64,20 @@ export function makeSymmetricCryptoKey( */ export const mockFromJson = (stub: any) => (stub + "_fromJSON") as any; +/** + * Use to mock a return value of a static fromSdk method. + */ +export const mockFromSdk = (stub: any) => { + if (typeof stub === "object") { + return { + ...stub, + __fromSdk: true, + }; + } + + return `${stub}_fromSdk`; +}; + /** * Tracks the emissions of the given observable. * diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index ddc75eb0d66..d349703bddf 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -57,6 +57,7 @@ export enum FeatureFlag { PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", SecurityTasks = "security-tasks", + PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk", CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", EndUserNotifications = "pm-10609-end-user-notifications", @@ -111,6 +112,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, + [FeatureFlag.PM19941MigrateCipherDomainToSdk]: FALSE, /* Auth */ [FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE, diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts index 3ea86a1f504..5c377e1a980 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.spec.ts @@ -152,6 +152,7 @@ describe("FidoAuthenticatorService", () => { id === excludedCipher.id ? ({ decrypt: () => excludedCipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([excludedCipher]); + cipherService.decrypt.mockResolvedValue(excludedCipher); }); /** @@ -220,6 +221,7 @@ describe("FidoAuthenticatorService", () => { id === existingCipher.id ? ({ decrypt: () => existingCipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([existingCipher]); + cipherService.decrypt.mockResolvedValue(existingCipher); }); /** @@ -306,6 +308,11 @@ describe("FidoAuthenticatorService", () => { const encryptedCipher = { ...existingCipher, reprompt: CipherRepromptType.Password }; cipherService.get.mockResolvedValue(encryptedCipher as unknown as Cipher); + cipherService.decrypt.mockResolvedValue({ + ...existingCipher, + reprompt: CipherRepromptType.Password, + } as unknown as CipherView); + const result = async () => await authenticator.makeCredential(params, windowReference); await expect(result).rejects.toThrowError(Fido2AuthenticatorErrorCode.Unknown); @@ -347,6 +354,7 @@ describe("FidoAuthenticatorService", () => { cipherId === cipher.id ? ({ decrypt: () => cipher } as any) : undefined, ); cipherService.getAllDecrypted.mockResolvedValue([await cipher]); + cipherService.decrypt.mockResolvedValue(cipher); cipherService.encrypt.mockImplementation(async (cipher) => { cipher.login.fido2Credentials[0].credentialId = credentialId; // Replace id for testability return {} as any; diff --git a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts index 76bd19b2876..a605e466338 100644 --- a/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts +++ b/libs/common/src/platform/services/fido2/fido2-authenticator.service.ts @@ -151,9 +151,7 @@ export class Fido2AuthenticatorService ); const encrypted = await this.cipherService.get(cipherId, activeUserId); - cipher = await encrypted.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(encrypted, activeUserId), - ); + cipher = await this.cipherService.decrypt(encrypted, activeUserId); if ( !userVerified && diff --git a/libs/common/src/vault/abstractions/cipher-encryption.service.ts b/libs/common/src/vault/abstractions/cipher-encryption.service.ts new file mode 100644 index 00000000000..6b2a8e8943e --- /dev/null +++ b/libs/common/src/vault/abstractions/cipher-encryption.service.ts @@ -0,0 +1,60 @@ +import { CipherListView } from "@bitwarden/sdk-internal"; + +import { UserId } from "../../types/guid"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; + +/** + * Service responsible for encrypting and decrypting ciphers. + */ +export abstract class CipherEncryptionService { + /** + * Decrypts a cipher using the SDK for the given userId. + * + * @param cipher The encrypted cipher object + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted cipher view + */ + abstract decrypt(cipher: Cipher, userId: UserId): Promise; + /** + * Decrypts many ciphers using the SDK for the given userId. + * + * For bulk decryption, prefer using `decryptMany`, which returns a more efficient + * `CipherListView` object. + * + * @param ciphers The encrypted cipher objects + * @param userId The user ID whose key will be used for decryption + * + * @deprecated Use `decryptMany` for bulk decryption instead. + * + * @returns A promise that resolves to an array of decrypted cipher views + */ + abstract decryptManyLegacy(ciphers: Cipher[], userId: UserId): Promise; + /** + * Decrypts many ciphers using the SDK for the given userId. + * + * @param ciphers The encrypted cipher objects + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to an array of decrypted cipher list views + */ + abstract decryptMany(ciphers: Cipher[], userId: UserId): Promise; + /** + * Decrypts an attachment's content from a response object. + * + * @param cipher The encrypted cipher object that owns the attachment + * @param attachment The attachment view object + * @param encryptedContent The encrypted content of the attachment + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + abstract decryptAttachmentContent( + cipher: Cipher, + attachment: AttachmentView, + encryptedContent: Uint8Array, + userId: UserId, + ): Promise; +} diff --git a/libs/common/src/vault/abstractions/cipher.service.ts b/libs/common/src/vault/abstractions/cipher.service.ts index b12488a5e03..a67dfcef8b9 100644 --- a/libs/common/src/vault/abstractions/cipher.service.ts +++ b/libs/common/src/vault/abstractions/cipher.service.ts @@ -14,6 +14,7 @@ import { LocalData } from "../models/data/local.data"; import { Cipher } from "../models/domain/cipher"; import { Field } from "../models/domain/field"; import { CipherWithIdRequest } from "../models/request/cipher-with-id.request"; +import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { FieldView } from "../models/view/field.view"; import { AddEditCipherInfo } from "../types/add-edit-cipher-info"; @@ -215,4 +216,28 @@ export abstract class CipherService implements UserKeyRotationDataProvider; abstract getNextCardCipher(userId: UserId): Promise; abstract getNextIdentityCipher(userId: UserId): Promise; + + /** + * Decrypts a cipher using either the SDK or the legacy method based on the feature flag. + * @param cipher The cipher to decrypt. + * @param userId The user ID to use for decryption. + * @returns A promise that resolves to the decrypted cipher view. + */ + abstract decrypt(cipher: Cipher, userId: UserId): Promise; + /** + * Decrypts an attachment's content from a response object. + * + * @param cipherId The ID of the cipher that owns the attachment + * @param attachment The attachment view object + * @param response The response object containing the encrypted content + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + abstract getDecryptedAttachmentBuffer( + cipherId: CipherId, + attachment: AttachmentView, + response: Response, + userId: UserId, + ): Promise; } diff --git a/libs/common/src/vault/models/api/cipher-permissions.api.ts b/libs/common/src/vault/models/api/cipher-permissions.api.ts index 4df7f891e26..b7341d39b1d 100644 --- a/libs/common/src/vault/models/api/cipher-permissions.api.ts +++ b/libs/common/src/vault/models/api/cipher-permissions.api.ts @@ -1,5 +1,7 @@ import { Jsonify } from "type-fest"; +import { CipherPermissions as SdkCipherPermissions } from "@bitwarden/sdk-internal"; + import { BaseResponse } from "../../../models/response/base.response"; export class CipherPermissionsApi extends BaseResponse { @@ -18,4 +20,19 @@ export class CipherPermissionsApi extends BaseResponse { static fromJSON(obj: Jsonify) { return Object.assign(new CipherPermissionsApi(), obj); } + + /** + * Converts the SDK CipherPermissionsApi to a CipherPermissionsApi. + */ + static fromSdkCipherPermissions(obj: SdkCipherPermissions): CipherPermissionsApi | undefined { + if (!obj) { + return undefined; + } + + const permissions = new CipherPermissionsApi(); + permissions.delete = obj.delete; + permissions.restore = obj.restore; + + return permissions; + } } diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts index ee5e5b3e72b..1be70283fb3 100644 --- a/libs/common/src/vault/models/data/cipher.data.ts +++ b/libs/common/src/vault/models/data/cipher.data.ts @@ -39,7 +39,7 @@ export class CipherData { passwordHistory?: PasswordHistoryData[]; collectionIds?: string[]; creationDate: string; - deletedDate: string; + deletedDate: string | null; reprompt: CipherRepromptType; key: string; diff --git a/libs/common/src/vault/models/domain/attachment.spec.ts b/libs/common/src/vault/models/domain/attachment.spec.ts index 40d7ea7f05c..eab67320679 100644 --- a/libs/common/src/vault/models/domain/attachment.spec.ts +++ b/libs/common/src/vault/models/domain/attachment.spec.ts @@ -153,4 +153,21 @@ describe("Attachment", () => { expect(Attachment.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkAttachment", () => { + it("should map to SDK Attachment", () => { + const attachment = new Attachment(data); + + const sdkAttachment = attachment.toSdkAttachment(); + + expect(sdkAttachment).toEqual({ + id: "id", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "fileName", + key: "key", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 15ce06e0881..4339f31a2e1 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Attachment as SdkAttachment } from "@bitwarden/sdk-internal"; + import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; @@ -113,4 +115,20 @@ export class Attachment extends Domain { fileName, }); } + + /** + * Maps to SDK Attachment + * + * @returns {SdkAttachment} - The SDK Attachment object + */ + toSdkAttachment(): SdkAttachment { + return { + id: this.id, + url: this.url, + size: this.size, + sizeName: this.sizeName, + fileName: this.fileName?.toJSON(), + key: this.key?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/card.spec.ts b/libs/common/src/vault/models/domain/card.spec.ts index a7011966d94..19546ddcb05 100644 --- a/libs/common/src/vault/models/domain/card.spec.ts +++ b/libs/common/src/vault/models/domain/card.spec.ts @@ -99,4 +99,21 @@ describe("Card", () => { expect(Card.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkCard", () => { + it("should map to SDK Card", () => { + const card = new Card(data); + + const sdkCard = card.toSdkCard(); + + expect(sdkCard).toEqual({ + cardholderName: "encHolder", + brand: "encBrand", + number: "encNumber", + expMonth: "encMonth", + expYear: "encYear", + code: "encCode", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/card.ts b/libs/common/src/vault/models/domain/card.ts index 3d73a8f527c..43068012ef6 100644 --- a/libs/common/src/vault/models/domain/card.ts +++ b/libs/common/src/vault/models/domain/card.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Card as SdkCard } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -85,4 +87,20 @@ export class Card extends Domain { code, }); } + + /** + * Maps Card to SDK format. + * + * @returns {SdkCard} The SDK card object. + */ + toSdkCard(): SdkCard { + return { + cardholderName: this.cardholderName?.toJSON(), + brand: this.brand?.toJSON(), + number: this.number?.toJSON(), + expMonth: this.expMonth?.toJSON(), + expYear: this.expYear?.toJSON(), + code: this.code?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/cipher.spec.ts b/libs/common/src/vault/models/domain/cipher.spec.ts index 0ef2233120a..a889f0b969c 100644 --- a/libs/common/src/vault/models/domain/cipher.spec.ts +++ b/libs/common/src/vault/models/domain/cipher.spec.ts @@ -3,6 +3,12 @@ import { Jsonify } from "type-fest"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { KeyService } from "@bitwarden/key-management"; +import { + CipherType as SdkCipherType, + UriMatchType, + CipherRepromptType as SdkCipherRepromptType, + LoginLinkedIdType, +} from "@bitwarden/sdk-internal"; import { makeStaticByteArray, mockEnc, mockFromJson } from "../../../../spec/utils"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; @@ -12,7 +18,7 @@ import { ContainerService } from "../../../platform/services/container.service"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; import { UserId } from "../../../types/guid"; import { CipherService } from "../../abstractions/cipher.service"; -import { FieldType, SecureNoteType } from "../../enums"; +import { FieldType, LoginLinkedId, SecureNoteType } from "../../enums"; import { CipherRepromptType } from "../../enums/cipher-reprompt-type"; import { CipherType } from "../../enums/cipher-type"; import { CipherData } from "../../models/data/cipher.data"; @@ -770,6 +776,165 @@ describe("Cipher DTO", () => { expect(Cipher.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkCipher", () => { + it("should map to SDK Cipher", () => { + const lastUsedDate = new Date("2025-04-15T12:00:00.000Z").getTime(); + const lastLaunched = new Date("2025-04-15T12:00:00.000Z").getTime(); + + const cipherData: CipherData = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + edit: true, + permissions: new CipherPermissionsApi(), + viewPassword: true, + organizationUseTotp: true, + favorite: false, + revisionDate: "2022-01-31T12:00:00.000Z", + type: CipherType.Login, + name: "EncryptedString", + notes: "EncryptedString", + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: null, + reprompt: CipherRepromptType.None, + key: "EncryptedString", + login: { + uris: [ + { + uri: "EncryptedString", + uriChecksum: "EncryptedString", + match: UriMatchStrategy.Domain, + }, + ], + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "EncryptedString", + autofillOnPageLoad: false, + }, + passwordHistory: [ + { password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }, + ], + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], + fields: [ + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedId.Username, + }, + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedId.Password, + }, + ], + }; + + const cipher = new Cipher(cipherData, { lastUsedDate, lastLaunched }); + const sdkCipher = cipher.toSdkCipher(); + + expect(sdkCipher).toEqual({ + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: [], + key: "EncryptedString", + name: "EncryptedString", + notes: "EncryptedString", + type: SdkCipherType.Login, + login: { + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + uris: [ + { + uri: "EncryptedString", + uriChecksum: "EncryptedString", + match: UriMatchType.Domain, + }, + ], + totp: "EncryptedString", + autofillOnPageLoad: false, + fido2Credentials: undefined, + }, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + favorite: false, + reprompt: SdkCipherRepromptType.None, + organizationUseTotp: true, + edit: true, + permissions: new CipherPermissionsApi(), + viewPassword: true, + localData: { + lastUsedDate: "2025-04-15T12:00:00.000Z", + lastLaunched: "2025-04-15T12:00:00.000Z", + }, + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], + fields: [ + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedIdType.Username, + }, + { + name: "EncryptedString", + value: "EncryptedString", + type: FieldType.Linked, + linkedId: LoginLinkedIdType.Password, + }, + ], + passwordHistory: [ + { + password: "EncryptedString", + lastUsedDate: "2022-01-31T12:00:00.000Z", + }, + ], + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: undefined, + revisionDate: "2022-01-31T12:00:00.000Z", + }); + }); + }); }); const mockUserId = "TestUserId" as UserId; diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts index 780690217a8..f647adf198e 100644 --- a/libs/common/src/vault/models/domain/cipher.ts +++ b/libs/common/src/vault/models/domain/cipher.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Cipher as SdkCipher } from "@bitwarden/sdk-internal"; + import { Decryptable } from "../../../platform/interfaces/decryptable.interface"; import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; @@ -330,4 +332,72 @@ export class Cipher extends Domain implements Decryptable { return domain; } + + /** + * Maps Cipher to SDK format. + * + * @returns {SdkCipher} The SDK cipher object. + */ + toSdkCipher(): SdkCipher { + const sdkCipher: SdkCipher = { + id: this.id, + organizationId: this.organizationId, + folderId: this.folderId, + collectionIds: this.collectionIds || [], + key: this.key?.toJSON(), + name: this.name.toJSON(), + notes: this.notes?.toJSON(), + type: this.type, + favorite: this.favorite, + organizationUseTotp: this.organizationUseTotp, + edit: this.edit, + permissions: this.permissions, + viewPassword: this.viewPassword, + localData: this.localData + ? { + lastUsedDate: this.localData.lastUsedDate + ? new Date(this.localData.lastUsedDate).toISOString() + : undefined, + lastLaunched: this.localData.lastLaunched + ? new Date(this.localData.lastLaunched).toISOString() + : undefined, + } + : undefined, + attachments: this.attachments?.map((a) => a.toSdkAttachment()), + fields: this.fields?.map((f) => f.toSdkField()), + passwordHistory: this.passwordHistory?.map((ph) => ph.toSdkPasswordHistory()), + revisionDate: this.revisionDate?.toISOString(), + creationDate: this.creationDate?.toISOString(), + deletedDate: this.deletedDate?.toISOString(), + reprompt: this.reprompt, + // Initialize all cipher-type-specific properties as undefined + login: undefined, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + }; + + switch (this.type) { + case CipherType.Login: + sdkCipher.login = this.login.toSdkLogin(); + break; + case CipherType.SecureNote: + sdkCipher.secureNote = this.secureNote.toSdkSecureNote(); + break; + case CipherType.Card: + sdkCipher.card = this.card.toSdkCard(); + break; + case CipherType.Identity: + sdkCipher.identity = this.identity.toSdkIdentity(); + break; + case CipherType.SshKey: + sdkCipher.sshKey = this.sshKey.toSdkSshKey(); + break; + default: + break; + } + + return sdkCipher; + } } diff --git a/libs/common/src/vault/models/domain/fido2-credential.spec.ts b/libs/common/src/vault/models/domain/fido2-credential.spec.ts index e2cddcea3f3..bde29d0e99c 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.spec.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.spec.ts @@ -167,6 +167,45 @@ describe("Fido2Credential", () => { expect(Fido2Credential.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Fido2Credential Mapping", () => { + it("should map to SDK Fido2Credential", () => { + const data: Fido2CredentialData = { + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "2", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: mockDate.toISOString(), + }; + + const credential = new Fido2Credential(data); + const sdkCredential = credential.toSdkFido2Credential(); + + expect(sdkCredential).toEqual({ + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "2", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: mockDate.toISOString(), + }); + }); + }); }); function createEncryptedEncString(s: string): EncString { diff --git a/libs/common/src/vault/models/domain/fido2-credential.ts b/libs/common/src/vault/models/domain/fido2-credential.ts index 8b0082892e4..7002a58150d 100644 --- a/libs/common/src/vault/models/domain/fido2-credential.ts +++ b/libs/common/src/vault/models/domain/fido2-credential.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Fido2Credential as SdkFido2Credential } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -148,4 +150,27 @@ export class Fido2Credential extends Domain { creationDate, }); } + + /** + * Maps Fido2Credential to SDK format. + * + * @returns {SdkFido2Credential} The SDK Fido2Credential object. + */ + toSdkFido2Credential(): SdkFido2Credential { + return { + credentialId: this.credentialId?.toJSON(), + keyType: this.keyType.toJSON(), + keyAlgorithm: this.keyAlgorithm.toJSON(), + keyCurve: this.keyCurve.toJSON(), + keyValue: this.keyValue.toJSON(), + rpId: this.rpId.toJSON(), + userHandle: this.userHandle.toJSON(), + userName: this.userName.toJSON(), + counter: this.counter.toJSON(), + rpName: this.rpName?.toJSON(), + userDisplayName: this.userDisplayName?.toJSON(), + discoverable: this.discoverable?.toJSON(), + creationDate: this.creationDate.toISOString(), + }; + } } diff --git a/libs/common/src/vault/models/domain/field.spec.ts b/libs/common/src/vault/models/domain/field.spec.ts index 7dc5556e6cf..c0f9713f7ab 100644 --- a/libs/common/src/vault/models/domain/field.spec.ts +++ b/libs/common/src/vault/models/domain/field.spec.ts @@ -1,6 +1,6 @@ import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptedString, EncString } from "../../../platform/models/domain/enc-string"; -import { FieldType } from "../../enums"; +import { CardLinkedId, FieldType, IdentityLinkedId, LoginLinkedId } from "../../enums"; import { FieldData } from "../../models/data/field.data"; import { Field } from "../../models/domain/field"; @@ -82,4 +82,26 @@ describe("Field", () => { expect(Field.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Field Mapping", () => { + it("should map to SDK Field", () => { + // Test Login LinkedId + const loginField = new Field(data); + loginField.type = FieldType.Linked; + loginField.linkedId = LoginLinkedId.Username; + expect(loginField.toSdkField().linkedId).toBe(100); + + // Test Card LinkedId + const cardField = new Field(data); + cardField.type = FieldType.Linked; + cardField.linkedId = CardLinkedId.Number; + expect(cardField.toSdkField().linkedId).toBe(305); + + // Test Identity LinkedId + const identityField = new Field(data); + identityField.type = FieldType.Linked; + identityField.linkedId = IdentityLinkedId.LicenseNumber; + expect(identityField.toSdkField().linkedId).toBe(415); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/field.ts b/libs/common/src/vault/models/domain/field.ts index c0f08a38bcc..223c9b39163 100644 --- a/libs/common/src/vault/models/domain/field.ts +++ b/libs/common/src/vault/models/domain/field.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Field as SdkField, LinkedIdType as SdkLinkedIdType } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -73,4 +75,19 @@ export class Field extends Domain { value, }); } + + /** + * Maps Field to SDK format. + * + * @returns {SdkField} The SDK field object. + */ + toSdkField(): SdkField { + return { + name: this.name?.toJSON(), + value: this.value?.toJSON(), + type: this.type, + // Safe type cast: client and SDK LinkedIdType enums have identical values + linkedId: this.linkedId as unknown as SdkLinkedIdType, + }; + } } diff --git a/libs/common/src/vault/models/domain/identity.spec.ts b/libs/common/src/vault/models/domain/identity.spec.ts index 3a95138998b..cf296a6ff08 100644 --- a/libs/common/src/vault/models/domain/identity.spec.ts +++ b/libs/common/src/vault/models/domain/identity.spec.ts @@ -184,4 +184,32 @@ describe("Identity", () => { expect(Identity.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkIdentity", () => { + it("returns the correct SDK Identity object", () => { + const identity = new Identity(data); + const sdkIdentity = identity.toSdkIdentity(); + + expect(sdkIdentity).toEqual({ + title: "enctitle", + firstName: "encfirstName", + middleName: "encmiddleName", + lastName: "enclastName", + address1: "encaddress1", + address2: "encaddress2", + address3: "encaddress3", + city: "enccity", + state: "encstate", + postalCode: "encpostalCode", + country: "enccountry", + company: "enccompany", + email: "encemail", + phone: "encphone", + ssn: "encssn", + username: "encusername", + passportNumber: "encpassportNumber", + licenseNumber: "enclicenseNumber", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/identity.ts b/libs/common/src/vault/models/domain/identity.ts index 5d8c20ef2b3..c7756733a66 100644 --- a/libs/common/src/vault/models/domain/identity.ts +++ b/libs/common/src/vault/models/domain/identity.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Identity as SdkIdentity } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -165,4 +167,32 @@ export class Identity extends Domain { licenseNumber, }); } + + /** + * Maps Identity to SDK format. + * + * @returns {SdkIdentity} The SDK identity object. + */ + toSdkIdentity(): SdkIdentity { + return { + title: this.title?.toJSON(), + firstName: this.firstName?.toJSON(), + middleName: this.middleName?.toJSON(), + lastName: this.lastName?.toJSON(), + address1: this.address1?.toJSON(), + address2: this.address2?.toJSON(), + address3: this.address3?.toJSON(), + city: this.city?.toJSON(), + state: this.state?.toJSON(), + postalCode: this.postalCode?.toJSON(), + country: this.country?.toJSON(), + company: this.company?.toJSON(), + email: this.email?.toJSON(), + phone: this.phone?.toJSON(), + ssn: this.ssn?.toJSON(), + username: this.username?.toJSON(), + passportNumber: this.passportNumber?.toJSON(), + licenseNumber: this.licenseNumber?.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/domain/login-uri.spec.ts b/libs/common/src/vault/models/domain/login-uri.spec.ts index 6346f38f0de..a0e6b6d7dc9 100644 --- a/libs/common/src/vault/models/domain/login-uri.spec.ts +++ b/libs/common/src/vault/models/domain/login-uri.spec.ts @@ -1,6 +1,8 @@ import { MockProxy, mock } from "jest-mock-extended"; import { Jsonify } from "type-fest"; +import { UriMatchType } from "@bitwarden/sdk-internal"; + import { mockEnc, mockFromJson } from "../../../../spec"; import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service"; import { UriMatchStrategy } from "../../../models/domain/domain-service"; @@ -118,4 +120,17 @@ describe("LoginUri", () => { expect(LoginUri.fromJSON(null)).toBeNull(); }); }); + + describe("SDK Login Uri Mapping", () => { + it("should map to SDK login uri", () => { + const loginUri = new LoginUri(data); + const sdkLoginUri = loginUri.toSdkLoginUri(); + + expect(sdkLoginUri).toEqual({ + uri: "encUri", + uriChecksum: "encUriChecksum", + match: UriMatchType.Domain, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/login-uri.ts b/libs/common/src/vault/models/domain/login-uri.ts index 883f8c9a616..b3e6fad70dd 100644 --- a/libs/common/src/vault/models/domain/login-uri.ts +++ b/libs/common/src/vault/models/domain/login-uri.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { LoginUri as SdkLoginUri } from "@bitwarden/sdk-internal"; + import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; import Domain from "../../../platform/models/domain/domain-base"; @@ -87,4 +89,17 @@ export class LoginUri extends Domain { uriChecksum, }); } + + /** + * Maps LoginUri to SDK format. + * + * @returns {SdkLoginUri} The SDK login uri object. + */ + toSdkLoginUri(): SdkLoginUri { + return { + uri: this.uri.toJSON(), + uriChecksum: this.uriChecksum.toJSON(), + match: this.match, + }; + } } diff --git a/libs/common/src/vault/models/domain/login.spec.ts b/libs/common/src/vault/models/domain/login.spec.ts index 4f9e4546220..84d12e8131f 100644 --- a/libs/common/src/vault/models/domain/login.spec.ts +++ b/libs/common/src/vault/models/domain/login.spec.ts @@ -202,6 +202,54 @@ describe("Login DTO", () => { expect(Login.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkLogin", () => { + it("should map to SDK login", () => { + const data: LoginData = { + uris: [{ uri: "uri", uriChecksum: "checksum", match: UriMatchStrategy.Domain }], + username: "username", + password: "password", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "123", + autofillOnPageLoad: false, + fido2Credentials: [initializeFido2Credential(new Fido2CredentialData())], + }; + const login = new Login(data); + const sdkLogin = login.toSdkLogin(); + + expect(sdkLogin).toEqual({ + username: "username", + password: "password", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + uris: [ + { + match: 0, + uri: "uri", + uriChecksum: "checksum", + }, + ], + totp: "123", + autofillOnPageLoad: false, + fido2Credentials: [ + { + credentialId: "credentialId", + keyType: "public-key", + keyAlgorithm: "ECDSA", + keyCurve: "P-256", + keyValue: "keyValue", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: "counter", + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: "discoverable", + creationDate: "2023-01-01T12:00:00.000Z", + }, + ], + }); + }); + }); }); type Fido2CredentialLike = Fido2CredentialData | Fido2CredentialView | Fido2CredentialApi; diff --git a/libs/common/src/vault/models/domain/login.ts b/libs/common/src/vault/models/domain/login.ts index b29b42bf3de..1893212bdaa 100644 --- a/libs/common/src/vault/models/domain/login.ts +++ b/libs/common/src/vault/models/domain/login.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Login as SdkLogin } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -144,4 +146,21 @@ export class Login extends Domain { fido2Credentials, }); } + + /** + * Maps Login to SDK format. + * + * @returns {SdkLogin} The SDK login object. + */ + toSdkLogin(): SdkLogin { + return { + uris: this.uris?.map((u) => u.toSdkLoginUri()), + username: this.username?.toJSON(), + password: this.password?.toJSON(), + passwordRevisionDate: this.passwordRevisionDate?.toISOString(), + totp: this.totp?.toJSON(), + autofillOnPageLoad: this.autofillOnPageLoad, + fido2Credentials: this.fido2Credentials?.map((f) => f.toSdkFido2Credential()), + }; + } } diff --git a/libs/common/src/vault/models/domain/password.spec.ts b/libs/common/src/vault/models/domain/password.spec.ts index 614b9639e52..24163cccf36 100644 --- a/libs/common/src/vault/models/domain/password.spec.ts +++ b/libs/common/src/vault/models/domain/password.spec.ts @@ -70,4 +70,17 @@ describe("Password", () => { expect(Password.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkPasswordHistory", () => { + it("returns the correct SDK PasswordHistory object", () => { + const password = new Password(data); + + const sdkPasswordHistory = password.toSdkPasswordHistory(); + + expect(sdkPasswordHistory).toEqual({ + password: "encPassword", + lastUsedDate: new Date("2022-01-31T12:00:00.000Z").toISOString(), + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/password.ts b/libs/common/src/vault/models/domain/password.ts index 8573c224416..b69a61a95a9 100644 --- a/libs/common/src/vault/models/domain/password.ts +++ b/libs/common/src/vault/models/domain/password.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { PasswordHistory } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -57,4 +59,16 @@ export class Password extends Domain { lastUsedDate, }); } + + /** + * Maps Password to SDK format. + * + * @returns {PasswordHistory} The SDK password history object. + */ + toSdkPasswordHistory(): PasswordHistory { + return { + password: this.password.toJSON(), + lastUsedDate: this.lastUsedDate.toISOString(), + }; + } } diff --git a/libs/common/src/vault/models/domain/secure-note.spec.ts b/libs/common/src/vault/models/domain/secure-note.spec.ts index 719cf59f136..ff71e53238d 100644 --- a/libs/common/src/vault/models/domain/secure-note.spec.ts +++ b/libs/common/src/vault/models/domain/secure-note.spec.ts @@ -50,4 +50,17 @@ describe("SecureNote", () => { expect(SecureNote.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkSecureNote", () => { + it("returns the correct SDK SecureNote object", () => { + const secureNote = new SecureNote(); + secureNote.type = SecureNoteType.Generic; + + const sdkSecureNote = secureNote.toSdkSecureNote(); + + expect(sdkSecureNote).toEqual({ + type: 0, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/secure-note.ts b/libs/common/src/vault/models/domain/secure-note.ts index 693ae38d9fb..ac7977b0e46 100644 --- a/libs/common/src/vault/models/domain/secure-note.ts +++ b/libs/common/src/vault/models/domain/secure-note.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SecureNote as SdkSecureNote } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { SecureNoteType } from "../../enums"; @@ -41,4 +43,15 @@ export class SecureNote extends Domain { return Object.assign(new SecureNote(), obj); } + + /** + * Maps Secure note to SDK format. + * + * @returns {SdkSecureNote} The SDK secure note object. + */ + toSdkSecureNote(): SdkSecureNote { + return { + type: this.type, + }; + } } diff --git a/libs/common/src/vault/models/domain/ssh-key.spec.ts b/libs/common/src/vault/models/domain/ssh-key.spec.ts index f56d738fde8..6576d1a41e9 100644 --- a/libs/common/src/vault/models/domain/ssh-key.spec.ts +++ b/libs/common/src/vault/models/domain/ssh-key.spec.ts @@ -64,4 +64,17 @@ describe("Sshkey", () => { expect(SshKey.fromJSON(null)).toBeNull(); }); }); + + describe("toSdkSshKey", () => { + it("returns the correct SDK SshKey object", () => { + const sshKey = new SshKey(data); + const sdkSshKey = sshKey.toSdkSshKey(); + + expect(sdkSshKey).toEqual({ + privateKey: "privateKey", + publicKey: "publicKey", + fingerprint: "keyFingerprint", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts index f32a1a913ca..96a1c9e58de 100644 --- a/libs/common/src/vault/models/domain/ssh-key.ts +++ b/libs/common/src/vault/models/domain/ssh-key.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SshKey as SdkSshKey } from "@bitwarden/sdk-internal"; + import Domain from "../../../platform/models/domain/domain-base"; import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; @@ -70,4 +72,17 @@ export class SshKey extends Domain { keyFingerprint, }); } + + /** + * Maps SSH key to SDK format. + * + * @returns {SdkSshKey} The SDK SSH key object. + */ + toSdkSshKey(): SdkSshKey { + return { + privateKey: this.privateKey.toJSON(), + publicKey: this.publicKey.toJSON(), + fingerprint: this.keyFingerprint.toJSON(), + }; + } } diff --git a/libs/common/src/vault/models/view/attachment.view.spec.ts b/libs/common/src/vault/models/view/attachment.view.spec.ts index 7cb291f2714..8ae836e1265 100644 --- a/libs/common/src/vault/models/view/attachment.view.spec.ts +++ b/libs/common/src/vault/models/view/attachment.view.spec.ts @@ -1,4 +1,7 @@ +import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; + import { mockFromJson } from "../../../../spec"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { AttachmentView } from "./attachment.view"; @@ -15,4 +18,56 @@ describe("AttachmentView", () => { expect(actual.key).toEqual("encKeyB64_fromJSON"); }); + + describe("fromSdkAttachmentView", () => { + it("should return undefined when the input is null", () => { + const result = AttachmentView.fromSdkAttachmentView(null as unknown as any); + expect(result).toBeUndefined(); + }); + + it("should return an AttachmentView from an SdkAttachmentView", () => { + const sdkAttachmentView = { + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: "encKeyB64_fromString", + } as SdkAttachmentView; + + const result = AttachmentView.fromSdkAttachmentView(sdkAttachmentView); + + expect(result).toMatchObject({ + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: null, + encryptedKey: new EncString(sdkAttachmentView.key as string), + }); + }); + }); + + describe("toSdkAttachmentView", () => { + it("should convert AttachmentView to SdkAttachmentView", () => { + const attachmentView = new AttachmentView(); + attachmentView.id = "id"; + attachmentView.url = "url"; + attachmentView.size = "size"; + attachmentView.sizeName = "sizeName"; + attachmentView.fileName = "fileName"; + attachmentView.encryptedKey = new EncString("encKeyB64"); + + const result = attachmentView.toSdkAttachmentView(); + expect(result).toEqual({ + id: "id", + url: "url", + size: "size", + sizeName: "sizeName", + fileName: "fileName", + key: "encKeyB64", + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/attachment.view.ts b/libs/common/src/vault/models/view/attachment.view.ts index 09839ed2fef..2ef4280d97a 100644 --- a/libs/common/src/vault/models/view/attachment.view.ts +++ b/libs/common/src/vault/models/view/attachment.view.ts @@ -2,7 +2,10 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; +import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { Attachment } from "../domain/attachment"; @@ -13,6 +16,10 @@ export class AttachmentView implements View { sizeName: string = null; fileName: string = null; key: SymmetricCryptoKey = null; + /** + * The SDK returns an encrypted key for the attachment. + */ + encryptedKey: EncString | undefined; constructor(a?: Attachment) { if (!a) { @@ -40,4 +47,37 @@ export class AttachmentView implements View { const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key); return Object.assign(new AttachmentView(), obj, { key: key }); } + + /** + * Converts the AttachmentView to a SDK AttachmentView. + */ + toSdkAttachmentView(): SdkAttachmentView { + return { + id: this.id, + url: this.url, + size: this.size, + sizeName: this.sizeName, + fileName: this.fileName, + key: this.encryptedKey?.toJSON(), + }; + } + + /** + * Converts the SDK AttachmentView to a AttachmentView. + */ + static fromSdkAttachmentView(obj: SdkAttachmentView): AttachmentView | undefined { + if (!obj) { + return undefined; + } + + const view = new AttachmentView(); + view.id = obj.id ?? null; + view.url = obj.url ?? null; + view.size = obj.size ?? null; + view.sizeName = obj.sizeName ?? null; + view.fileName = obj.fileName ?? null; + view.encryptedKey = new EncString(obj.key); + + return view; + } } diff --git a/libs/common/src/vault/models/view/card.view.ts b/libs/common/src/vault/models/view/card.view.ts index 9eeb4dabf4d..2adfbb39e89 100644 --- a/libs/common/src/vault/models/view/card.view.ts +++ b/libs/common/src/vault/models/view/card.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { CardView as SdkCardView } from "@bitwarden/sdk-internal"; + import { normalizeExpiryYearFormat } from "../../../autofill/utils"; import { CardLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; @@ -146,4 +148,15 @@ export class CardView extends ItemView { return null; } + + /** + * Converts an SDK CardView to a CardView. + */ + static fromSdkCardView(obj: SdkCardView): CardView | undefined { + if (obj == null) { + return undefined; + } + + return Object.assign(new CardView(), obj); + } } diff --git a/libs/common/src/vault/models/view/cipher.view.spec.ts b/libs/common/src/vault/models/view/cipher.view.spec.ts index 3ab2706d356..b9d3e42aa62 100644 --- a/libs/common/src/vault/models/view/cipher.view.spec.ts +++ b/libs/common/src/vault/models/view/cipher.view.spec.ts @@ -1,4 +1,16 @@ -import { mockFromJson } from "../../../../spec"; +import { + CipherView as SdkCipherView, + CipherType as SdkCipherType, + CipherRepromptType as SdkCipherRepromptType, + AttachmentView as SdkAttachmentView, + LoginUriView as SdkLoginUriView, + LoginView as SdkLoginView, + FieldView as SdkFieldView, + FieldType as SdkFieldType, +} from "@bitwarden/sdk-internal"; + +import { mockFromJson, mockFromSdk } from "../../../../spec"; +import { CipherRepromptType } from "../../enums"; import { CipherType } from "../../enums/cipher-type"; import { AttachmentView } from "./attachment.view"; @@ -9,6 +21,7 @@ import { IdentityView } from "./identity.view"; import { LoginView } from "./login.view"; import { PasswordHistoryView } from "./password-history.view"; import { SecureNoteView } from "./secure-note.view"; +import { SshKeyView } from "./ssh-key.view"; jest.mock("../../models/view/login.view"); jest.mock("../../models/view/attachment.view"); @@ -73,4 +86,121 @@ describe("CipherView", () => { expect(actual).toMatchObject(expected); }); }); + + describe("fromSdkCipherView", () => { + let sdkCipherView: SdkCipherView; + + beforeEach(() => { + jest.spyOn(CardView, "fromSdkCardView").mockImplementation(mockFromSdk); + jest.spyOn(IdentityView, "fromSdkIdentityView").mockImplementation(mockFromSdk); + jest.spyOn(LoginView, "fromSdkLoginView").mockImplementation(mockFromSdk); + jest.spyOn(SecureNoteView, "fromSdkSecureNoteView").mockImplementation(mockFromSdk); + jest.spyOn(SshKeyView, "fromSdkSshKeyView").mockImplementation(mockFromSdk); + jest.spyOn(AttachmentView, "fromSdkAttachmentView").mockImplementation(mockFromSdk); + jest.spyOn(FieldView, "fromSdkFieldView").mockImplementation(mockFromSdk); + + sdkCipherView = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: ["collectionId"], + key: undefined, + name: "name", + notes: undefined, + type: SdkCipherType.Login, + favorite: true, + edit: true, + reprompt: SdkCipherRepromptType.None, + organizationUseTotp: false, + viewPassword: true, + localData: undefined, + permissions: undefined, + attachments: [{ id: "attachmentId", url: "attachmentUrl" } as SdkAttachmentView], + login: { + username: "username", + password: "password", + uris: [{ uri: "bitwarden.com" } as SdkLoginUriView], + totp: "totp", + autofillOnPageLoad: true, + } as SdkLoginView, + identity: undefined, + card: undefined, + secureNote: undefined, + sshKey: undefined, + fields: [ + { + name: "fieldName", + value: "fieldValue", + type: SdkFieldType.Linked, + linkedId: 100, + } as SdkFieldView, + ], + passwordHistory: undefined, + creationDate: "2022-01-01T12:00:00.000Z", + revisionDate: "2022-01-02T12:00:00.000Z", + deletedDate: undefined, + }; + }); + + it("returns undefined when input is null", () => { + expect(CipherView.fromSdkCipherView(null as unknown as SdkCipherView)).toBeUndefined(); + }); + + it("maps properties correctly", () => { + const result = CipherView.fromSdkCipherView(sdkCipherView); + + expect(result).toMatchObject({ + id: "id", + organizationId: "orgId", + folderId: "folderId", + collectionIds: ["collectionId"], + name: "name", + notes: null, + type: CipherType.Login, + favorite: true, + edit: true, + reprompt: CipherRepromptType.None, + organizationUseTotp: false, + viewPassword: true, + localData: undefined, + permissions: undefined, + attachments: [ + { + id: "attachmentId", + url: "attachmentUrl", + __fromSdk: true, + }, + ], + login: { + username: "username", + password: "password", + uris: [ + { + uri: "bitwarden.com", + }, + ], + totp: "totp", + autofillOnPageLoad: true, + __fromSdk: true, + }, + identity: new IdentityView(), + card: new CardView(), + secureNote: new SecureNoteView(), + sshKey: new SshKeyView(), + fields: [ + { + name: "fieldName", + value: "fieldValue", + type: SdkFieldType.Linked, + linkedId: 100, + __fromSdk: true, + }, + ], + passwordHistory: null, + creationDate: new Date("2022-01-01T12:00:00.000Z"), + revisionDate: new Date("2022-01-02T12:00:00.000Z"), + deletedDate: null, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index 7ddba9e2ed5..1f73903a5bc 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CipherView as SdkCipherView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; @@ -110,7 +112,7 @@ export class CipherView implements View, InitializerMetadata { get hasOldAttachments(): boolean { if (this.hasAttachments) { for (let i = 0; i < this.attachments.length; i++) { - if (this.attachments[i].key == null) { + if (this.attachments[i].key == null && this.attachments[i].encryptedKey == null) { return true; } } @@ -222,4 +224,68 @@ export class CipherView implements View, InitializerMetadata { return view; } + + /** + * Creates a CipherView from the SDK CipherView. + */ + static fromSdkCipherView(obj: SdkCipherView): CipherView | undefined { + if (obj == null) { + return undefined; + } + + const cipherView = new CipherView(); + cipherView.id = obj.id ?? null; + cipherView.organizationId = obj.organizationId ?? null; + cipherView.folderId = obj.folderId ?? null; + cipherView.name = obj.name; + cipherView.notes = obj.notes ?? null; + cipherView.type = obj.type; + cipherView.favorite = obj.favorite; + cipherView.organizationUseTotp = obj.organizationUseTotp; + cipherView.permissions = CipherPermissionsApi.fromSdkCipherPermissions(obj.permissions); + cipherView.edit = obj.edit; + cipherView.viewPassword = obj.viewPassword; + cipherView.localData = obj.localData + ? { + lastUsedDate: obj.localData.lastUsedDate + ? new Date(obj.localData.lastUsedDate).getTime() + : undefined, + lastLaunched: obj.localData.lastLaunched + ? new Date(obj.localData.lastLaunched).getTime() + : undefined, + } + : undefined; + cipherView.attachments = + obj.attachments?.map((a) => AttachmentView.fromSdkAttachmentView(a)) ?? null; + cipherView.fields = obj.fields?.map((f) => FieldView.fromSdkFieldView(f)) ?? null; + cipherView.passwordHistory = + obj.passwordHistory?.map((ph) => PasswordHistoryView.fromSdkPasswordHistoryView(ph)) ?? null; + cipherView.collectionIds = obj.collectionIds ?? null; + cipherView.revisionDate = obj.revisionDate == null ? null : new Date(obj.revisionDate); + cipherView.creationDate = obj.creationDate == null ? null : new Date(obj.creationDate); + cipherView.deletedDate = obj.deletedDate == null ? null : new Date(obj.deletedDate); + cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; + + switch (obj.type) { + case CipherType.Card: + cipherView.card = CardView.fromSdkCardView(obj.card); + break; + case CipherType.Identity: + cipherView.identity = IdentityView.fromSdkIdentityView(obj.identity); + break; + case CipherType.Login: + cipherView.login = LoginView.fromSdkLoginView(obj.login); + break; + case CipherType.SecureNote: + cipherView.secureNote = SecureNoteView.fromSdkSecureNoteView(obj.secureNote); + break; + case CipherType.SshKey: + cipherView.sshKey = SshKeyView.fromSdkSshKeyView(obj.sshKey); + break; + default: + break; + } + + return cipherView; + } } diff --git a/libs/common/src/vault/models/view/fido2-credential.view.ts b/libs/common/src/vault/models/view/fido2-credential.view.ts index b364d63b8ea..bf1d324d22d 100644 --- a/libs/common/src/vault/models/view/fido2-credential.view.ts +++ b/libs/common/src/vault/models/view/fido2-credential.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { Fido2CredentialView as SdkFido2CredentialView } from "@bitwarden/sdk-internal"; + import { ItemView } from "./item.view"; export class Fido2CredentialView extends ItemView { @@ -29,4 +31,29 @@ export class Fido2CredentialView extends ItemView { creationDate, }); } + + /** + * Converts the SDK Fido2CredentialView to a Fido2CredentialView. + */ + static fromSdkFido2CredentialView(obj: SdkFido2CredentialView): Fido2CredentialView | undefined { + if (!obj) { + return undefined; + } + + const view = new Fido2CredentialView(); + view.credentialId = obj.credentialId; + view.keyType = obj.keyType as "public-key"; + view.keyAlgorithm = obj.keyAlgorithm as "ECDSA"; + view.keyCurve = obj.keyCurve as "P-256"; + view.rpId = obj.rpId; + view.userHandle = obj.userHandle; + view.userName = obj.userName; + view.counter = parseInt(obj.counter); + view.rpName = obj.rpName; + view.userDisplayName = obj.userDisplayName; + view.discoverable = obj.discoverable?.toLowerCase() === "true" ? true : false; + view.creationDate = obj.creationDate ? new Date(obj.creationDate) : null; + + return view; + } } diff --git a/libs/common/src/vault/models/view/field.view.ts b/libs/common/src/vault/models/view/field.view.ts index ef8c5113fd0..770150f8a63 100644 --- a/libs/common/src/vault/models/view/field.view.ts +++ b/libs/common/src/vault/models/view/field.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { FieldView as SdkFieldView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { FieldType, LinkedIdType } from "../../enums"; import { Field } from "../domain/field"; @@ -31,4 +33,21 @@ export class FieldView implements View { static fromJSON(obj: Partial>): FieldView { return Object.assign(new FieldView(), obj); } + + /** + * Converts the SDK FieldView to a FieldView. + */ + static fromSdkFieldView(obj: SdkFieldView): FieldView | undefined { + if (!obj) { + return undefined; + } + + const view = new FieldView(); + view.name = obj.name; + view.value = obj.value; + view.type = obj.type; + view.linkedId = obj.linkedId as unknown as LinkedIdType; + + return view; + } } diff --git a/libs/common/src/vault/models/view/identity.view.ts b/libs/common/src/vault/models/view/identity.view.ts index 247e5cfec86..a75d11efd95 100644 --- a/libs/common/src/vault/models/view/identity.view.ts +++ b/libs/common/src/vault/models/view/identity.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { IdentityView as SdkIdentityView } from "@bitwarden/sdk-internal"; + import { Utils } from "../../../platform/misc/utils"; import { IdentityLinkedId as LinkedId } from "../../enums"; import { linkedFieldOption } from "../../linked-field-option.decorator"; @@ -158,4 +160,15 @@ export class IdentityView extends ItemView { static fromJSON(obj: Partial>): IdentityView { return Object.assign(new IdentityView(), obj); } + + /** + * Converts the SDK IdentityView to an IdentityView. + */ + static fromSdkIdentityView(obj: SdkIdentityView): IdentityView | undefined { + if (obj == null) { + return undefined; + } + + return Object.assign(new IdentityView(), obj); + } } diff --git a/libs/common/src/vault/models/view/login-uri-view.spec.ts b/libs/common/src/vault/models/view/login-uri-view.spec.ts index efc75096295..155d3d59f7c 100644 --- a/libs/common/src/vault/models/view/login-uri-view.spec.ts +++ b/libs/common/src/vault/models/view/login-uri-view.spec.ts @@ -1,3 +1,5 @@ +import { LoginUriView as SdkLoginUriView, UriMatchType } from "@bitwarden/sdk-internal"; + import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; @@ -184,6 +186,26 @@ describe("LoginUriView", () => { }); }); }); + + describe("fromSdkLoginUriView", () => { + it("should return undefined when the input is null", () => { + const result = LoginUriView.fromSdkLoginUriView(null as unknown as SdkLoginUriView); + expect(result).toBeUndefined(); + }); + + it("should create a LoginUriView from a SdkLoginUriView", () => { + const sdkLoginUriView = { + uri: "https://example.com", + match: UriMatchType.Host, + } as SdkLoginUriView; + + const loginUriView = LoginUriView.fromSdkLoginUriView(sdkLoginUriView); + + expect(loginUriView).toBeInstanceOf(LoginUriView); + expect(loginUriView!.uri).toBe(sdkLoginUriView.uri); + expect(loginUriView!.match).toBe(sdkLoginUriView.match); + }); + }); }); function uriFactory(match: UriMatchStrategySetting, uri: string) { diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index 315adb87c75..43d47aa4a3c 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { LoginUriView as SdkLoginUriView } from "@bitwarden/sdk-internal"; + import { UriMatchStrategy, UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { View } from "../../../models/view/view"; import { SafeUrls } from "../../../platform/misc/safe-urls"; @@ -112,6 +114,21 @@ export class LoginUriView implements View { return Object.assign(new LoginUriView(), obj); } + /** + * Converts a LoginUriView object from the SDK to a LoginUriView object. + */ + static fromSdkLoginUriView(obj: SdkLoginUriView): LoginUriView | undefined { + if (obj == null) { + return undefined; + } + + const view = new LoginUriView(); + view.uri = obj.uri; + view.match = obj.match; + + return view; + } + matchesUri( targetUri: string, equivalentDomains: Set, diff --git a/libs/common/src/vault/models/view/login.view.spec.ts b/libs/common/src/vault/models/view/login.view.spec.ts index 728a62deb9d..57e82faf7f1 100644 --- a/libs/common/src/vault/models/view/login.view.spec.ts +++ b/libs/common/src/vault/models/view/login.view.spec.ts @@ -1,4 +1,6 @@ -import { mockFromJson } from "../../../../spec"; +import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; + +import { mockFromJson, mockFromSdk } from "../../../../spec"; import { LoginUriView } from "./login-uri.view"; import { LoginView } from "./login.view"; @@ -25,4 +27,35 @@ describe("LoginView", () => { uris: ["uri1_fromJSON", "uri2_fromJSON", "uri3_fromJSON"], }); }); + + describe("fromSdkLoginView", () => { + it("should return undefined when the input is null", () => { + const result = LoginView.fromSdkLoginView(null as unknown as SdkLoginView); + expect(result).toBeUndefined(); + }); + + it("should return a LoginView from an SdkLoginView", () => { + jest.spyOn(LoginUriView, "fromSdkLoginUriView").mockImplementation(mockFromSdk); + + const sdkLoginView = { + username: "username", + password: "password", + passwordRevisionDate: "2025-01-01T01:06:40.441Z", + uris: [{ uri: "bitwarden.com" } as any], + totp: "totp", + autofillOnPageLoad: true, + } as SdkLoginView; + + const result = LoginView.fromSdkLoginView(sdkLoginView); + + expect(result).toMatchObject({ + username: "username", + password: "password", + passwordRevisionDate: new Date("2025-01-01T01:06:40.441Z"), + uris: [expect.objectContaining({ uri: "bitwarden.com", __fromSdk: true })], + totp: "totp", + autofillOnPageLoad: true, + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/login.view.ts b/libs/common/src/vault/models/view/login.view.ts index 228f3a60c34..41568f643d5 100644 --- a/libs/common/src/vault/models/view/login.view.ts +++ b/libs/common/src/vault/models/view/login.view.ts @@ -1,5 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { LoginView as SdkLoginView } from "@bitwarden/sdk-internal"; + import { UriMatchStrategySetting } from "../../../models/domain/domain-service"; import { Utils } from "../../../platform/misc/utils"; import { DeepJsonify } from "../../../types/deep-jsonify"; @@ -100,4 +102,27 @@ export class LoginView extends ItemView { fido2Credentials, }); } + + /** + * Converts the SDK LoginView to a LoginView. + * + * Note: FIDO2 credentials remain encrypted at this stage. + * Unlike other fields that are decrypted as part of the LoginView, the SDK maintains + * the FIDO2 credentials in encrypted form. We can decrypt them later using a separate + * call to client.vault().ciphers().decrypt_fido2_credentials(). + */ + static fromSdkLoginView(obj: SdkLoginView): LoginView | undefined { + if (obj == null) { + return undefined; + } + + const passwordRevisionDate = + obj.passwordRevisionDate == null ? null : new Date(obj.passwordRevisionDate); + const uris = obj.uris?.map((uri) => LoginUriView.fromSdkLoginUriView(uri)); + + return Object.assign(new LoginView(), obj, { + passwordRevisionDate, + uris, + }); + } } diff --git a/libs/common/src/vault/models/view/password-history.view.spec.ts b/libs/common/src/vault/models/view/password-history.view.spec.ts index 7349e44454d..81894ec7493 100644 --- a/libs/common/src/vault/models/view/password-history.view.spec.ts +++ b/libs/common/src/vault/models/view/password-history.view.spec.ts @@ -1,3 +1,5 @@ +import { PasswordHistoryView as SdkPasswordHistoryView } from "@bitwarden/sdk-internal"; + import { PasswordHistoryView } from "./password-history.view"; describe("PasswordHistoryView", () => { @@ -10,4 +12,25 @@ describe("PasswordHistoryView", () => { expect(actual.lastUsedDate).toEqual(lastUsedDate); }); + + describe("fromSdkPasswordHistoryView", () => { + it("should return undefined when the input is null", () => { + const result = PasswordHistoryView.fromSdkPasswordHistoryView(null as unknown as any); + expect(result).toBeUndefined(); + }); + + it("should return a PasswordHistoryView from an SdkPasswordHistoryView", () => { + const sdkPasswordHistoryView = { + password: "password", + lastUsedDate: "2023-10-01T00:00:00Z", + } as SdkPasswordHistoryView; + + const result = PasswordHistoryView.fromSdkPasswordHistoryView(sdkPasswordHistoryView); + + expect(result).toMatchObject({ + password: "password", + lastUsedDate: new Date("2023-10-01T00:00:00Z"), + }); + }); + }); }); diff --git a/libs/common/src/vault/models/view/password-history.view.ts b/libs/common/src/vault/models/view/password-history.view.ts index 3ab360d5e09..31f05f4cc71 100644 --- a/libs/common/src/vault/models/view/password-history.view.ts +++ b/libs/common/src/vault/models/view/password-history.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { PasswordHistoryView as SdkPasswordHistoryView } from "@bitwarden/sdk-internal"; + import { View } from "../../../models/view/view"; import { Password } from "../domain/password"; @@ -24,4 +26,19 @@ export class PasswordHistoryView implements View { lastUsedDate: lastUsedDate, }); } + + /** + * Converts the SDK PasswordHistoryView to a PasswordHistoryView. + */ + static fromSdkPasswordHistoryView(obj: SdkPasswordHistoryView): PasswordHistoryView | undefined { + if (!obj) { + return undefined; + } + + const view = new PasswordHistoryView(); + view.password = obj.password; + view.lastUsedDate = obj.lastUsedDate == null ? null : new Date(obj.lastUsedDate); + + return view; + } } diff --git a/libs/common/src/vault/models/view/secure-note.view.ts b/libs/common/src/vault/models/view/secure-note.view.ts index c7dd4f8932d..075e4dfc520 100644 --- a/libs/common/src/vault/models/view/secure-note.view.ts +++ b/libs/common/src/vault/models/view/secure-note.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SecureNoteView as SdkSecureNoteView } from "@bitwarden/sdk-internal"; + import { SecureNoteType } from "../../enums"; import { SecureNote } from "../domain/secure-note"; @@ -26,4 +28,15 @@ export class SecureNoteView extends ItemView { static fromJSON(obj: Partial>): SecureNoteView { return Object.assign(new SecureNoteView(), obj); } + + /** + * Converts the SDK SecureNoteView to a SecureNoteView. + */ + static fromSdkSecureNoteView(obj: SdkSecureNoteView): SecureNoteView | undefined { + if (!obj) { + return undefined; + } + + return Object.assign(new SecureNoteView(), obj); + } } diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts index 8f1a9c5a65a..a3d091e4c07 100644 --- a/libs/common/src/vault/models/view/ssh-key.view.ts +++ b/libs/common/src/vault/models/view/ssh-key.view.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { Jsonify } from "type-fest"; +import { SshKeyView as SdkSshKeyView } from "@bitwarden/sdk-internal"; + import { SshKey } from "../domain/ssh-key"; import { ItemView } from "./item.view"; @@ -44,4 +46,19 @@ export class SshKeyView extends ItemView { static fromJSON(obj: Partial>): SshKeyView { return Object.assign(new SshKeyView(), obj); } + + /** + * Converts the SDK SshKeyView to a SshKeyView. + */ + static fromSdkSshKeyView(obj: SdkSshKeyView): SshKeyView | undefined { + if (!obj) { + return undefined; + } + + const keyFingerprint = obj.fingerprint; + + return Object.assign(new SshKeyView(), obj, { + keyFingerprint, + }); + } } diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index a8b37e8adc6..b15bc4a9112 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -6,7 +6,7 @@ import { CipherDecryptionKeys, KeyService } from "@bitwarden/key-management"; import { FakeAccountService, mockAccountServiceWith } from "../../../spec/fake-account-service"; import { FakeStateProvider } from "../../../spec/fake-state-provider"; -import { makeStaticByteArray } from "../../../spec/utils"; +import { makeStaticByteArray, makeSymmetricCryptoKey } from "../../../spec/utils"; import { ApiService } from "../../abstractions/api.service"; import { SearchService } from "../../abstractions/search.service"; import { AutofillSettingsService } from "../../autofill/services/autofill-settings.service"; @@ -24,6 +24,7 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { ContainerService } from "../../platform/services/container.service"; import { CipherId, UserId } from "../../types/guid"; import { CipherKey, OrgKey, UserKey } from "../../types/key"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; import { CipherRepromptType } from "../enums/cipher-reprompt-type"; @@ -34,6 +35,7 @@ import { Cipher } from "../models/domain/cipher"; import { CipherCreateRequest } from "../models/request/cipher-create.request"; import { CipherPartialRequest } from "../models/request/cipher-partial.request"; import { CipherRequest } from "../models/request/cipher.request"; +import { AttachmentView } from "../models/view/attachment.view"; import { CipherView } from "../models/view/cipher.view"; import { LoginUriView } from "../models/view/login-uri.view"; @@ -124,6 +126,7 @@ describe("Cipher Service", () => { accountService = mockAccountServiceWith(mockUserId); const logService = mock(); const stateProvider = new FakeStateProvider(accountService); + const cipherEncryptionService = mock(); const userId = "TestUserId" as UserId; @@ -151,6 +154,7 @@ describe("Cipher Service", () => { stateProvider, accountService, logService, + cipherEncryptionService, ); cipherObj = new Cipher(cipherData); @@ -478,4 +482,85 @@ describe("Cipher Service", () => { ).rejects.toThrow("Cannot rotate ciphers when decryption failures are present"); }); }); + + describe("decrypt", () => { + it("should call decrypt method of CipherEncryptionService when feature flag is true", async () => { + configService.getFeatureFlag.mockResolvedValue(true); + cipherEncryptionService.decrypt.mockResolvedValue(new CipherView(cipherObj)); + + const result = await cipherService.decrypt(cipherObj, userId); + + expect(result).toEqual(new CipherView(cipherObj)); + expect(cipherEncryptionService.decrypt).toHaveBeenCalledWith(cipherObj, userId); + }); + + it("should call legacy decrypt when feature flag is false", async () => { + const mockUserKey = new SymmetricCryptoKey(new Uint8Array(32)) as UserKey; + configService.getFeatureFlag.mockResolvedValue(false); + cipherService.getKeyForCipherKeyDecryption = jest.fn().mockResolvedValue(mockUserKey); + encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); + jest.spyOn(cipherObj, "decrypt").mockResolvedValue(new CipherView(cipherObj)); + + const result = await cipherService.decrypt(cipherObj, userId); + + expect(result).toEqual(new CipherView(cipherObj)); + expect(cipherObj.decrypt).toHaveBeenCalledWith(mockUserKey); + }); + }); + + describe("getDecryptedAttachmentBuffer", () => { + const mockEncryptedContent = new Uint8Array([1, 2, 3]); + const mockDecryptedContent = new Uint8Array([4, 5, 6]); + + it("should use SDK when feature flag is enabled", async () => { + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + configService.getFeatureFlag.mockResolvedValue(true); + + jest.spyOn(cipherService, "ciphers$").mockReturnValue(of({ [cipher.id]: cipherData })); + cipherEncryptionService.decryptAttachmentContent.mockResolvedValue(mockDecryptedContent); + const mockResponse = { + arrayBuffer: jest.fn().mockResolvedValue(mockEncryptedContent.buffer), + } as unknown as Response; + + const result = await cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + mockResponse, + userId, + ); + + expect(result).toEqual(mockDecryptedContent); + expect(cipherEncryptionService.decryptAttachmentContent).toHaveBeenCalledWith( + cipher, + attachment, + mockEncryptedContent, + userId, + ); + }); + + it("should use legacy decryption when feature flag is enabled", async () => { + configService.getFeatureFlag.mockResolvedValue(false); + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + attachment.key = makeSymmetricCryptoKey(64); + + const mockResponse = { + arrayBuffer: jest.fn().mockResolvedValue(mockEncryptedContent.buffer), + } as unknown as Response; + const mockEncBuf = {} as EncArrayBuffer; + EncArrayBuffer.fromResponse = jest.fn().mockResolvedValue(mockEncBuf); + encryptService.decryptFileData.mockResolvedValue(mockDecryptedContent); + + const result = await cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + mockResponse, + userId, + ); + + expect(result).toEqual(mockDecryptedContent); + expect(encryptService.decryptFileData).toHaveBeenCalledWith(mockEncBuf, attachment.key); + }); + }); }); diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index 169568d44e9..6bea56baa5e 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -29,7 +29,8 @@ import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypt import { StateProvider } from "../../platform/state"; import { CipherId, CollectionId, OrganizationId, UserId } from "../../types/guid"; import { OrgKey, UserKey } from "../../types/key"; -import { perUserCache$ } from "../../vault/utils/observable-utilities"; +import { filterOutNullish, perUserCache$ } from "../../vault/utils/observable-utilities"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; import { CipherService as CipherServiceAbstraction } from "../abstractions/cipher.service"; import { CipherFileUploadService } from "../abstractions/file-upload/cipher-file-upload.service"; import { FieldType } from "../enums"; @@ -103,6 +104,7 @@ export class CipherService implements CipherServiceAbstraction { private stateProvider: StateProvider, private accountService: AccountService, private logService: LogService, + private cipherEncryptionService: CipherEncryptionService, ) {} localData$(userId: UserId): Observable> { @@ -424,13 +426,21 @@ export class CipherService implements CipherServiceAbstraction { ciphers: Cipher[], userId: UserId, ): Promise<[CipherView[], CipherView[]] | null> { - const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true)); + if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) { + const decryptStartTime = new Date().getTime(); + const decrypted = await this.decryptCiphersWithSdk(ciphers, userId); + this.logService.info( + `[CipherService] Decrypting ${decrypted.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`, + ); + // With SDK, failed ciphers are not returned + return [decrypted, []]; + } + const keys = await firstValueFrom(this.keyService.cipherDecryptionKeys$(userId, true)); if (keys == null || (keys.userKey == null && Object.keys(keys.orgKeys).length === 0)) { // return early if there are no keys to decrypt with return null; } - // Group ciphers by orgId or under 'null' for the user's ciphers const grouped = ciphers.reduce( (agg, c) => { @@ -440,7 +450,6 @@ export class CipherService implements CipherServiceAbstraction { }, {} as Record, ); - const decryptStartTime = new Date().getTime(); const allCipherViews = ( await Promise.all( @@ -464,7 +473,6 @@ export class CipherService implements CipherServiceAbstraction { this.logService.info( `[CipherService] Decrypting ${allCipherViews.length} ciphers took ${new Date().getTime() - decryptStartTime}ms`, ); - // Split ciphers into two arrays, one for successfully decrypted ciphers and one for ciphers that failed to decrypt return allCipherViews.reduce( (acc, c) => { @@ -479,6 +487,21 @@ export class CipherService implements CipherServiceAbstraction { ); } + /** + * Decrypts a cipher using either the SDK or the legacy method based on the feature flag. + * @param cipher The cipher to decrypt. + * @param userId The user ID to use for decryption. + * @returns A promise that resolves to the decrypted cipher view. + */ + async decrypt(cipher: Cipher, userId: UserId): Promise { + if (await this.configService.getFeatureFlag(FeatureFlag.PM19941MigrateCipherDomainToSdk)) { + return await this.cipherEncryptionService.decrypt(cipher, userId); + } else { + const encKey = await this.getKeyForCipherKeyDecryption(cipher, userId); + return await cipher.decrypt(encKey); + } + } + private async reindexCiphers(userId: UserId) { const reindexRequired = this.searchService != null && @@ -895,7 +918,7 @@ export class CipherService implements CipherServiceAbstraction { //then we rollback to using the user key as the main key of encryption of the item //in order to keep item and it's attachments with the same encryption level if (cipher.key != null && !cipherKeyEncryptionEnabled) { - const model = await cipher.decrypt(await this.getKeyForCipherKeyDecryption(cipher, userId)); + const model = await this.decrypt(cipher, userId); cipher = await this.encrypt(model, userId); await this.updateWithServer(cipher); } @@ -1381,6 +1404,43 @@ export class CipherService implements CipherServiceAbstraction { return encryptedCiphers; } + async getDecryptedAttachmentBuffer( + cipherId: CipherId, + attachment: AttachmentView, + response: Response, + userId: UserId, + ): Promise { + const useSdkDecryption = await this.configService.getFeatureFlag( + FeatureFlag.PM19941MigrateCipherDomainToSdk, + ); + + const cipherDomain = await firstValueFrom( + this.ciphers$(userId).pipe(map((ciphersData) => new Cipher(ciphersData[cipherId]))), + ); + + if (useSdkDecryption) { + const encryptedContent = await response.arrayBuffer(); + return this.cipherEncryptionService.decryptAttachmentContent( + cipherDomain, + attachment, + new Uint8Array(encryptedContent), + userId, + ); + } + + const encBuf = await EncArrayBuffer.fromResponse(response); + const key = + attachment.key != null + ? attachment.key + : await firstValueFrom( + this.keyService.orgKeys$(userId).pipe( + filterOutNullish(), + map((orgKeys) => orgKeys[cipherDomain.organizationId as OrganizationId] as OrgKey), + ), + ); + return await this.encryptService.decryptFileData(encBuf, key); + } + /** * @returns a SingleUserState */ @@ -1430,9 +1490,7 @@ export class CipherService implements CipherServiceAbstraction { originalCipher: Cipher, userId: UserId, ): Promise { - const existingCipher = await originalCipher.decrypt( - await this.getKeyForCipherKeyDecryption(originalCipher, userId), - ); + const existingCipher = await this.decrypt(originalCipher, userId); model.passwordHistory = existingCipher.passwordHistory || []; if (model.type === CipherType.Login && existingCipher.type === CipherType.Login) { if ( @@ -1852,4 +1910,17 @@ export class CipherService implements CipherServiceAbstraction { ); return featureEnabled && meetsServerVersion; } + + /** + * Decrypts the provided ciphers using the SDK. + * @param ciphers The ciphers to decrypt. + * @param userId The user ID to use for decryption. + * @returns The decrypted ciphers. + * @private + */ + private async decryptCiphersWithSdk(ciphers: Cipher[], userId: UserId): Promise { + const decryptedViews = await this.cipherEncryptionService.decryptManyLegacy(ciphers, userId); + + return decryptedViews.sort(this.getLocaleSortingFunction()); + } } diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts new file mode 100644 index 00000000000..c0b3d3be85f --- /dev/null +++ b/libs/common/src/vault/services/default-cipher-encryption.service.spec.ts @@ -0,0 +1,334 @@ +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { + Fido2Credential, + Cipher as SdkCipher, + CipherType as SdkCipherType, + CipherView as SdkCipherView, + CipherListView, + Attachment as SdkAttachment, +} from "@bitwarden/sdk-internal"; + +import { mockEnc } from "../../../spec"; +import { UriMatchStrategy } from "../../models/domain/domain-service"; +import { LogService } from "../../platform/abstractions/log.service"; +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { UserId } from "../../types/guid"; +import { CipherRepromptType, CipherType } from "../enums"; +import { CipherPermissionsApi } from "../models/api/cipher-permissions.api"; +import { CipherData } from "../models/data/cipher.data"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; +import { Fido2CredentialView } from "../models/view/fido2-credential.view"; + +import { DefaultCipherEncryptionService } from "./default-cipher-encryption.service"; + +const cipherData: CipherData = { + id: "id", + organizationId: "orgId", + folderId: "folderId", + edit: true, + viewPassword: true, + organizationUseTotp: true, + favorite: false, + revisionDate: "2022-01-31T12:00:00.000Z", + type: CipherType.Login, + name: "EncryptedString", + notes: "EncryptedString", + creationDate: "2022-01-01T12:00:00.000Z", + deletedDate: null, + permissions: new CipherPermissionsApi(), + key: "EncKey", + reprompt: CipherRepromptType.None, + login: { + uris: [ + { uri: "EncryptedString", uriChecksum: "EncryptedString", match: UriMatchStrategy.Domain }, + ], + username: "EncryptedString", + password: "EncryptedString", + passwordRevisionDate: "2022-01-31T12:00:00.000Z", + totp: "EncryptedString", + autofillOnPageLoad: false, + }, + passwordHistory: [{ password: "EncryptedString", lastUsedDate: "2022-01-31T12:00:00.000Z" }], + attachments: [ + { + id: "a1", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + { + id: "a2", + url: "url", + size: "1100", + sizeName: "1.1 KB", + fileName: "file", + key: "EncKey", + }, + ], +}; + +describe("DefaultCipherEncryptionService", () => { + let cipherEncryptionService: DefaultCipherEncryptionService; + const sdkService = mock(); + const logService = mock(); + let sdkCipherView: SdkCipherView; + + const mockSdkClient = { + vault: jest.fn().mockReturnValue({ + ciphers: jest.fn().mockReturnValue({ + decrypt: jest.fn(), + decrypt_list: jest.fn(), + decrypt_fido2_credentials: jest.fn(), + }), + attachments: jest.fn().mockReturnValue({ + decrypt_buffer: jest.fn(), + }), + }), + }; + const mockRef = { + value: mockSdkClient, + [Symbol.dispose]: jest.fn(), + }; + const mockSdk = { + take: jest.fn().mockReturnValue(mockRef), + }; + + const userId = "user-id" as UserId; + + let cipherObj: Cipher; + + beforeEach(() => { + sdkService.userClient$ = jest.fn((userId: UserId) => of(mockSdk)) as any; + cipherEncryptionService = new DefaultCipherEncryptionService(sdkService, logService); + cipherObj = new Cipher(cipherData); + + jest.spyOn(cipherObj, "toSdkCipher").mockImplementation(() => { + return { id: cipherData.id } as SdkCipher; + }); + + sdkCipherView = { + id: "test-id", + type: SdkCipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + }, + } as SdkCipherView; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("decrypt", () => { + it("should decrypt a cipher successfully", async () => { + const expectedCipherView: CipherView = { + id: "test-id", + type: CipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + }, + } as CipherView; + + mockSdkClient.vault().ciphers().decrypt.mockReturnValue(sdkCipherView); + jest.spyOn(CipherView, "fromSdkCipherView").mockReturnValue(expectedCipherView); + + const result = await cipherEncryptionService.decrypt(cipherObj, userId); + + expect(result).toEqual(expectedCipherView); + expect(cipherObj.toSdkCipher).toHaveBeenCalledTimes(1); + expect(mockSdkClient.vault().ciphers().decrypt).toHaveBeenCalledWith({ id: cipherData.id }); + expect(CipherView.fromSdkCipherView).toHaveBeenCalledWith(sdkCipherView); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_credentials).not.toHaveBeenCalled(); + }); + + it("should decrypt FIDO2 credentials if present", async () => { + const fido2Credentials = [ + { + credentialId: mockEnc("credentialId"), + keyType: mockEnc("keyType"), + keyAlgorithm: mockEnc("keyAlgorithm"), + keyCurve: mockEnc("keyCurve"), + keyValue: mockEnc("keyValue"), + rpId: mockEnc("rpId"), + userHandle: mockEnc("userHandle"), + userName: mockEnc("userName"), + counter: mockEnc("2"), + rpName: mockEnc("rpName"), + userDisplayName: mockEnc("userDisplayName"), + discoverable: mockEnc("true"), + creationDate: new Date("2023-01-01T12:00:00.000Z"), + }, + ] as unknown as Fido2Credential[]; + + sdkCipherView.login!.fido2Credentials = fido2Credentials; + + const expectedCipherView: CipherView = { + id: "test-id", + type: CipherType.Login, + name: "test-name", + login: { + username: "test-username", + password: "test-password", + fido2Credentials: [], + }, + } as unknown as CipherView; + + const fido2CredentialView: Fido2CredentialView = { + credentialId: "credentialId", + keyType: "keyType", + keyAlgorithm: "keyAlgorithm", + keyCurve: "keyCurve", + keyValue: "decrypted-key-value", + rpId: "rpId", + userHandle: "userHandle", + userName: "userName", + counter: 2, + rpName: "rpName", + userDisplayName: "userDisplayName", + discoverable: true, + creationDate: new Date("2023-01-01T12:00:00.000Z"), + } as unknown as Fido2CredentialView; + + mockSdkClient.vault().ciphers().decrypt.mockReturnValue(sdkCipherView); + mockSdkClient.vault().ciphers().decrypt_fido2_credentials.mockReturnValue(fido2Credentials); + mockSdkClient.vault().ciphers().decrypt_fido2_private_key = jest + .fn() + .mockReturnValue("decrypted-key-value"); + + jest.spyOn(CipherView, "fromSdkCipherView").mockReturnValue(expectedCipherView); + jest + .spyOn(Fido2CredentialView, "fromSdkFido2CredentialView") + .mockReturnValueOnce(fido2CredentialView); + + const result = await cipherEncryptionService.decrypt(cipherObj, userId); + + expect(result).toBe(expectedCipherView); + expect(result.login?.fido2Credentials).toEqual([fido2CredentialView]); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_credentials).toHaveBeenCalledWith( + sdkCipherView, + ); + expect(mockSdkClient.vault().ciphers().decrypt_fido2_private_key).toHaveBeenCalledWith( + sdkCipherView, + ); + expect(Fido2CredentialView.fromSdkFido2CredentialView).toHaveBeenCalledTimes(1); + }); + }); + + describe("decryptManyLegacy", () => { + it("should decrypt multiple ciphers successfully", async () => { + const ciphers = [new Cipher(cipherData), new Cipher(cipherData)]; + + const expectedViews = [ + { + id: "test-id-1", + name: "test-name-1", + } as CipherView, + { + id: "test-id-2", + name: "test-name-2", + } as CipherView, + ]; + + mockSdkClient + .vault() + .ciphers() + .decrypt.mockReturnValueOnce({ id: "test-id-1", name: "test-name-1" } as SdkCipherView) + .mockReturnValueOnce({ id: "test-id-2", name: "test-name-2" } as SdkCipherView); + + jest + .spyOn(CipherView, "fromSdkCipherView") + .mockReturnValueOnce(expectedViews[0]) + .mockReturnValueOnce(expectedViews[1]); + + const result = await cipherEncryptionService.decryptManyLegacy(ciphers, userId); + + expect(result).toEqual(expectedViews); + expect(mockSdkClient.vault().ciphers().decrypt).toHaveBeenCalledTimes(2); + expect(CipherView.fromSdkCipherView).toHaveBeenCalledTimes(2); + }); + + it("should throw EmptyError when SDK is not available", async () => { + sdkService.userClient$ = jest.fn().mockReturnValue(of(null)) as any; + + await expect( + cipherEncryptionService.decryptManyLegacy([cipherObj], userId), + ).rejects.toThrow(); + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Failed to decrypt ciphers"), + ); + }); + }); + + describe("decryptMany", () => { + it("should decrypt multiple ciphers to list views", async () => { + const ciphers = [new Cipher(cipherData), new Cipher(cipherData)]; + + const expectedListViews = [ + { id: "list1", name: "List 1" } as CipherListView, + { id: "list2", name: "List 2" } as CipherListView, + ]; + + mockSdkClient.vault().ciphers().decrypt_list.mockReturnValue(expectedListViews); + + const result = await cipherEncryptionService.decryptMany(ciphers, userId); + + expect(result).toEqual(expectedListViews); + expect(mockSdkClient.vault().ciphers().decrypt_list).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ id: cipherData.id }), + expect.objectContaining({ id: cipherData.id }), + ]), + ); + }); + + it("should throw EmptyError when SDK is not available", async () => { + sdkService.userClient$ = jest.fn().mockReturnValue(of(null)) as any; + + await expect(cipherEncryptionService.decryptMany([cipherObj], userId)).rejects.toThrow(); + + expect(logService.error).toHaveBeenCalledWith( + expect.stringContaining("Failed to decrypt cipher list"), + ); + }); + }); + + describe("decryptAttachmentContent", () => { + it("should decrypt attachment content successfully", async () => { + const cipher = new Cipher(cipherData); + const attachment = new AttachmentView(cipher.attachments![0]); + const encryptedContent = new Uint8Array([1, 2, 3, 4]); + const expectedDecryptedContent = new Uint8Array([5, 6, 7, 8]); + + jest.spyOn(cipher, "toSdkCipher").mockReturnValue({ id: "id" } as SdkCipher); + jest.spyOn(attachment, "toSdkAttachmentView").mockReturnValue({ id: "a1" } as SdkAttachment); + mockSdkClient.vault().attachments().decrypt_buffer.mockReturnValue(expectedDecryptedContent); + + const result = await cipherEncryptionService.decryptAttachmentContent( + cipher, + attachment, + encryptedContent, + userId, + ); + + expect(result).toEqual(expectedDecryptedContent); + expect(cipher.toSdkCipher).toHaveBeenCalled(); + expect(attachment.toSdkAttachmentView).toHaveBeenCalled(); + expect(mockSdkClient.vault().attachments().decrypt_buffer).toHaveBeenCalledWith( + { id: "id" }, + { id: "a1" }, + encryptedContent, + ); + }); + }); +}); diff --git a/libs/common/src/vault/services/default-cipher-encryption.service.ts b/libs/common/src/vault/services/default-cipher-encryption.service.ts new file mode 100644 index 00000000000..2c57df6f5bb --- /dev/null +++ b/libs/common/src/vault/services/default-cipher-encryption.service.ts @@ -0,0 +1,190 @@ +import { EMPTY, catchError, firstValueFrom, map } from "rxjs"; + +import { CipherListView } from "@bitwarden/sdk-internal"; + +import { LogService } from "../../platform/abstractions/log.service"; +import { SdkService } from "../../platform/abstractions/sdk/sdk.service"; +import { UserId } from "../../types/guid"; +import { CipherEncryptionService } from "../abstractions/cipher-encryption.service"; +import { CipherType } from "../enums"; +import { Cipher } from "../models/domain/cipher"; +import { AttachmentView } from "../models/view/attachment.view"; +import { CipherView } from "../models/view/cipher.view"; +import { Fido2CredentialView } from "../models/view/fido2-credential.view"; + +export class DefaultCipherEncryptionService implements CipherEncryptionService { + constructor( + private sdkService: SdkService, + private logService: LogService, + ) {} + + async decrypt(cipher: Cipher, userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher()); + + const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!; + + // Decrypt Fido2 credentials if available + if ( + clientCipherView.type === CipherType.Login && + sdkCipherView.login?.fido2Credentials?.length + ) { + const fido2CredentialViews = ref.value + .vault() + .ciphers() + .decrypt_fido2_credentials(sdkCipherView); + + // TEMPORARY: Manually decrypt the keyValue for Fido2 credentials + // since we don't currently use the SDK for Fido2 Authentication. + const decryptedKeyValue = ref.value + .vault() + .ciphers() + .decrypt_fido2_private_key(sdkCipherView); + + clientCipherView.login.fido2Credentials = fido2CredentialViews + .map((f) => { + const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!; + + return { + ...view, + keyValue: decryptedKeyValue, + }; + }) + .filter((view): view is Fido2CredentialView => view !== undefined); + } + + return clientCipherView; + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher ${error}`); + return EMPTY; + }), + ), + ); + } + + decryptManyLegacy(ciphers: Cipher[], userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK not available"); + } + + using ref = sdk.take(); + + return ciphers.map((cipher) => { + const sdkCipherView = ref.value.vault().ciphers().decrypt(cipher.toSdkCipher()); + const clientCipherView = CipherView.fromSdkCipherView(sdkCipherView)!; + + // Handle FIDO2 credentials if present + if ( + clientCipherView.type === CipherType.Login && + sdkCipherView.login?.fido2Credentials?.length + ) { + const fido2CredentialViews = ref.value + .vault() + .ciphers() + .decrypt_fido2_credentials(sdkCipherView); + + // TODO (PM-21259): Remove manual keyValue decryption for FIDO2 credentials. + // This is a temporary workaround until we can use the SDK for FIDO2 authentication. + const decryptedKeyValue = ref.value + .vault() + .ciphers() + .decrypt_fido2_private_key(sdkCipherView); + + clientCipherView.login.fido2Credentials = fido2CredentialViews + .map((f) => { + const view = Fido2CredentialView.fromSdkFido2CredentialView(f)!; + return { + ...view, + keyValue: decryptedKeyValue, + }; + }) + .filter((view): view is Fido2CredentialView => view !== undefined); + } + + return clientCipherView; + }); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt ciphers: ${error}`); + return EMPTY; + }), + ), + ); + } + + async decryptMany(ciphers: Cipher[], userId: UserId): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK is undefined"); + } + + using ref = sdk.take(); + + return ref.value + .vault() + .ciphers() + .decrypt_list(ciphers.map((cipher) => cipher.toSdkCipher())); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher list: ${error}`); + return EMPTY; + }), + ), + ); + } + + /** + * Decrypts an attachment's content from a response object. + * + * @param cipher The encrypted cipher object that owns the attachment + * @param attachment The encrypted attachment object + * @param encryptedContent The encrypted content as a Uint8Array + * @param userId The user ID whose key will be used for decryption + * + * @returns A promise that resolves to the decrypted content + */ + async decryptAttachmentContent( + cipher: Cipher, + attachment: AttachmentView, + encryptedContent: Uint8Array, + userId: UserId, + ): Promise { + return firstValueFrom( + this.sdkService.userClient$(userId).pipe( + map((sdk) => { + if (!sdk) { + throw new Error("SDK is undefined"); + } + + using ref = sdk.take(); + + return ref.value + .vault() + .attachments() + .decrypt_buffer( + cipher.toSdkCipher(), + attachment.toSdkAttachmentView(), + encryptedContent, + ); + }), + catchError((error: unknown) => { + this.logService.error(`Failed to decrypt cipher buffer: ${error}`); + return EMPTY; + }), + ), + ); + } +} diff --git a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts index 9284718a063..af29d8263c6 100644 --- a/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts +++ b/libs/importer/src/importers/bitwarden/bitwarden-json-importer.ts @@ -118,9 +118,7 @@ export class BitwardenJsonImporter extends BaseImporter implements Importer { const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(map((a) => a?.id)), ); - const view = await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + const view = await this.cipherService.decrypt(cipher, activeUserId); this.cleanupCipher(view); this.result.ciphers.push(view); } diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index ae408af421b..6ed4caa3f8d 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -10,7 +10,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract import { CipherWithIdExport } from "@bitwarden/common/models/export/cipher-with-ids.export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; @@ -172,6 +172,8 @@ describe("VaultExportService", () => { let apiService: MockProxy; let fetchMock: jest.Mock; + const userId = "" as UserId; + beforeEach(() => { cryptoFunctionService = mock(); cipherService = mock(); @@ -185,7 +187,6 @@ describe("VaultExportService", () => { keyService.userKey$.mockReturnValue(new BehaviorSubject("mockOriginalUserKey" as any)); - const userId = "" as UserId; const accountInfo: AccountInfo = { email: "", emailVerified: true, @@ -338,7 +339,9 @@ describe("VaultExportService", () => { cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue( + new Error("Error decrypting attachment"), + ); global.fetch = jest.fn(() => Promise.resolve({ @@ -356,13 +359,17 @@ describe("VaultExportService", () => { it("contains attachments with folders", async () => { const cipherData = new CipherData(); cipherData.id = "mock-id"; + const cipherRecord: Record = { + ["mock-id" as CipherId]: cipherData, + }; const cipherView = new CipherView(new Cipher(cipherData)); const attachmentView = new AttachmentView(new Attachment(new AttachmentData())); attachmentView.fileName = "mock-file-name"; cipherView.attachments = [attachmentView]; + cipherService.ciphers$.mockReturnValue(of(cipherRecord)); cipherService.getAllDecrypted.mockResolvedValue([cipherView]); folderService.getAllDecryptedFromState.mockResolvedValue([]); - encryptService.decryptFileData.mockResolvedValue(new Uint8Array(255)); + cipherService.getDecryptedAttachmentBuffer.mockResolvedValue(new Uint8Array(255)); global.fetch = jest.fn(() => Promise.resolve({ status: 200, diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index 8b66580d4cd..537585aac7e 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -12,14 +12,12 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { CipherWithIdExport, FolderWithIdExport } from "@bitwarden/common/models/export"; import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; -import { UserId } from "@bitwarden/common/types/guid"; +import { CipherId, UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction"; import { CipherType } from "@bitwarden/common/vault/enums"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; import { Folder } from "@bitwarden/common/vault/models/domain/folder"; -import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @@ -118,8 +116,19 @@ export class IndividualVaultExportService const cipherFolder = attachmentsFolder.folder(cipher.id); for (const attachment of cipher.attachments) { const response = await this.downloadAttachment(cipher.id, attachment.id); - const decBuf = await this.decryptAttachment(cipher, attachment, response); - cipherFolder.file(attachment.fileName, decBuf); + + try { + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + cipher.id as CipherId, + attachment, + response, + activeUserId, + ); + + cipherFolder.file(attachment.fileName, decBuf); + } catch { + throw new Error("Error decrypting attachment"); + } } } @@ -146,23 +155,6 @@ export class IndividualVaultExportService return response; } - private async decryptAttachment( - cipher: CipherView, - attachment: AttachmentView, - response: Response, - ) { - try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = - attachment.key != null - ? attachment.key - : await this.keyService.getOrgKey(cipher.organizationId); - return await this.encryptService.decryptFileData(encBuf, key); - } catch { - throw new Error("Error decrypting attachment"); - } - } - private async getDecryptedExport( activeUserId: UserId, format: "json" | "csv", diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts index fc46915c15d..4f30f299062 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/org-vault-export.service.ts @@ -155,12 +155,9 @@ export class OrganizationVaultExportService .forEach(async (c) => { const cipher = new Cipher(new CipherData(c)); exportPromises.push( - this.cipherService - .getKeyForCipherKeyDecryption(cipher, activeUserId) - .then((key) => cipher.decrypt(key)) - .then((decCipher) => { - decCiphers.push(decCipher); - }), + this.cipherService.decrypt(cipher, activeUserId).then((decCipher) => { + decCiphers.push(decCipher); + }), ); }); } diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts index 0fe358cd89b..da827addf67 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.spec.ts @@ -80,6 +80,7 @@ describe("CipherAttachmentsComponent", () => { get: cipherServiceGet, saveAttachmentWithServer, getKeyForCipherKeyDecryption: () => Promise.resolve(null), + decrypt: jest.fn().mockResolvedValue(cipherView), }, }, { diff --git a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts index 29a80c826c6..aa9769ec392 100644 --- a/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts +++ b/libs/vault/src/cipher-form/components/attachments/cipher-attachments.component.ts @@ -137,9 +137,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { this.organization = await this.getOrganization(); this.cipherDomain = await this.getCipher(this.cipherId); - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Update the initial state of the submit button if (this.submitBtn) { @@ -210,9 +208,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit { ); // re-decrypt the cipher to update the attachments - this.cipher = await this.cipherDomain.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(this.cipherDomain, this.activeUserId), - ); + this.cipher = await this.cipherService.decrypt(this.cipherDomain, this.activeUserId); // Reset reactive form and input element this.fileInput.nativeElement.value = ""; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts index 98286e4bbb2..68eac4f0da2 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form.service.ts @@ -3,7 +3,6 @@ import { inject, Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; @@ -21,13 +20,10 @@ function isSetEqual(a: Set, b: Set) { export class DefaultCipherFormService implements CipherFormService { private cipherService: CipherService = inject(CipherService); private accountService: AccountService = inject(AccountService); - private apiService: ApiService = inject(ApiService); async decryptCipher(cipher: Cipher): Promise { const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - return await cipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(cipher, activeUserId), - ); + return await this.cipherService.decrypt(cipher, activeUserId); } async saveCipher(cipher: CipherView, config: CipherFormConfig): Promise { @@ -46,9 +42,7 @@ export class DefaultCipherFormService implements CipherFormService { // Creating a new cipher if (cipher.id == null) { savedCipher = await this.cipherService.createWithServer(encryptedCipher, config.admin); - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } if (config.originalCipher == null) { @@ -100,8 +94,6 @@ export class DefaultCipherFormService implements CipherFormService { return null; } - return await savedCipher.decrypt( - await this.cipherService.getKeyForCipherKeyDecryption(savedCipher, activeUserId), - ); + return await this.cipherService.decrypt(savedCipher, activeUserId); } } diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts index f621ca63101..8a4e962707d 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.spec.ts @@ -6,15 +6,16 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherType } from "@bitwarden/common/vault/enums"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; import { PasswordRepromptService } from "../../services/password-reprompt.service"; @@ -51,6 +52,21 @@ describe("DownloadAttachmentComponent", () => { }, } as CipherView; + const ciphers$ = new BehaviorSubject({ + "5555-444-3333": { + id: "5555-444-3333", + attachments: [ + { + id: "222-3333-4444", + fileName: "encrypted-filename", + key: "encrypted-key", + }, + ], + }, + }); + + const getFeatureFlag = jest.fn().mockResolvedValue(false); + beforeEach(async () => { showToast.mockClear(); getAttachmentData.mockClear(); @@ -60,13 +76,22 @@ describe("DownloadAttachmentComponent", () => { imports: [DownloadAttachmentComponent], providers: [ { provide: EncryptService, useValue: mock() }, - { provide: KeyService, useValue: mock() }, { provide: I18nService, useValue: { t: (key: string) => key } }, { provide: StateProvider, useValue: { activeUserId$ } }, { provide: ToastService, useValue: { showToast } }, { provide: ApiService, useValue: { getAttachmentData } }, { provide: FileDownloadService, useValue: { download } }, { provide: PasswordRepromptService, useValue: mock() }, + { + provide: ConfigService, + useValue: { + getFeatureFlag, + }, + }, + { + provide: CipherService, + useValue: { ciphers$: () => ciphers$, getDecryptedAttachmentBuffer: jest.fn() }, + }, ], }).compileComponents(); }); @@ -128,10 +153,12 @@ describe("DownloadAttachmentComponent", () => { }); }); - it("shows an error toast when EncArrayBuffer fails", async () => { + it("shows an error toast when getDecryptedAttachmentBuffer fails", async () => { getAttachmentData.mockResolvedValue({ url: "https://www.downloadattachement.com" }); fetchMock.mockResolvedValue({ status: 200 }); - EncArrayBuffer.fromResponse = jest.fn().mockRejectedValue({}); + + const cipherService = TestBed.inject(CipherService) as jest.Mocked; + cipherService.getDecryptedAttachmentBuffer.mockRejectedValue(new Error()); await component.download(); diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index e64777ebb8e..f06d6db582a 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -2,23 +2,19 @@ // @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { NEVER, switchMap } from "rxjs"; +import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { StateProvider } from "@bitwarden/common/platform/state"; -import { EmergencyAccessId, OrganizationId } from "@bitwarden/common/types/guid"; -import { OrgKey } from "@bitwarden/common/types/key"; +import { CipherId, EmergencyAccessId } from "@bitwarden/common/types/guid"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { AttachmentView } from "@bitwarden/common/vault/models/view/attachment.view"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { AsyncActionsModule, IconButtonModule, ToastService } from "@bitwarden/components"; -import { KeyService } from "@bitwarden/key-management"; @Component({ standalone: true, @@ -42,29 +38,14 @@ export class DownloadAttachmentComponent { /** When owners/admins can mange all items and when accessing from the admin console, use the admin endpoint */ @Input() admin?: boolean = false; - /** The organization key if the cipher is associated with one */ - private orgKey: OrgKey | null = null; - constructor( private i18nService: I18nService, private apiService: ApiService, private fileDownloadService: FileDownloadService, private toastService: ToastService, - private encryptService: EncryptService, private stateProvider: StateProvider, - private keyService: KeyService, - ) { - this.stateProvider.activeUserId$ - .pipe( - switchMap((userId) => (userId !== null ? this.keyService.orgKeys$(userId) : NEVER)), - takeUntilDestroyed(), - ) - .subscribe((data: Record | null) => { - if (data) { - this.orgKey = data[this.cipher.organizationId as OrganizationId]; - } - }); - } + private cipherService: CipherService, + ) {} /** Download the attachment */ download = async () => { @@ -100,9 +81,15 @@ export class DownloadAttachmentComponent { } try { - const encBuf = await EncArrayBuffer.fromResponse(response); - const key = this.attachment.key != null ? this.attachment.key : this.orgKey; - const decBuf = await this.encryptService.decryptFileData(encBuf, key); + const userId = await firstValueFrom(this.stateProvider.activeUserId$); + + const decBuf = await this.cipherService.getDecryptedAttachmentBuffer( + this.cipher.id as CipherId, + this.attachment, + response, + userId, + ); + this.fileDownloadService.download({ fileName: this.attachment.fileName, blobData: decBuf, From 1a1481bbd643169a554e72437c3f8b78264cfbbc Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Wed, 14 May 2025 16:01:03 +0100 Subject: [PATCH 16/54] Resolve the tiny line issue (#14758) --- .../billing/members/free-bitwarden-families.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.html b/apps/web/src/app/billing/members/free-bitwarden-families.component.html index ee21909beec..243cf612c73 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.html +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.html @@ -67,7 +67,9 @@ {{ "resendInvitation" | i18n }} -
+ +
+
diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 4f7c2a67483..e810aaec8cb 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -91,7 +91,7 @@ export class BitFormFieldComponent implements AfterContentChecked { protected defaultContentIsFocused = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.defaultContentIsFocused.set(target.matches(".default-content *:focus-visible")); + this.defaultContentIsFocused.set(target.matches("[data-default-content] *:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 2a6e06291fd..76fa3996210 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -25,7 +25,8 @@ import { TypographyModule } from "../typography"; * y-axis padding should be kept in sync with `item-action.component.ts`'s `top` and `bottom` units. * we want this to be the same height as the `item-action`'s `:after` element */ - "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", + "data-fvw-target": "", }, changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/libs/components/src/item/item.component.html b/libs/components/src/item/item.component.html index 812d6b0315b..2863bb2891b 100644 --- a/libs/components/src/item/item.component.html +++ b/libs/components/src/item/item.component.html @@ -1,4 +1,4 @@ - + diff --git a/libs/components/src/item/item.component.ts b/libs/components/src/item/item.component.ts index 1ef4a4af1fa..0c45f98139e 100644 --- a/libs/components/src/item/item.component.ts +++ b/libs/components/src/item/item.component.ts @@ -19,7 +19,7 @@ import { ItemActionComponent } from "./item-action.component"; providers: [{ provide: A11yRowDirective, useExisting: ItemComponent }], host: { class: - "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-cursor-pointer [&:has(.item-main-content_button:hover,.item-main-content_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", + "tw-block tw-box-border tw-overflow-hidden tw-flex tw-bg-background [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-cursor-pointer [&:has([data-item-main-content]_button:hover,[data-item-main-content]_a:hover)]:tw-bg-primary-100 tw-text-main tw-border-solid tw-border-b tw-border-0 [&:not(bit-layout_*)]:tw-rounded-lg bit-compact:[&:not(bit-layout_*)]:tw-rounded-none bit-compact:[&:not(bit-layout_*)]:last-of-type:tw-rounded-b-lg bit-compact:[&:not(bit-layout_*)]:first-of-type:tw-rounded-t-lg tw-min-h-9 tw-mb-1.5 bit-compact:tw-mb-0", }, }) export class ItemComponent extends A11yRowDirective { @@ -29,7 +29,7 @@ export class ItemComponent extends A11yRowDirective { protected focusVisibleWithin = signal(false); @HostListener("focusin", ["$event.target"]) onFocusIn(target: HTMLElement) { - this.focusVisibleWithin.set(target.matches(".fvw-target:focus-visible")); + this.focusVisibleWithin.set(target.matches("[data-fvw-target]:focus-visible")); } @HostListener("focusout") onFocusOut() { diff --git a/libs/components/src/navigation/nav-item.component.html b/libs/components/src/navigation/nav-item.component.html index c3cf559389f..595ddf5a99f 100644 --- a/libs/components/src/navigation/nav-item.component.html +++ b/libs/components/src/navigation/nav-item.component.html @@ -70,10 +70,11 @@ - + `, + template: ` + + + + + `, }) class StoryDialogComponent { constructor(public dialogService: DialogService) {} @@ -31,6 +41,14 @@ class StoryDialogComponent { }, }); } + + openDrawer() { + this.dialogService.openDrawer(StoryDialogContentComponent, { + data: { + animal: "panda", + }, + }); + } } @Component({ @@ -65,25 +83,37 @@ export default { title: "Component Library/Dialogs/Service", component: StoryDialogComponent, decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ declarations: [StoryDialogContentComponent], imports: [ SharedModule, ButtonModule, + NoopAnimationsModule, DialogModule, IconButtonModule, DialogCloseDirective, DialogComponent, DialogTitleContainerDirective, + RouterTestingModule, + LayoutComponent, ], + providers: [DialogService], + }), + applicationConfig({ providers: [ - DialogService, { provide: I18nService, useFactory: () => { return new I18nMockService({ close: "Close", - loading: "Loading", + search: "Search", + skipToContent: "Skip to content", + submenu: "submenu", + toggleCollapse: "toggle collapse", + toggleSideNavigation: "Toggle side navigation", + yes: "Yes", + no: "No", }); }, }, @@ -100,4 +130,21 @@ export default { type Story = StoryObj; -export const Default: Story = {}; +export const Default: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[0]; + await userEvent.click(button); + }, +}; + +/** Drawers must be a descendant of `bit-layout`. */ +export const Drawer: Story = { + play: async (context) => { + const canvas = context.canvasElement; + + const button = getAllByRole(canvas, "button")[1]; + await userEvent.click(button); + }, +}; diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 83aaaff470e..409bf0a5b55 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -1,31 +1,25 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { - DEFAULT_DIALOG_CONFIG, - Dialog, - DialogConfig, - DialogRef, - DIALOG_SCROLL_STRATEGY, + Dialog as CdkDialog, + DialogConfig as CdkDialogConfig, + DialogRef as CdkDialogRefBase, + DIALOG_DATA, + DialogCloseOptions, } from "@angular/cdk/dialog"; -import { ComponentType, Overlay, OverlayContainer, ScrollStrategy } from "@angular/cdk/overlay"; -import { - Inject, - Injectable, - Injector, - OnDestroy, - Optional, - SkipSelf, - TemplateRef, -} from "@angular/core"; +import { ComponentType, ScrollStrategy } from "@angular/cdk/overlay"; +import { ComponentPortal, Portal } from "@angular/cdk/portal"; +import { Injectable, Injector, TemplateRef, inject } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { NavigationEnd, Router } from "@angular/router"; -import { filter, firstValueFrom, Subject, switchMap, takeUntil } from "rxjs"; +import { filter, firstValueFrom, map, Observable, Subject, switchMap } from "rxjs"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DrawerService } from "../drawer/drawer.service"; + import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; -import { SimpleDialogOptions, Translation } from "./simple-dialog/types"; +import { SimpleDialogOptions } from "./simple-dialog/types"; /** * The default `BlockScrollStrategy` does not work well with virtual scrolling. @@ -48,61 +42,163 @@ class CustomBlockScrollStrategy implements ScrollStrategy { detach() {} } +export abstract class DialogRef + implements Pick, "close" | "closed" | "disableClose" | "componentInstance"> +{ + abstract readonly isDrawer?: boolean; + + // --- From CdkDialogRef --- + abstract close(result?: R, options?: DialogCloseOptions): void; + abstract readonly closed: Observable; + abstract disableClose: boolean | undefined; + /** + * @deprecated + * Does not work with drawer dialogs. + **/ + abstract componentInstance: C | null; +} + +export type DialogConfig = Pick< + CdkDialogConfig, + "data" | "disableClose" | "ariaModal" | "positionStrategy" | "height" | "width" +>; + +class DrawerDialogRef implements DialogRef { + readonly isDrawer = true; + + private _closed = new Subject(); + closed = this._closed.asObservable(); + disableClose = false; + + /** The portal containing the drawer */ + portal?: Portal; + + constructor(private drawerService: DrawerService) {} + + close(result?: R, _options?: DialogCloseOptions): void { + if (this.disableClose) { + return; + } + this.drawerService.close(this.portal!); + this._closed.next(result); + this._closed.complete(); + } + + componentInstance: C | null = null; +} + +/** + * DialogRef that delegates functionality to the CDK implementation + **/ +export class CdkDialogRef implements DialogRef { + readonly isDrawer = false; + + /** This is not available until after construction, @see DialogService.open. */ + cdkDialogRefBase!: CdkDialogRefBase; + + // --- Delegated to CdkDialogRefBase --- + + close(result?: R, options?: DialogCloseOptions): void { + this.cdkDialogRefBase.close(result, options); + } + + get closed(): Observable { + return this.cdkDialogRefBase.closed; + } + + get disableClose(): boolean | undefined { + return this.cdkDialogRefBase.disableClose; + } + set disableClose(value: boolean | undefined) { + this.cdkDialogRefBase.disableClose = value; + } + + // Delegate the `componentInstance` property to the CDK DialogRef + get componentInstance(): C | null { + return this.cdkDialogRefBase.componentInstance; + } +} + @Injectable() -export class DialogService extends Dialog implements OnDestroy { - private _destroy$ = new Subject(); +export class DialogService { + private dialog = inject(CdkDialog); + private drawerService = inject(DrawerService); + private injector = inject(Injector); + private router = inject(Router, { optional: true }); + private authService = inject(AuthService, { optional: true }); + private i18nService = inject(I18nService); private backDropClasses = ["tw-fixed", "tw-bg-black", "tw-bg-opacity-30", "tw-inset-0"]; - private defaultScrollStrategy = new CustomBlockScrollStrategy(); + private activeDrawer: DrawerDialogRef | null = null; - constructor( - /** Parent class constructor */ - _overlay: Overlay, - _injector: Injector, - @Optional() @Inject(DEFAULT_DIALOG_CONFIG) _defaultOptions: DialogConfig, - @Optional() @SkipSelf() _parentDialog: Dialog, - _overlayContainer: OverlayContainer, - @Inject(DIALOG_SCROLL_STRATEGY) scrollStrategy: any, - - /** Not in parent class */ - @Optional() router: Router, - @Optional() authService: AuthService, - - protected i18nService: I18nService, - ) { - super(_overlay, _injector, _defaultOptions, _parentDialog, _overlayContainer, scrollStrategy); - + constructor() { + /** + * TODO: This logic should exist outside of `libs/components`. + * @see https://bitwarden.atlassian.net/browse/CL-657 + **/ /** Close all open dialogs if the vault locks */ - if (router && authService) { - router.events + if (this.router && this.authService) { + this.router.events .pipe( filter((event) => event instanceof NavigationEnd), - switchMap(() => authService.getAuthStatus()), + switchMap(() => this.authService!.getAuthStatus()), filter((v) => v !== AuthenticationStatus.Unlocked), - takeUntil(this._destroy$), + takeUntilDestroyed(), ) .subscribe(() => this.closeAll()); } } - override ngOnDestroy(): void { - this._destroy$.next(); - this._destroy$.complete(); - super.ngOnDestroy(); - } - - override open( + open( componentOrTemplateRef: ComponentType | TemplateRef, config?: DialogConfig>, ): DialogRef { - config = { + /** + * This is a bit circular in nature: + * We need the DialogRef instance for the DI injector that is passed *to* `Dialog.open`, + * but we get the base CDK DialogRef instance *from* `Dialog.open`. + * + * To break the circle, we define CDKDialogRef as a wrapper for the CDKDialogRefBase. + * This allows us to create the class instance and provide the base instance later, almost like "deferred inheritance". + **/ + const ref = new CdkDialogRef(); + const injector = this.createInjector({ + data: config?.data, + dialogRef: ref, + }); + + // Merge the custom config with the default config + const _config = { backdropClass: this.backDropClasses, scrollStrategy: this.defaultScrollStrategy, + injector, ...config, }; - return super.open(componentOrTemplateRef, config); + ref.cdkDialogRefBase = this.dialog.open(componentOrTemplateRef, _config); + return ref; + } + + /** Opens a dialog in the side drawer */ + openDrawer( + component: ComponentType, + config?: DialogConfig>, + ): DialogRef { + this.activeDrawer?.close(); + /** + * This is also circular. When creating the DrawerDialogRef, we do not yet have a portal instance to provide to the injector. + * Similar to `this.open`, we get around this with mutability. + */ + this.activeDrawer = new DrawerDialogRef(this.drawerService); + const portal = new ComponentPortal( + component, + null, + this.createInjector({ data: config?.data, dialogRef: this.activeDrawer }), + ); + this.activeDrawer.portal = portal; + this.drawerService.open(portal); + return this.activeDrawer; } /** @@ -113,8 +209,7 @@ export class DialogService extends Dialog implements OnDestroy { */ async openSimpleDialog(simpleDialogOptions: SimpleDialogOptions): Promise { const dialogRef = this.openSimpleDialogRef(simpleDialogOptions); - - return firstValueFrom(dialogRef.closed); + return firstValueFrom(dialogRef.closed.pipe(map((v: boolean | undefined) => !!v))); } /** @@ -134,20 +229,29 @@ export class DialogService extends Dialog implements OnDestroy { }); } - protected translate(translation: string | Translation, defaultKey?: string): string { - if (translation == null && defaultKey == null) { - return null; - } + /** Close all open dialogs */ + closeAll(): void { + return this.dialog.closeAll(); + } - if (translation == null) { - return this.i18nService.t(defaultKey); - } - - // Translation interface use implies we must localize. - if (typeof translation === "object") { - return this.i18nService.t(translation.key, ...(translation.placeholders ?? [])); - } - - return translation; + /** The injector that is passed to the opened dialog */ + private createInjector(opts: { data: unknown; dialogRef: DialogRef }): Injector { + return Injector.create({ + providers: [ + { + provide: DIALOG_DATA, + useValue: opts.data, + }, + { + provide: DialogRef, + useValue: opts.dialogRef, + }, + { + provide: CdkDialogRefBase, + useValue: opts.dialogRef, + }, + ], + parent: this.injector, + }); } } diff --git a/libs/components/src/dialog/dialog/dialog.component.html b/libs/components/src/dialog/dialog/dialog.component.html index 01f05985127..eaf7fc2beec 100644 --- a/libs/components/src/dialog/dialog/dialog.component.html +++ b/libs/components/src/dialog/dialog/dialog.component.html @@ -1,12 +1,22 @@ +@let isDrawer = dialogRef?.isDrawer;
+ @let showHeaderBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().top;
-

} -

+
+ @let showFooterBorder = !isDrawer || background === "alt" || bodyHasScrolledFrom().bottom;
diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 504dbd3a1ea..8cf9cd18c78 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -1,14 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CdkTrapFocus } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { CdkScrollable } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { Component, HostBinding, Input } from "@angular/core"; +import { Component, HostBinding, Input, inject, viewChild } from "@angular/core"; import { I18nPipe } from "@bitwarden/ui-common"; import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; import { TypographyDirective } from "../../typography/typography.directive"; +import { hasScrolledFrom } from "../../utils/has-scrolled-from"; import { fadeIn } from "../animations"; +import { DialogRef } from "../dialog.service"; import { DialogCloseDirective } from "../directives/dialog-close.directive"; import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; @@ -17,6 +21,9 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai templateUrl: "./dialog.component.html", animations: [fadeIn], standalone: true, + host: { + "(keydown.esc)": "handleEsc($event)", + }, imports: [ CommonModule, DialogTitleContainerDirective, @@ -24,9 +31,15 @@ import { DialogTitleContainerDirective } from "../directives/dialog-title-contai BitIconButtonComponent, DialogCloseDirective, I18nPipe, + CdkTrapFocus, + CdkScrollable, ], }) export class DialogComponent { + protected dialogRef = inject(DialogRef, { optional: true }); + private scrollableBody = viewChild.required(CdkScrollable); + protected bodyHasScrolledFrom = hasScrolledFrom(this.scrollableBody); + /** Background color */ @Input() background: "default" | "alt" = "default"; @@ -64,21 +77,31 @@ export class DialogComponent { @HostBinding("class") get classes() { // `tw-max-h-[90vh]` is needed to prevent dialogs from overlapping the desktop header - return ["tw-flex", "tw-flex-col", "tw-w-screen", "tw-p-4", "tw-max-h-[90vh]"].concat( - this.width, - ); + return ["tw-flex", "tw-flex-col", "tw-w-screen"] + .concat( + this.width, + this.dialogRef?.isDrawer + ? ["tw-min-h-screen", "md:tw-w-[23rem]"] + : ["tw-p-4", "tw-w-screen", "tw-max-h-[90vh]"], + ) + .flat(); + } + + handleEsc(event: Event) { + this.dialogRef?.close(); + event.stopPropagation(); } get width() { switch (this.dialogSize) { case "small": { - return "tw-max-w-sm"; + return "md:tw-max-w-sm"; } case "large": { - return "tw-max-w-3xl"; + return "md:tw-max-w-3xl"; } default: { - return "tw-max-w-xl"; + return "md:tw-max-w-xl"; } } } diff --git a/libs/components/src/dialog/dialogs.mdx b/libs/components/src/dialog/dialogs.mdx index 63df0bfc131..3f44f31a5eb 100644 --- a/libs/components/src/dialog/dialogs.mdx +++ b/libs/components/src/dialog/dialogs.mdx @@ -22,6 +22,9 @@ For alerts or simple confirmation actions, like speedbumps, use the Dialogs's should be used sparingly as they do call extra attention to themselves and can be interruptive if overused. +For non-blocking, supplementary content, open dialogs as a +[Drawer](?path=/story/component-library-dialogs-service--drawer) (requires `bit-layout`). + ## Placement Dialogs should be centered vertically and horizontally on screen. Dialogs height should expand to diff --git a/libs/components/src/dialog/index.ts b/libs/components/src/dialog/index.ts index 0ab9a5d9e67..fb4c2721b81 100644 --- a/libs/components/src/dialog/index.ts +++ b/libs/components/src/dialog/index.ts @@ -1,4 +1,4 @@ export * from "./dialog.module"; export * from "./simple-dialog/types"; export * from "./dialog.service"; -export { DialogConfig, DIALOG_DATA, DialogRef } from "@angular/cdk/dialog"; +export { DIALOG_DATA } from "@angular/cdk/dialog"; diff --git a/libs/components/src/drawer/drawer-body.component.ts b/libs/components/src/drawer/drawer-body.component.ts index 9bd2adcffbc..563987dd8b0 100644 --- a/libs/components/src/drawer/drawer-body.component.ts +++ b/libs/components/src/drawer/drawer-body.component.ts @@ -1,7 +1,7 @@ import { CdkScrollable } from "@angular/cdk/scrolling"; -import { ChangeDetectionStrategy, Component, Signal, inject } from "@angular/core"; -import { toSignal } from "@angular/core/rxjs-interop"; -import { map } from "rxjs"; +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +import { hasScrolledFrom } from "../utils/has-scrolled-from"; /** * Body container for `bit-drawer` @@ -14,7 +14,7 @@ import { map } from "rxjs"; host: { class: "tw-p-4 tw-pt-0 tw-block tw-overflow-auto tw-border-solid tw-border tw-border-transparent tw-transition-colors tw-duration-200", - "[class.tw-border-t-secondary-300]": "isScrolled()", + "[class.tw-border-t-secondary-300]": "this.hasScrolledFrom().top", }, hostDirectives: [ { @@ -24,13 +24,5 @@ import { map } from "rxjs"; template: ` `, }) export class DrawerBodyComponent { - private scrollable = inject(CdkScrollable); - - /** TODO: share this utility with browser popup header? */ - protected isScrolled: Signal = toSignal( - this.scrollable - .elementScrolled() - .pipe(map(() => this.scrollable.measureScrollOffset("top") > 0)), - { initialValue: false }, - ); + protected hasScrolledFrom = hasScrolledFrom(); } diff --git a/libs/components/src/drawer/drawer.component.ts b/libs/components/src/drawer/drawer.component.ts index ccabb6f0b6e..351be57e07b 100644 --- a/libs/components/src/drawer/drawer.component.ts +++ b/libs/components/src/drawer/drawer.component.ts @@ -10,7 +10,7 @@ import { viewChild, } from "@angular/core"; -import { DrawerHostDirective } from "./drawer-host.directive"; +import { DrawerService } from "./drawer.service"; /** * A drawer is a panel of supplementary content that is adjacent to the page's main content. @@ -25,7 +25,7 @@ import { DrawerHostDirective } from "./drawer-host.directive"; templateUrl: "drawer.component.html", }) export class DrawerComponent { - private drawerHost = inject(DrawerHostDirective); + private drawerHost = inject(DrawerService); private portal = viewChild.required(CdkPortal); /** diff --git a/libs/components/src/drawer/drawer.mdx b/libs/components/src/drawer/drawer.mdx index 57d618cfe95..bc99fa290d6 100644 --- a/libs/components/src/drawer/drawer.mdx +++ b/libs/components/src/drawer/drawer.mdx @@ -12,6 +12,8 @@ import { DrawerComponent } from "@bitwarden/components"; # Drawer +**Note: `bit-drawer` is deprecated. Use `bit-dialog` and `DialogService.openDrawer(...)` instead.** + A drawer is a panel of supplementary content that is adjacent to the page's main content. diff --git a/libs/components/src/drawer/drawer.service.ts b/libs/components/src/drawer/drawer.service.ts new file mode 100644 index 00000000000..dd8575efee8 --- /dev/null +++ b/libs/components/src/drawer/drawer.service.ts @@ -0,0 +1,20 @@ +import { Portal } from "@angular/cdk/portal"; +import { Injectable, signal } from "@angular/core"; + +@Injectable({ providedIn: "root" }) +export class DrawerService { + private _portal = signal | undefined>(undefined); + + /** The portal to display */ + portal = this._portal.asReadonly(); + + open(portal: Portal) { + this._portal.set(portal); + } + + close(portal: Portal) { + if (portal === this.portal()) { + this._portal.set(undefined); + } + } +} diff --git a/libs/components/src/layout/index.ts b/libs/components/src/layout/index.ts index 6994a4f639f..a257a4dde85 100644 --- a/libs/components/src/layout/index.ts +++ b/libs/components/src/layout/index.ts @@ -1 +1,2 @@ export * from "./layout.component"; +export * from "./scroll-layout.directive"; diff --git a/libs/components/src/layout/layout.component.html b/libs/components/src/layout/layout.component.html index 33b8de81572..19280c99756 100644 --- a/libs/components/src/layout/layout.component.html +++ b/libs/components/src/layout/layout.component.html @@ -1,43 +1,52 @@ -
+@let mainContentId = "main-content";
- -
- +
+ + +
+ - - @if ( - { - open: sideNavService.open$ | async, - }; - as data - ) { -
- @if (data.open) { -
- } -
- } -
- + + @if ( + { + open: sideNavService.open$ | async, + }; + as data + ) { +
+ @if (data.open) { +
+ } +
+ } +
+
+
+ +
diff --git a/libs/components/src/layout/layout.component.ts b/libs/components/src/layout/layout.component.ts index 7bf8a6ad173..6ee7dd8222e 100644 --- a/libs/components/src/layout/layout.component.ts +++ b/libs/components/src/layout/layout.component.ts @@ -1,9 +1,10 @@ +import { A11yModule, CdkTrapFocus } from "@angular/cdk/a11y"; import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; -import { Component, inject } from "@angular/core"; +import { Component, ElementRef, inject, viewChild } from "@angular/core"; import { RouterModule } from "@angular/router"; -import { DrawerHostDirective } from "../drawer/drawer-host.directive"; +import { DrawerService } from "../drawer/drawer.service"; import { LinkModule } from "../link"; import { SideNavService } from "../navigation/side-nav.service"; import { SharedModule } from "../shared"; @@ -12,16 +13,23 @@ import { SharedModule } from "../shared"; selector: "bit-layout", templateUrl: "layout.component.html", standalone: true, - imports: [CommonModule, SharedModule, LinkModule, RouterModule, PortalModule], - hostDirectives: [DrawerHostDirective], + imports: [ + CommonModule, + SharedModule, + LinkModule, + RouterModule, + PortalModule, + A11yModule, + CdkTrapFocus, + ], }) export class LayoutComponent { - protected mainContentId = "main-content"; - protected sideNavService = inject(SideNavService); - protected drawerPortal = inject(DrawerHostDirective).portal; + protected drawerPortal = inject(DrawerService).portal; - focusMainContent() { - document.getElementById(this.mainContentId)?.focus(); + private mainContent = viewChild.required>("main"); + + protected focusMainContent() { + this.mainContent().nativeElement.focus(); } } diff --git a/libs/components/src/layout/scroll-layout.directive.ts b/libs/components/src/layout/scroll-layout.directive.ts new file mode 100644 index 00000000000..d3ea2bf557b --- /dev/null +++ b/libs/components/src/layout/scroll-layout.directive.ts @@ -0,0 +1,35 @@ +import { Directionality } from "@angular/cdk/bidi"; +import { CdkVirtualScrollable, ScrollDispatcher, VIRTUAL_SCROLLABLE } from "@angular/cdk/scrolling"; +import { Directive, ElementRef, NgZone, Optional } from "@angular/core"; + +@Directive({ + selector: "cdk-virtual-scroll-viewport[bitScrollLayout]", + standalone: true, + providers: [{ provide: VIRTUAL_SCROLLABLE, useExisting: ScrollLayoutDirective }], +}) +export class ScrollLayoutDirective extends CdkVirtualScrollable { + private mainRef: ElementRef; + + constructor(scrollDispatcher: ScrollDispatcher, ngZone: NgZone, @Optional() dir: Directionality) { + const mainEl = document.querySelector("main")!; + if (!mainEl) { + // eslint-disable-next-line no-console + console.error("HTML main element must be an ancestor of [bitScrollLayout]"); + } + const mainRef = new ElementRef(mainEl); + super(mainRef, scrollDispatcher, ngZone, dir); + this.mainRef = mainRef; + } + + override getElementRef(): ElementRef { + return this.mainRef; + } + + override measureBoundingClientRectWithScrollOffset( + from: "left" | "top" | "right" | "bottom", + ): number { + return ( + this.mainRef.nativeElement.getBoundingClientRect()[from] - this.measureScrollOffset(from) + ); + } +} 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 index 7709506f050..904b9e11c3a 100644 --- 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 @@ -3,15 +3,23 @@ import { Component, OnInit } from "@angular/core"; import { DialogModule, DialogService } from "../../../dialog"; import { IconButtonModule } from "../../../icon-button"; +import { ScrollLayoutDirective } from "../../../layout"; import { SectionComponent } from "../../../section"; import { TableDataSource, TableModule } from "../../../table"; @Component({ selector: "dialog-virtual-scroll-block", standalone: true, - imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], + imports: [ + DialogModule, + IconButtonModule, + SectionComponent, + TableModule, + ScrollingModule, + ScrollLayoutDirective, + ], template: /*html*/ ` - + diff --git a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts index 70f56d2e28d..b62e669d369 100644 --- a/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/kitchen-sink-main.component.ts @@ -12,8 +12,69 @@ import { KitchenSinkToggleList } from "./kitchen-sink-toggle-list.component"; standalone: true, imports: [KitchenSinkSharedModule], template: ` - - Dialog body text goes here. + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+ + What did foo say to bar? + + +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id + est laborum. +

+
@@ -90,72 +151,6 @@ class KitchenSinkDialog {
- - - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

- - What did foo say to bar? - - -

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt - ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation - ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur - sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id - est laborum. -

-
-
`, }) export class KitchenSinkMainComponent { @@ -168,7 +163,7 @@ export class KitchenSinkMainComponent { } openDrawer() { - this.drawerOpen.set(true); + this.dialogService.openDrawer(KitchenSinkDialog); } navItems = [ 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 f57a9de4e68..d318e1b5f0e 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -14,7 +14,6 @@ import { import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DialogService } from "../../dialog"; import { LayoutComponent } from "../../layout"; import { I18nMockService } from "../../utils/i18n-mock.service"; import { positionFixedWrapperDecorator } from "../storybook-decorators"; @@ -39,8 +38,20 @@ export default { KitchenSinkTable, KitchenSinkToggleList, ], + }), + applicationConfig({ providers: [ - DialogService, + provideNoopAnimations(), + importProvidersFrom( + RouterModule.forRoot( + [ + { path: "", redirectTo: "bitwarden", pathMatch: "full" }, + { path: "bitwarden", component: KitchenSinkMainComponent }, + { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, + ], + { useHash: true }, + ), + ), { provide: I18nService, useFactory: () => { @@ -58,21 +69,6 @@ export default { }, ], }), - applicationConfig({ - providers: [ - provideNoopAnimations(), - importProvidersFrom( - RouterModule.forRoot( - [ - { path: "", redirectTo: "bitwarden", pathMatch: "full" }, - { path: "bitwarden", component: KitchenSinkMainComponent }, - { path: "virtual-scroll", component: DialogVirtualScrollBlockComponent }, - ], - { useHash: true }, - ), - ), - ], - }), ], } as Meta; diff --git a/libs/components/src/table/table-scroll.component.html b/libs/components/src/table/table-scroll.component.html index 8f2c88ba3ad..523912cd7ac 100644 --- a/libs/components/src/table/table-scroll.component.html +++ b/libs/components/src/table/table-scroll.component.html @@ -1,5 +1,5 @@ diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 34cd8c5d9ca..e198da1aba0 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -21,6 +21,8 @@ import { TrackByFunction, } from "@angular/core"; +import { ScrollLayoutDirective } from "../layout"; + import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; @@ -58,6 +60,7 @@ export class BitRowDef { CdkFixedSizeVirtualScroll, CdkVirtualForOf, RowDirective, + ScrollLayoutDirective, ], }) export class TableScrollComponent diff --git a/libs/components/src/table/table.mdx b/libs/components/src/table/table.mdx index 8d784190ed9..59bf5b773a3 100644 --- a/libs/components/src/table/table.mdx +++ b/libs/components/src/table/table.mdx @@ -142,7 +142,7 @@ dataSource.filter = (data) => data.orgType === "family"; Rudimentary string filtering is supported out of the box with `TableDataSource.simpleStringFilter`. It works by converting each entry into a string of it's properties. The provided string is then -compared against the filter value using a simple `indexOf` check. For convienence, you can also just +compared against the filter value using a simple `indexOf` check. For convenience, you can also just pass a string directly. ```ts @@ -153,7 +153,7 @@ dataSource.filter = "search value"; ### Virtual Scrolling -It's heavily adviced to use virtual scrolling if you expect the table to have any significant amount +It's heavily advised to use virtual scrolling if you expect the table to have any significant amount of data. This is done by using the `bit-table-scroll` component instead of the `bit-table` component. This component behaves slightly different from the `bit-table` component. Instead of using the `*ngFor` directive to render the rows, you provide a `bitRowDef` template that will be @@ -178,6 +178,14 @@ height and align vertically. ``` +#### Deprecated approach + +Before `bit-table-scroll` was introduced, virtual scroll in tables was implemented manually via +constructs from Angular CDK. This included wrapping the table with a `cdk-virtual-scroll-viewport` +and targeting with `bit-layout`'s scroll container with the `bitScrollLayout` directive. + +This pattern is deprecated in favor of `bit-table-scroll`. + ## Accessibility - Always include a row or column header with your table; this allows assistive technology to better diff --git a/libs/components/src/table/table.stories.ts b/libs/components/src/table/table.stories.ts index e8ab24ee8b7..d696e6077dd 100644 --- a/libs/components/src/table/table.stories.ts +++ b/libs/components/src/table/table.stories.ts @@ -1,6 +1,13 @@ +import { RouterTestingModule } from "@angular/router/testing"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; + import { countries } from "../form/countries"; +import { LayoutComponent } from "../layout"; +import { mockLayoutI18n } from "../layout/mocks"; +import { positionFixedWrapperDecorator } from "../stories/storybook-decorators"; +import { I18nMockService } from "../utils"; import { TableDataSource } from "./table-data-source"; import { TableModule } from "./table.module"; @@ -8,8 +15,17 @@ import { TableModule } from "./table.module"; export default { title: "Component Library/Table", decorators: [ + positionFixedWrapperDecorator(), moduleMetadata({ - imports: [TableModule], + imports: [TableModule, LayoutComponent, RouterTestingModule], + providers: [ + { + provide: I18nService, + useFactory: () => { + return new I18nMockService(mockLayoutI18n); + }, + }, + ], }), ], argTypes: { @@ -116,18 +132,20 @@ export const Scrollable: Story = { trackBy: (index: number, item: any) => item.id, }, template: ` - - - Id - Name - Other - - - {{ row.id }} - {{ row.name }} - {{ row.other }} - - + + + + Id + Name + Other + + + {{ row.id }} + {{ row.name }} + {{ row.other }} + + + `, }), }; @@ -144,17 +162,19 @@ export const Filterable: Story = { sortFn: (a: any, b: any) => a.id - b.id, }, template: ` - - - - Name - Value - - - {{ row.name }} - {{ row.value }} - - + + + + + Name + Value + + + {{ row.name }} + {{ row.value }} + + + `, }), }; diff --git a/libs/components/src/utils/has-scrolled-from.ts b/libs/components/src/utils/has-scrolled-from.ts new file mode 100644 index 00000000000..44c73465bdd --- /dev/null +++ b/libs/components/src/utils/has-scrolled-from.ts @@ -0,0 +1,41 @@ +import { CdkScrollable } from "@angular/cdk/scrolling"; +import { Signal, inject, signal } from "@angular/core"; +import { toObservable, toSignal } from "@angular/core/rxjs-interop"; +import { map, startWith, switchMap } from "rxjs"; + +export type ScrollState = { + /** `true` when the scrollbar is not at the top-most position */ + top: boolean; + + /** `true` when the scrollbar is not at the bottom-most position */ + bottom: boolean; +}; + +/** + * Check if a `CdkScrollable` instance has been scrolled + * @param scrollable The instance to check, defaults to the one provided by the current injector + * @returns {Signal} + */ +export const hasScrolledFrom = (scrollable?: Signal): Signal => { + const _scrollable = scrollable ?? signal(inject(CdkScrollable)); + const scrollable$ = toObservable(_scrollable); + + const scrollState$ = scrollable$.pipe( + switchMap((_scrollable) => + _scrollable.elementScrolled().pipe( + startWith(null), + map(() => ({ + top: _scrollable.measureScrollOffset("top") > 0, + bottom: _scrollable.measureScrollOffset("bottom") > 0, + })), + ), + ), + ); + + return toSignal(scrollState$, { + initialValue: { + top: false, + bottom: false, + }, + }); +}; From 623deea4fcf6543e34b95950ee2b5d6eb0893e56 Mon Sep 17 00:00:00 2001 From: Tom <144813356+ttalty@users.noreply.github.com> Date: Thu, 15 May 2025 10:36:57 -0400 Subject: [PATCH 28/54] Adding at risk cipher ids for accurate notifications (#14784) --- .../src/dirt/reports/risk-insights/models/password-health.ts | 1 + .../risk-insights/services/risk-insights-report.service.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index 27e135667b4..d24d8386ecd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -27,6 +27,7 @@ export type ApplicationHealthReportDetail = { applicationName: string; passwordCount: number; atRiskPasswordCount: number; + atRiskCipherIds: string[]; memberCount: number; atRiskMemberCount: number; memberDetails: MemberDetailsFlat[]; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index a503d24cad7..e4fece801b6 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -354,12 +354,15 @@ export class RiskInsightsReportService { : newUriDetail.cipherMembers, atRiskMemberDetails: existingUriDetail ? existingUriDetail.atRiskMemberDetails : [], atRiskPasswordCount: existingUriDetail ? existingUriDetail.atRiskPasswordCount : 0, + atRiskCipherIds: existingUriDetail ? existingUriDetail.atRiskCipherIds : [], atRiskMemberCount: existingUriDetail ? existingUriDetail.atRiskMemberDetails.length : 0, cipher: newUriDetail.cipher, } as ApplicationHealthReportDetail; if (isAtRisk) { reportDetail.atRiskPasswordCount = reportDetail.atRiskPasswordCount + 1; + reportDetail.atRiskCipherIds.push(newUriDetail.cipherId); + reportDetail.atRiskMemberDetails = this.getUniqueMembers( reportDetail.atRiskMemberDetails.concat(newUriDetail.cipherMembers), ); From ac49e594c1957734bfc14b5d0c04738d501d65f9 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 15 May 2025 16:44:07 +0200 Subject: [PATCH 29/54] Add standalone false to all non migrated (#14797) Adds standalone: false to all components since Angular is changing the default to true and we'd rather not have the angular PR change 300+ files. --- .../extension-anon-layout-wrapper.stories.ts | 4 ++++ apps/browser/src/auth/popup/set-password.component.ts | 1 + .../popup/settings/vault-timeout-input.component.ts | 1 + .../src/auth/popup/update-temp-password.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + .../popup/view-cache/popup-router-cache.spec.ts | 5 ++++- .../platform/popup/view-cache/popup-view-cache.spec.ts | 10 ++++++++-- apps/browser/src/popup/app.component.ts | 1 + .../popup/components/user-verification.component.ts | 1 + apps/browser/src/popup/tabs-v2.component.ts | 1 + apps/desktop/src/app/accounts/settings.component.ts | 1 + .../src/app/accounts/vault-timeout-input.component.ts | 1 + apps/desktop/src/app/app.component.ts | 1 + apps/desktop/src/app/components/avatar.component.ts | 1 + .../src/app/layout/account-switcher.component.ts | 1 + apps/desktop/src/app/layout/header.component.ts | 1 + apps/desktop/src/app/layout/search/search.component.ts | 1 + apps/desktop/src/auth/set-password.component.ts | 1 + .../desktop/src/auth/update-temp-password.component.ts | 1 + .../src/billing/app/accounts/premium.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + .../app/vault/add-edit-custom-fields.component.ts | 1 + apps/desktop/src/vault/app/vault/add-edit.component.ts | 1 + .../src/vault/app/vault/attachments.component.ts | 1 + .../src/vault/app/vault/collections.component.ts | 1 + .../src/vault/app/vault/folder-add-edit.component.ts | 1 + .../src/vault/app/vault/password-history.component.ts | 1 + apps/desktop/src/vault/app/vault/share.component.ts | 1 + .../filters/collection-filter.component.ts | 1 + .../vault-filter/filters/folder-filter.component.ts | 1 + .../filters/organization-filter.component.ts | 1 + .../vault-filter/filters/status-filter.component.ts | 1 + .../vault-filter/filters/type-filter.component.ts | 1 + .../app/vault/vault-filter/vault-filter.component.ts | 1 + .../src/vault/app/vault/vault-items.component.ts | 1 + apps/desktop/src/vault/app/vault/vault.component.ts | 1 + .../vault/app/vault/view-custom-fields.component.ts | 1 + apps/desktop/src/vault/app/vault/view.component.ts | 1 + .../group-badge/group-name-badge.component.ts | 1 + .../collections/vault-filter/vault-filter.component.ts | 1 + .../create/organization-information.component.ts | 1 + .../guards/is-enterprise-org.guard.spec.ts | 3 +++ .../organizations/guards/is-paid-org.guard.spec.ts | 3 +++ .../organizations/guards/org-redirect.guard.spec.ts | 3 +++ .../organizations/manage/events.component.ts | 1 + .../organizations/manage/group-add-edit.component.ts | 1 + .../organizations/manage/groups.component.ts | 1 + .../organizations/manage/user-confirm.component.ts | 1 + .../components/bulk/bulk-confirm-dialog.component.ts | 1 + .../components/bulk/bulk-delete-dialog.component.ts | 1 + .../components/bulk/bulk-enable-sm-dialog.component.ts | 1 + .../components/bulk/bulk-remove-dialog.component.ts | 1 + .../components/bulk/bulk-restore-revoke.component.ts | 1 + .../members/components/bulk/bulk-status.component.ts | 1 + .../member-dialog/member-dialog.component.ts | 1 + .../member-dialog/nested-checkbox.component.ts | 1 + .../members/components/reset-password.component.ts | 1 + .../organizations/members/members.component.ts | 1 + .../organizations/policies/disable-send.component.ts | 1 + .../policies/master-password.component.ts | 1 + .../policies/password-generator.component.ts | 1 + .../policies/personal-ownership.component.ts | 1 + .../organizations/policies/policies.component.ts | 1 + .../organizations/policies/policy-edit.component.ts | 1 + .../policies/remove-unlock-with-pin.component.ts | 1 + .../organizations/policies/require-sso.component.ts | 1 + .../organizations/policies/reset-password.component.ts | 1 + .../organizations/policies/send-options.component.ts | 1 + .../organizations/policies/single-org.component.ts | 1 + .../policies/two-factor-authentication.component.ts | 1 + .../organizations/reporting/reports-home.component.ts | 1 + .../organizations/settings/account.component.ts | 1 + .../settings/two-factor-setup.component.ts | 1 + .../access-selector/access-selector.component.ts | 1 + .../components/access-selector/user-type.pipe.ts | 1 + .../accept-family-sponsorship.component.ts | 1 + apps/web/src/app/app.component.ts | 1 + apps/web/src/app/auth/guards/deep-link.guard.spec.ts | 3 +++ .../login-via-webauthn/login-via-webauthn.component.ts | 1 + .../accept-organization.component.ts | 1 + apps/web/src/app/auth/recover-delete.component.ts | 1 + apps/web/src/app/auth/recover-two-factor.component.ts | 1 + apps/web/src/app/auth/set-password.component.ts | 1 + .../src/app/auth/settings/account/account.component.ts | 1 + .../settings/account/change-avatar-dialog.component.ts | 1 + .../auth/settings/account/change-email.component.ts | 1 + .../settings/account/deauthorize-sessions.component.ts | 1 + .../account/delete-account-dialog.component.ts | 1 + .../src/app/auth/settings/account/profile.component.ts | 1 + .../settings/account/selectable-avatar.component.ts | 1 + .../src/app/auth/settings/change-password.component.ts | 1 + .../confirm/emergency-access-confirm.component.ts | 1 + .../emergency-access-add-edit.component.ts | 1 + .../emergency-access/emergency-access.component.ts | 1 + .../takeover/emergency-access-takeover.component.ts | 1 + .../view/emergency-access-view.component.ts | 1 + .../app/auth/settings/security/api-key.component.ts | 1 + .../change-kdf/change-kdf-confirmation.component.ts | 1 + .../security/change-kdf/change-kdf.component.ts | 1 + .../auth/settings/security/security-keys.component.ts | 1 + .../app/auth/settings/security/security.component.ts | 1 + .../create-credential-dialog.component.ts | 1 + .../delete-credential-dialog.component.ts | 1 + .../enable-encryption-dialog.component.ts | 1 + .../webauthn-login-settings.component.ts | 1 + .../user-verification-prompt.component.ts | 1 + .../user-verification/user-verification.component.ts | 1 + apps/web/src/app/auth/update-password.component.ts | 1 + .../web/src/app/auth/update-temp-password.component.ts | 1 + apps/web/src/app/auth/verify-email-token.component.ts | 1 + .../src/app/auth/verify-recover-delete.component.ts | 1 + .../individual/billing-history-view.component.ts | 1 + .../billing/individual/premium/premium.component.ts | 1 + .../app/billing/individual/subscription.component.ts | 1 + .../billing/individual/user-subscription.component.ts | 1 + .../members/free-bitwarden-families.component.ts | 1 + .../organizations/adjust-subscription.component.ts | 1 + .../organizations/billing-sync-api-key.component.ts | 1 + .../organizations/billing-sync-key.component.ts | 1 + .../app/billing/organizations/change-plan.component.ts | 1 + .../organizations/download-license.component.ts | 1 + .../organization-billing-history-view.component.ts | 1 + .../organization-subscription-cloud.component.ts | 1 + .../organization-subscription-selfhost.component.ts | 1 + .../organization-payment-method.component.ts | 1 + .../organizations/sm-adjust-subscription.component.ts | 1 + .../organizations/sm-subscribe-standalone.component.ts | 1 + .../organizations/subscription-hidden.component.ts | 1 + .../organizations/subscription-status.component.ts | 1 + .../billing/settings/sponsored-families.component.ts | 1 + .../billing/settings/sponsoring-org-row.component.ts | 1 + .../app/billing/shared/add-credit-dialog.component.ts | 1 + .../adjust-payment-dialog.component.ts | 1 + .../adjust-storage-dialog.component.ts | 1 + .../app/billing/shared/billing-history.component.ts | 1 + .../app/billing/shared/offboarding-survey.component.ts | 1 + .../src/app/billing/shared/payment-method.component.ts | 1 + ...dividual-self-hosting-license-uploader.component.ts | 1 + ...nization-self-hosting-license-uploader.component.ts | 1 + .../src/app/billing/shared/sm-subscribe.component.ts | 1 + .../billing/shared/update-license-dialog.component.ts | 1 + .../src/app/billing/shared/update-license.component.ts | 1 + .../complete-trial-initiation.component.ts | 1 + .../trial-initiation/confirmation-details.component.ts | 1 + .../vertical-step-content.component.ts | 1 + .../vertical-stepper/vertical-step.component.ts | 1 + .../vertical-stepper/vertical-stepper.component.ts | 1 + .../environment-selector.component.ts | 1 + .../app/dirt/reports/pages/breach-report.component.ts | 1 + .../pages/exposed-passwords-report.component.ts | 1 + .../pages/inactive-two-factor-report.component.ts | 1 + .../exposed-passwords-report.component.ts | 1 + .../inactive-two-factor-report.component.ts | 1 + .../organizations/reused-passwords-report.component.ts | 1 + .../unsecured-websites-report.component.ts | 1 + .../organizations/weak-passwords-report.component.ts | 1 + .../app/dirt/reports/pages/reports-home.component.ts | 1 + .../reports/pages/reused-passwords-report.component.ts | 1 + .../pages/unsecured-websites-report.component.ts | 1 + .../reports/pages/weak-passwords-report.component.ts | 1 + .../src/app/dirt/reports/reports-layout.component.ts | 1 + .../shared/report-card/report-card.component.ts | 1 + .../shared/report-list/report-list.component.ts | 1 + .../key-connector/remove-password.component.ts | 1 + apps/web/src/app/layouts/frontend-layout.component.ts | 1 + .../web/src/app/layouts/header/web-header.component.ts | 1 + .../navigation-switcher.component.ts | 1 + .../navigation-switcher/navigation-switcher.stories.ts | 4 ++++ .../product-switcher-content.component.ts | 1 + .../product-switcher/product-switcher.component.ts | 1 + .../product-switcher/product-switcher.stories.ts | 4 ++++ apps/web/src/app/settings/domain-rules.component.ts | 1 + apps/web/src/app/settings/preferences.component.ts | 1 + .../components/onboarding/onboarding-task.component.ts | 1 + .../components/onboarding/onboarding.component.ts | 1 + .../app/vault/components/premium-badge.component.ts | 1 + .../vault-items/vault-cipher-row.component.ts | 1 + .../vault-items/vault-collection-row.component.ts | 1 + .../components/vault-items/vault-items.component.ts | 3 +-- .../bulk-delete-dialog/bulk-delete-dialog.component.ts | 1 + .../bulk-move-dialog/bulk-move-dialog.component.ts | 1 + .../individual-vault/folder-add-edit.component.ts | 1 + .../organization-name-badge.component.ts | 1 + .../individual-vault/pipes/get-group-name.pipe.ts | 1 + .../pipes/get-organization-name.pipe.ts | 1 + .../components/organization-options.component.ts | 1 + .../vault-filter/components/vault-filter.component.ts | 1 + .../components/vault-filter-section.component.ts | 1 + .../src/app/vault/settings/purge-vault.component.ts | 1 + .../domain-add-edit-dialog.component.ts | 1 + .../domain-verification.component.ts | 1 + .../organizations/manage/scim.component.ts | 1 + .../policies/activate-autofill.component.ts | 1 + .../policies/automatic-app-login.component.ts | 1 + .../disable-personal-vault-export.component.ts | 1 + .../policies/maximum-vault-timeout.component.ts | 1 + .../providers/clients/add-organization.component.ts | 1 + .../providers/clients/create-organization.component.ts | 1 + .../providers/manage/accept-provider.component.ts | 1 + .../manage/dialogs/add-edit-member-dialog.component.ts | 1 + .../manage/dialogs/bulk-confirm-dialog.component.ts | 1 + .../manage/dialogs/bulk-remove-dialog.component.ts | 1 + .../admin-console/providers/manage/events.component.ts | 1 + .../providers/manage/members.component.ts | 1 + .../app/admin-console/providers/providers.component.ts | 1 + .../providers/settings/account.component.ts | 1 + .../providers/setup/setup-provider.component.ts | 1 + .../admin-console/providers/setup/setup.component.ts | 1 + .../verify-recover-delete-provider.component.ts | 1 + bitwarden_license/bit-web/src/app/app.component.ts | 1 + .../bit-web/src/app/auth/sso/sso.component.ts | 1 + .../policies/free-families-sponsorship.component.ts | 1 + .../provider-billing-history.component.ts | 1 + .../add-existing-organization-dialog.component.ts | 1 + .../clients/create-client-dialog.component.ts | 1 + .../clients/manage-client-name-dialog.component.ts | 1 + .../manage-client-subscription-dialog.component.ts | 1 + .../providers/setup/setup-business-unit.component.ts | 1 + .../provider-subscription-status.component.ts | 1 + .../subscription/provider-subscription.component.ts | 1 + .../integrations/integrations.component.spec.ts | 2 ++ .../integrations/integrations.component.ts | 1 + .../src/app/secrets-manager/layout/layout.component.ts | 1 + .../app/secrets-manager/layout/navigation.component.ts | 1 + .../app/secrets-manager/overview/overview.component.ts | 1 + .../app/secrets-manager/overview/section.component.ts | 1 + .../projects/dialog/project-delete-dialog.component.ts | 1 + .../projects/dialog/project-dialog.component.ts | 1 + .../projects/guards/project-access.guard.spec.ts | 2 ++ .../projects/project/project-people.component.ts | 1 + .../projects/project/project-secrets.component.ts | 1 + .../project/project-service-accounts.component.ts | 1 + .../projects/project/project.component.ts | 1 + .../projects/projects/projects.component.ts | 1 + .../secrets/dialog/secret-delete.component.ts | 1 + .../secrets/dialog/secret-dialog.component.ts | 1 + .../secrets/dialog/secret-view-dialog.component.ts | 1 + .../app/secrets-manager/secrets/secrets.component.ts | 1 + .../service-accounts/access/access-list.component.ts | 1 + .../service-accounts/access/access-tokens.component.ts | 1 + .../dialogs/access-token-create-dialog.component.ts | 1 + .../access/dialogs/access-token-dialog.component.ts | 1 + .../access/dialogs/expiration-options.component.ts | 1 + .../service-accounts/config/config.component.ts | 1 + .../dialog/service-account-delete-dialog.component.ts | 1 + .../dialog/service-account-dialog.component.ts | 1 + .../event-logs/service-accounts-events.component.ts | 1 + .../guards/service-account-access.guard.spec.ts | 2 ++ .../people/service-account-people.component.ts | 1 + .../projects/service-account-projects.component.ts | 1 + .../service-accounts/service-account.component.ts | 1 + .../service-accounts-list.component.ts | 1 + .../service-accounts/service-accounts.component.ts | 1 + .../dialog/sm-import-error-dialog.component.ts | 1 + .../settings/porting/sm-export.component.ts | 1 + .../settings/porting/sm-import.component.ts | 1 + .../access-policy-selector.component.ts | 1 + .../dialogs/bulk-confirmation-dialog.component.ts | 1 + .../shared/dialogs/bulk-status-dialog.component.ts | 1 + .../app/secrets-manager/shared/new-menu.component.ts | 1 + .../secrets-manager/shared/org-suspended.component.ts | 1 + .../secrets-manager/shared/projects-list.component.ts | 1 + .../secrets-manager/shared/secrets-list.component.ts | 1 + .../trash/dialog/secret-hard-delete.component.ts | 1 + .../trash/dialog/secret-restore.component.ts | 1 + .../src/app/secrets-manager/trash/trash.component.ts | 1 + .../auth/components/environment-selector.component.ts | 1 + .../src/auth/components/two-factor-icon.component.ts | 1 + .../src/auth/components/user-verification.component.ts | 1 + .../add-account-credit-dialog.component.ts | 1 + .../billing/components/invoices/invoices.component.ts | 1 + .../components/invoices/no-invoices.component.ts | 1 + .../manage-tax-information.component.ts | 1 + .../src/billing/directives/not-premium.directive.ts | 1 + libs/angular/src/components/callout.component.ts | 1 + .../src/components/modal/dynamic-modal.component.ts | 1 + libs/angular/src/directives/a11y-invalid.directive.ts | 1 + libs/angular/src/directives/api-action.directive.ts | 1 + libs/angular/src/directives/box-row.directive.ts | 1 + libs/angular/src/directives/copy-text.directive.ts | 1 + libs/angular/src/directives/fallback-src.directive.ts | 1 + libs/angular/src/directives/if-feature.directive.ts | 1 + .../src/directives/input-strip-spaces.directive.ts | 1 + .../angular/src/directives/input-verbatim.directive.ts | 1 + libs/angular/src/directives/launch-click.directive.ts | 1 + libs/angular/src/directives/stop-click.directive.ts | 1 + libs/angular/src/directives/stop-prop.directive.ts | 1 + .../src/directives/true-false-value.directive.ts | 1 + libs/angular/src/pipes/color-password-count.pipe.ts | 5 ++++- libs/angular/src/pipes/color-password.pipe.ts | 5 ++++- libs/angular/src/pipes/credit-card-number.pipe.ts | 5 ++++- libs/angular/src/pipes/search-ciphers.pipe.ts | 1 + libs/angular/src/pipes/search.pipe.ts | 1 + libs/angular/src/pipes/user-name.pipe.ts | 1 + libs/angular/src/pipes/user-type.pipe.ts | 1 + libs/angular/src/platform/pipes/ellipsis.pipe.ts | 1 + libs/angular/src/platform/pipes/fingerprint.pipe.ts | 1 + libs/angular/src/platform/pipes/i18n.pipe.ts | 1 + .../password-strength/password-strength.component.ts | 1 + libs/angular/src/vault/components/icon.component.ts | 1 + libs/components/src/app/app.component.ts | 1 + libs/components/src/input/autofocus.directive.ts | 1 + .../components/src/catchall-settings.component.ts | 1 + .../components/src/credential-generator.component.ts | 1 + .../components/src/forwarder-settings.component.ts | 1 + .../components/src/passphrase-settings.component.ts | 1 + .../components/src/password-generator.component.ts | 1 + .../components/src/password-settings.component.ts | 1 + .../components/src/subaddress-settings.component.ts | 1 + .../components/src/username-generator.component.ts | 1 + .../components/src/username-settings.component.ts | 1 + 311 files changed, 350 insertions(+), 8 deletions(-) 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 a0990485d49..78ca577a69d 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 @@ -168,18 +168,21 @@ type Story = StoryObj; @Component({ selector: "bit-default-primary-outlet-example-component", template: "

Primary Outlet Example:
your primary component goes here

", + standalone: false, }) class DefaultPrimaryOutletExampleComponent {} @Component({ selector: "bit-default-secondary-outlet-example-component", template: "

Secondary Outlet Example:
your secondary component goes here

", + standalone: false, }) class DefaultSecondaryOutletExampleComponent {} @Component({ selector: "bit-default-env-selector-outlet-example-component", template: "

Env Selector Outlet Example:
your env selector component goes here

", + standalone: false, }) class DefaultEnvSelectorOutletExampleComponent {} @@ -264,6 +267,7 @@ const changedData: ExtensionAnonLayoutWrapperData = { template: ` `, + standalone: false, }) export class DynamicContentExampleComponent { initialData = true; diff --git a/apps/browser/src/auth/popup/set-password.component.ts b/apps/browser/src/auth/popup/set-password.component.ts index accde2e9a09..2a796854531 100644 --- a/apps/browser/src/auth/popup/set-password.component.ts +++ b/apps/browser/src/auth/popup/set-password.component.ts @@ -5,5 +5,6 @@ import { SetPasswordComponent as BaseSetPasswordComponent } from "@bitwarden/ang @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent {} diff --git a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts +++ b/apps/browser/src/auth/popup/settings/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/browser/src/auth/popup/update-temp-password.component.ts b/apps/browser/src/auth/popup/update-temp-password.component.ts index 465bc3f7038..e8cf64b7548 100644 --- a/apps/browser/src/auth/popup/update-temp-password.component.ts +++ b/apps/browser/src/auth/popup/update-temp-password.component.ts @@ -8,6 +8,7 @@ import { postLogoutMessageListener$ } from "./utils/post-logout-message-listener @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent { onSuccessfulChangePassword: () => Promise = this.doOnSuccessfulChangePassword.bind(this); diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ b/apps/browser/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts index 465a6e6c69c..22fb7bf99b9 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-router-cache.spec.ts @@ -13,7 +13,10 @@ import { PopupRouterCacheService, popupRouterCacheGuard } from "./popup-router-c const flushPromises = async () => await new Promise(process.nextTick); -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} describe("Popup router cache guard", () => { diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts index 2ec75791d1b..60baf94eeae 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.spec.ts @@ -19,10 +19,16 @@ import { import { PopupViewCacheService } from "./popup-view-cache.service"; -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class EmptyComponent {} -@Component({ template: "" }) +@Component({ + template: "", + standalone: false, +}) export class TestComponent { private viewCacheService = inject(PopupViewCacheService); diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index a480e1d6ba3..5f7fbc1fad7 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -43,6 +43,7 @@ import { DesktopSyncVerificationDialogComponent } from "./components/desktop-syn
`, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { private compactModeService = inject(PopupCompactModeService); diff --git a/apps/browser/src/popup/components/user-verification.component.ts b/apps/browser/src/popup/components/user-verification.component.ts index 6befc8973b0..f6cb6cdff12 100644 --- a/apps/browser/src/popup/components/user-verification.component.ts +++ b/apps/browser/src/popup/components/user-verification.component.ts @@ -22,5 +22,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 63b539fddce..5df8fb85d6d 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -13,6 +13,7 @@ import { NavButton } from "../platform/popup/layout/popup-tab-navigation.compone @Component({ selector: "app-tabs-v2", templateUrl: "./tabs-v2.component.html", + standalone: false, }) export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 2639d819854..83c982fbaba 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -54,6 +54,7 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man @Component({ selector: "app-settings", templateUrl: "settings.component.html", + standalone: false, }) export class SettingsComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts index c56e6578a0b..25a4d01333d 100644 --- a/apps/desktop/src/app/accounts/vault-timeout-input.component.ts +++ b/apps/desktop/src/app/accounts/vault-timeout-input.component.ts @@ -18,5 +18,6 @@ import { VaultTimeoutInputComponent as VaultTimeoutInputComponentBase } from "@b useExisting: VaultTimeoutInputComponent, }, ], + standalone: false, }) export class VaultTimeoutInputComponent extends VaultTimeoutInputComponentBase {} diff --git a/apps/desktop/src/app/app.component.ts b/apps/desktop/src/app/app.component.ts index c3cfdf49c2c..38c5ca3a2a8 100644 --- a/apps/desktop/src/app/app.component.ts +++ b/apps/desktop/src/app/app.component.ts @@ -93,6 +93,7 @@ const SyncInterval = 6 * 60 * 60 * 1000; // 6 hours `, + standalone: false, }) export class AppComponent implements OnInit, OnDestroy { @ViewChild("settings", { read: ViewContainerRef, static: true }) settingsRef: ViewContainerRef; diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index d2660763667..1fba864686c 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -8,6 +8,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-avatar", template: ``, + standalone: false, }) export class AvatarComponent implements OnChanges, OnInit { @Input() size = 45; diff --git a/apps/desktop/src/app/layout/account-switcher.component.ts b/apps/desktop/src/app/layout/account-switcher.component.ts index d8ffa5ae546..a54674c3a1e 100644 --- a/apps/desktop/src/app/layout/account-switcher.component.ts +++ b/apps/desktop/src/app/layout/account-switcher.component.ts @@ -54,6 +54,7 @@ type InactiveAccount = ActiveAccount & { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class AccountSwitcherComponent implements OnInit { activeAccount$: Observable; diff --git a/apps/desktop/src/app/layout/header.component.ts b/apps/desktop/src/app/layout/header.component.ts index 1cf697ad4ed..9aef093423f 100644 --- a/apps/desktop/src/app/layout/header.component.ts +++ b/apps/desktop/src/app/layout/header.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "app-header", templateUrl: "header.component.html", + standalone: false, }) export class HeaderComponent {} diff --git a/apps/desktop/src/app/layout/search/search.component.ts b/apps/desktop/src/app/layout/search/search.component.ts index a9faf2a2414..70196d74dda 100644 --- a/apps/desktop/src/app/layout/search/search.component.ts +++ b/apps/desktop/src/app/layout/search/search.component.ts @@ -11,6 +11,7 @@ import { SearchBarService, SearchBarState } from "./search-bar.service"; @Component({ selector: "app-search", templateUrl: "search.component.html", + standalone: false, }) export class SearchComponent implements OnInit, OnDestroy { state: SearchBarState; diff --git a/apps/desktop/src/auth/set-password.component.ts b/apps/desktop/src/auth/set-password.component.ts index 5a78fb08c47..48b18d7294c 100644 --- a/apps/desktop/src/auth/set-password.component.ts +++ b/apps/desktop/src/auth/set-password.component.ts @@ -28,6 +28,7 @@ const BroadcasterSubscriptionId = "SetPasswordComponent"; @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent implements OnInit, OnDestroy { constructor( diff --git a/apps/desktop/src/auth/update-temp-password.component.ts b/apps/desktop/src/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/desktop/src/auth/update-temp-password.component.ts +++ b/apps/desktop/src/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/desktop/src/billing/app/accounts/premium.component.ts b/apps/desktop/src/billing/app/accounts/premium.component.ts index 1b4573fe6d6..5d0fa7a5dde 100644 --- a/apps/desktop/src/billing/app/accounts/premium.component.ts +++ b/apps/desktop/src/billing/app/accounts/premium.component.ts @@ -13,6 +13,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-premium", templateUrl: "premium.component.html", + standalone: false, }) export class PremiumComponent extends BasePremiumComponent { constructor( diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ b/apps/desktop/src/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts index 6992455a8a6..b4be2406c4b 100644 --- a/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit-custom-fields.component.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Component({ selector: "app-vault-add-edit-custom-fields", templateUrl: "add-edit-custom-fields.component.html", + standalone: false, }) export class AddEditCustomFieldsComponent extends BaseAddEditCustomFieldsComponent { constructor(i18nService: I18nService, eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/add-edit.component.ts b/apps/desktop/src/vault/app/vault/add-edit.component.ts index 2c8b5a8321a..eb04003a418 100644 --- a/apps/desktop/src/vault/app/vault/add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/add-edit.component.ts @@ -30,6 +30,7 @@ const BroadcasterSubscriptionId = "AddEditComponent"; @Component({ selector: "app-vault-add-edit", templateUrl: "add-edit.component.html", + standalone: false, }) export class AddEditComponent extends BaseAddEditComponent implements OnInit, OnChanges, OnDestroy { @ViewChild("form") diff --git a/apps/desktop/src/vault/app/vault/attachments.component.ts b/apps/desktop/src/vault/app/vault/attachments.component.ts index a2cea5f2722..a116a4d2acb 100644 --- a/apps/desktop/src/vault/app/vault/attachments.component.ts +++ b/apps/desktop/src/vault/app/vault/attachments.component.ts @@ -18,6 +18,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-vault-attachments", templateUrl: "attachments.component.html", + standalone: false, }) export class AttachmentsComponent extends BaseAttachmentsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/collections.component.ts b/apps/desktop/src/vault/app/vault/collections.component.ts index e7684c3c07a..46455d04cd2 100644 --- a/apps/desktop/src/vault/app/vault/collections.component.ts +++ b/apps/desktop/src/vault/app/vault/collections.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-vault-collections", templateUrl: "collections.component.html", + standalone: false, }) export class CollectionsComponent extends BaseCollectionsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts index cdb879693c0..cecd5cd671c 100644 --- a/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts +++ b/apps/desktop/src/vault/app/vault/folder-add-edit.component.ts @@ -14,6 +14,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/password-history.component.ts b/apps/desktop/src/vault/app/vault/password-history.component.ts index 4a87617d8f4..e83ce0d56ea 100644 --- a/apps/desktop/src/vault/app/vault/password-history.component.ts +++ b/apps/desktop/src/vault/app/vault/password-history.component.ts @@ -10,6 +10,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-password-history", templateUrl: "password-history.component.html", + standalone: false, }) export class PasswordHistoryComponent extends BasePasswordHistoryComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/share.component.ts b/apps/desktop/src/vault/app/vault/share.component.ts index 6926e7e2abf..50842439323 100644 --- a/apps/desktop/src/vault/app/vault/share.component.ts +++ b/apps/desktop/src/vault/app/vault/share.component.ts @@ -13,6 +13,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi @Component({ selector: "app-vault-share", templateUrl: "share.component.html", + standalone: false, }) export class ShareComponent extends BaseShareComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts index 161c9ae5353..22372410e5b 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/collection-filter.component.ts @@ -5,5 +5,6 @@ import { CollectionFilterComponent as BaseCollectionFilterComponent } from "@bit @Component({ selector: "app-collection-filter", templateUrl: "collection-filter.component.html", + standalone: false, }) export class CollectionFilterComponent extends BaseCollectionFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts index 790d31a65e6..d7364808f6d 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/folder-filter.component.ts @@ -5,5 +5,6 @@ import { FolderFilterComponent as BaseFolderFilterComponent } from "@bitwarden/a @Component({ selector: "app-folder-filter", templateUrl: "folder-filter.component.html", + standalone: false, }) export class FolderFilterComponent extends BaseFolderFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts index 39f1c0200ea..33a47cdc91f 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/organization-filter.component.ts @@ -12,6 +12,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-organization-filter", templateUrl: "organization-filter.component.html", + standalone: false, }) export class OrganizationFilterComponent extends BaseOrganizationFilterComponent { get show() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts index 5d43fd52d20..276b11d7138 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/status-filter.component.ts @@ -5,5 +5,6 @@ import { StatusFilterComponent as BaseStatusFilterComponent } from "@bitwarden/a @Component({ selector: "app-status-filter", templateUrl: "status-filter.component.html", + standalone: false, }) export class StatusFilterComponent extends BaseStatusFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts index 5727cc0e9d5..5920233b206 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/filters/type-filter.component.ts @@ -5,6 +5,7 @@ import { TypeFilterComponent as BaseTypeFilterComponent } from "@bitwarden/angul @Component({ selector: "app-type-filter", templateUrl: "type-filter.component.html", + standalone: false, }) export class TypeFilterComponent extends BaseTypeFilterComponent { constructor() { diff --git a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts index 12ac1fef425..161d22687e8 100644 --- a/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-filter/vault-filter.component.ts @@ -5,5 +5,6 @@ import { VaultFilterComponent as BaseVaultFilterComponent } from "@bitwarden/ang @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent {} diff --git a/apps/desktop/src/vault/app/vault/vault-items.component.ts b/apps/desktop/src/vault/app/vault/vault-items.component.ts index d5838459ff7..2d1ba784753 100644 --- a/apps/desktop/src/vault/app/vault/vault-items.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-items.component.ts @@ -14,6 +14,7 @@ import { SearchBarService } from "../../../app/layout/search/search-bar.service" @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", + standalone: false, }) export class VaultItemsComponent extends BaseVaultItemsComponent { constructor( diff --git a/apps/desktop/src/vault/app/vault/vault.component.ts b/apps/desktop/src/vault/app/vault/vault.component.ts index 6c0d5ef81d0..560855347b3 100644 --- a/apps/desktop/src/vault/app/vault/vault.component.ts +++ b/apps/desktop/src/vault/app/vault/vault.component.ts @@ -57,6 +57,7 @@ const BroadcasterSubscriptionId = "VaultComponent"; @Component({ selector: "app-vault", templateUrl: "vault.component.html", + standalone: false, }) export class VaultComponent implements OnInit, OnDestroy { @ViewChild(ViewComponent) viewComponent: ViewComponent; diff --git a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts index 249f83c4444..efe61ad1fa7 100644 --- a/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts +++ b/apps/desktop/src/vault/app/vault/view-custom-fields.component.ts @@ -6,6 +6,7 @@ import { EventCollectionService } from "@bitwarden/common/abstractions/event/eve @Component({ selector: "app-vault-view-custom-fields", templateUrl: "view-custom-fields.component.html", + standalone: false, }) export class ViewCustomFieldsComponent extends BaseViewCustomFieldsComponent { constructor(eventCollectionService: EventCollectionService) { diff --git a/apps/desktop/src/vault/app/vault/view.component.ts b/apps/desktop/src/vault/app/vault/view.component.ts index e74b07445da..084a9a747ed 100644 --- a/apps/desktop/src/vault/app/vault/view.component.ts +++ b/apps/desktop/src/vault/app/vault/view.component.ts @@ -42,6 +42,7 @@ const BroadcasterSubscriptionId = "ViewComponent"; @Component({ selector: "app-vault-view", templateUrl: "view.component.html", + standalone: false, }) export class ViewComponent extends BaseViewComponent implements OnInit, OnDestroy, OnChanges { @Output() onViewCipherPasswordHistory = new EventEmitter(); diff --git a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts index 8e5f261bc26..8f703acf9af 100644 --- a/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/group-badge/group-name-badge.component.ts @@ -10,6 +10,7 @@ import { GroupView } from "../../core"; @Component({ selector: "app-group-badge", templateUrl: "group-name-badge.component.html", + standalone: false, }) export class GroupNameBadgeComponent implements OnChanges { @Input() selectedGroups: SelectionReadOnlyRequest[]; diff --git a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts index 13e90d5275c..f7d7acfdc2d 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault-filter/vault-filter.component.ts @@ -26,6 +26,7 @@ import { CollectionFilter } from "../../../../vault/individual-vault/vault-filte selector: "app-organization-vault-filter", templateUrl: "../../../../vault/individual-vault/vault-filter/components/vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent extends BaseVaultFilterComponent diff --git a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts index fc168f842dc..cd14b73a156 100644 --- a/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts +++ b/apps/web/src/app/admin-console/organizations/create/organization-information.component.ts @@ -9,6 +9,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv @Component({ selector: "app-org-info", templateUrl: "organization-information.component.html", + standalone: false, }) export class OrganizationInformationComponent implements OnInit { @Input() nameOnly = false; diff --git a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts index f5fce0e5e42..bc4a942301a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-enterprise-org.guard.spec.ts @@ -21,16 +21,19 @@ import { isEnterpriseOrgGuard } from "./is-enterprise-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a enterprise organization!

", + standalone: false, }) export class IsEnterpriseOrganizationComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts index 8efed8cefa2..ab5fd79321a 100644 --- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.spec.ts @@ -20,16 +20,19 @@ import { isPaidOrgGuard } from "./is-paid-org.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This component can only be accessed by a paid organization!

", + standalone: false, }) export class PaidOrganizationOnlyComponent {} @Component({ template: "

This is the organization upgrade screen!

", + standalone: false, }) export class OrganizationUpgradeScreenComponent {} diff --git a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts index fa348867a86..9dc084484f3 100644 --- a/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts +++ b/apps/web/src/app/admin-console/organizations/guards/org-redirect.guard.spec.ts @@ -19,16 +19,19 @@ import { organizationRedirectGuard } from "./org-redirect.guard"; @Component({ template: "

This is the home screen!

", + standalone: false, }) export class HomescreenComponent {} @Component({ template: "

This is the admin console!

", + standalone: false, }) export class AdminConsoleComponent {} @Component({ template: "

This is a subroute of the admin console!

", + standalone: false, }) export class AdminConsoleSubrouteComponent {} 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 737a38ee2ab..3daa6c17d07 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 @@ -48,6 +48,7 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record = { @Component({ selector: "app-org-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit, OnDestroy { exportFileName = "org-events"; 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 f29b4b642cb..53a6a3cf196 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 @@ -110,6 +110,7 @@ export const openGroupAddEditDialog = ( @Component({ selector: "app-group-add-edit", templateUrl: "group-add-edit.component.html", + standalone: false, }) export class GroupAddEditComponent implements OnInit, OnDestroy { private organization$ = this.accountService.activeAccount$.pipe( diff --git a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts index 669dcc251ca..6459cd1f857 100644 --- a/apps/web/src/app/admin-console/organizations/manage/groups.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/groups.component.ts @@ -75,6 +75,7 @@ const groupsFilter = (filter: string) => { @Component({ templateUrl: "groups.component.html", + standalone: false, }) export class GroupsComponent { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts index b5068ba55a6..03b77cfaa71 100644 --- a/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/user-confirm.component.ts @@ -18,6 +18,7 @@ export type UserConfirmDialogData = { @Component({ selector: "app-user-confirm", templateUrl: "user-confirm.component.html", + standalone: false, }) export class UserConfirmComponent implements OnInit { name: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts index c19984f980d..4ec50799ae0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.ts @@ -33,6 +33,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { organizationId: string; diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts index 51ba98fabb9..8fb60e85b08 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-delete-dialog.component.ts @@ -18,6 +18,7 @@ type BulkDeleteDialogParams = { @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { organizationId: string; 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 e01809789f3..9132625c587 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 @@ -22,6 +22,7 @@ export type BulkEnableSecretsManagerDialogData = { @Component({ templateUrl: `bulk-enable-sm-dialog.component.html`, + standalone: false, }) export class BulkEnableSecretsManagerDialogComponent implements OnInit { protected dataSource = new TableDataSource(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts index 00711e355cb..5bbc6f093f0 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.ts @@ -21,6 +21,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { organizationId: string; 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 5cb2c2e3d4e..ac99a9b51de 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 @@ -18,6 +18,7 @@ type BulkRestoreDialogParams = { @Component({ selector: "app-bulk-restore-revoke", templateUrl: "bulk-restore-revoke.component.html", + standalone: false, }) export class BulkRestoreRevokeComponent { isRevoking: boolean; 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 b8a2f45053b..078ba6c1fd1 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 @@ -41,6 +41,7 @@ type BulkStatusDialogData = { @Component({ selector: "app-bulk-status", templateUrl: "bulk-status.component.html", + standalone: false, }) export class BulkStatusComponent implements OnInit { users: BulkStatusEntry[]; 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 8349b44c735..10bbc5cfe52 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 @@ -106,6 +106,7 @@ export enum MemberDialogResult { @Component({ templateUrl: "member-dialog.component.html", + standalone: false, }) export class MemberDialogComponent implements OnDestroy { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts index 648a5a6ff26..9a2025c2b30 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/nested-checkbox.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-nested-checkbox", templateUrl: "nested-checkbox.component.html", + standalone: false, }) export class NestedCheckboxComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts index 4e78d4dc91f..80f0745f6d5 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts @@ -59,6 +59,7 @@ export enum ResetPasswordDialogResult { @Component({ selector: "app-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) /** * Used in a dialog for initiating the account recovery process against a 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 6a3ca58b73d..834aa2c7111 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 @@ -88,6 +88,7 @@ class MembersTableDataSource extends PeopleTableDataSource @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { userType = OrganizationUserType; diff --git a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts index 2acf175e3da..b323ac00d34 100644 --- a/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/disable-send.component.ts @@ -14,5 +14,6 @@ export class DisableSendPolicy extends BasePolicy { @Component({ selector: "policy-disable-send", templateUrl: "disable-send.component.html", + standalone: false, }) export class DisableSendPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts index 328989df66b..54cf1be88fc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/master-password.component.ts @@ -28,6 +28,7 @@ export class MasterPasswordPolicy extends BasePolicy { @Component({ selector: "policy-master-password", templateUrl: "master-password.component.html", + standalone: false, }) export class MasterPasswordPolicyComponent extends BasePolicyComponent implements OnInit { MinPasswordLength = Utils.minimumPasswordLength; diff --git a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts index 4439f974e55..f11b14aea38 100644 --- a/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/password-generator.component.ts @@ -21,6 +21,7 @@ export class PasswordGeneratorPolicy extends BasePolicy { @Component({ selector: "policy-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorPolicyComponent extends BasePolicyComponent { // these properties forward the application default settings to the UI diff --git a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts index 36c79a61fe4..ef92ee90581 100644 --- a/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/personal-ownership.component.ts @@ -14,5 +14,6 @@ export class PersonalOwnershipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "personal-ownership.component.html", + standalone: false, }) export class PersonalOwnershipPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts index 6e3b34eaa30..73f0d99b4f9 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policies.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policies.component.ts @@ -31,6 +31,7 @@ import { PolicyEditComponent, PolicyEditDialogResult } from "./policy-edit.compo @Component({ selector: "app-org-policies", templateUrl: "policies.component.html", + standalone: false, }) export class PoliciesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index 4d722840e23..d3d03d2aaae 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -50,6 +50,7 @@ export enum PolicyEditDialogResult { @Component({ selector: "app-policy-edit", templateUrl: "policy-edit.component.html", + standalone: false, }) export class PolicyEditComponent implements AfterViewInit { @ViewChild("policyForm", { read: ViewContainerRef, static: true }) diff --git a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts index b737c803b5f..0d0f42b603e 100644 --- a/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/remove-unlock-with-pin.component.ts @@ -14,5 +14,6 @@ export class RemoveUnlockWithPinPolicy extends BasePolicy { @Component({ selector: "remove-unlock-with-pin", templateUrl: "remove-unlock-with-pin.component.html", + standalone: false, }) export class RemoveUnlockWithPinPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts index ea85168f986..21de143dea6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/require-sso.component.ts @@ -19,5 +19,6 @@ export class RequireSsoPolicy extends BasePolicy { @Component({ selector: "policy-require-sso", templateUrl: "require-sso.component.html", + standalone: false, }) export class RequireSsoPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts index 80e4e5254ff..62fc42f6a06 100644 --- a/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/reset-password.component.ts @@ -27,6 +27,7 @@ export class ResetPasswordPolicy extends BasePolicy { @Component({ selector: "policy-reset-password", templateUrl: "reset-password.component.html", + standalone: false, }) export class ResetPasswordPolicyComponent extends BasePolicyComponent implements OnInit { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts index af6d1f38694..9a0a8871296 100644 --- a/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/send-options.component.ts @@ -15,6 +15,7 @@ export class SendOptionsPolicy extends BasePolicy { @Component({ selector: "policy-send-options", templateUrl: "send-options.component.html", + standalone: false, }) export class SendOptionsPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts index a40ec87bf6a..ad32b4218bc 100644 --- a/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/single-org.component.ts @@ -14,6 +14,7 @@ export class SingleOrgPolicy extends BasePolicy { @Component({ selector: "policy-single-org", templateUrl: "single-org.component.html", + standalone: false, }) export class SingleOrgPolicyComponent extends BasePolicyComponent implements OnInit { async ngOnInit() { diff --git a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts index 51151808872..691e12c72f6 100644 --- a/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/two-factor-authentication.component.ts @@ -14,5 +14,6 @@ export class TwoFactorAuthenticationPolicy extends BasePolicy { @Component({ selector: "policy-two-factor-authentication", templateUrl: "two-factor-authentication.component.html", + standalone: false, }) export class TwoFactorAuthenticationPolicyComponent extends BasePolicyComponent {} diff --git a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts index 9fd3170b73a..52cb24c90d1 100644 --- a/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts +++ b/apps/web/src/app/admin-console/organizations/reporting/reports-home.component.ts @@ -17,6 +17,7 @@ import { ReportVariant, reports, ReportType, ReportEntry } from "../../../dirt/r @Component({ selector: "app-org-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports$: Observable; diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts index f3997fe669e..b376c48b39a 100644 --- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts @@ -41,6 +41,7 @@ import { DeleteOrganizationDialogResult, openDeleteOrganizationDialog } from "./ @Component({ selector: "app-org-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { selfHosted = false; diff --git a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts index 014b8e3a3ef..d14e912f83e 100644 --- a/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/two-factor-setup.component.ts @@ -29,6 +29,7 @@ import { TwoFactorVerifyComponent } from "../../../auth/settings/two-factor/two- @Component({ selector: "app-two-factor-setup", templateUrl: "../../../auth/settings/two-factor/two-factor-setup.component.html", + standalone: false, }) export class TwoFactorSetupComponent extends BaseTwoFactorSetupComponent implements OnInit { tabbedHeader = false; diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index 1db1fc8a06e..366df34b2b8 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -55,6 +55,7 @@ export enum PermissionMode { multi: true, }, ], + standalone: false, }) export class AccessSelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts index 3d43c63efb0..673d09ec0f0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts index 7ceaed28f80..4df6defe8ad 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/accept-family-sponsorship.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component"; @Component({ selector: "app-accept-family-sponsorship", templateUrl: "accept-family-sponsorship.component.html", + standalone: false, }) export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/apps/web/src/app/app.component.ts b/apps/web/src/app/app.component.ts index b94ce004313..cac0487d05d 100644 --- a/apps/web/src/app/app.component.ts +++ b/apps/web/src/app/app.component.ts @@ -49,6 +49,7 @@ const IdleTimeout = 60000 * 10; // 10 minutes @Component({ selector: "app-root", templateUrl: "app.component.html", + standalone: false, }) export class AppComponent implements OnDestroy, OnInit { private lastActivity: Date = null; diff --git a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts index f9ced556e47..82ed004cf54 100644 --- a/apps/web/src/app/auth/guards/deep-link.guard.spec.ts +++ b/apps/web/src/app/auth/guards/deep-link.guard.spec.ts @@ -13,16 +13,19 @@ import { deepLinkGuard } from "./deep-link.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class LockTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts index 8f3a5ca3c37..d4a381159ab 100644 --- a/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts +++ b/apps/web/src/app/auth/login/login-via-webauthn/login-via-webauthn.component.ts @@ -7,6 +7,7 @@ import { CreatePasskeyIcon } from "@bitwarden/angular/auth/icons/create-passkey. @Component({ selector: "app-login-via-webauthn", templateUrl: "login-via-webauthn.component.html", + standalone: false, }) export class LoginViaWebAuthnComponent extends BaseLoginViaWebAuthnComponent { protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon }; diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts index 197b4031998..838a3029711 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.component.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.component.ts @@ -14,6 +14,7 @@ import { OrganizationInvite } from "./organization-invite"; @Component({ templateUrl: "accept-organization.component.html", + standalone: false, }) export class AcceptOrganizationComponent extends BaseAcceptComponent { orgName$ = this.acceptOrganizationInviteService.orgName$; diff --git a/apps/web/src/app/auth/recover-delete.component.ts b/apps/web/src/app/auth/recover-delete.component.ts index 6b45421911d..7381d526879 100644 --- a/apps/web/src/app/auth/recover-delete.component.ts +++ b/apps/web/src/app/auth/recover-delete.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-delete", templateUrl: "recover-delete.component.html", + standalone: false, }) export class RecoverDeleteComponent { protected recoverDeleteForm = new FormGroup({ diff --git a/apps/web/src/app/auth/recover-two-factor.component.ts b/apps/web/src/app/auth/recover-two-factor.component.ts index 106cd32a945..a69da0a66bf 100644 --- a/apps/web/src/app/auth/recover-two-factor.component.ts +++ b/apps/web/src/app/auth/recover-two-factor.component.ts @@ -16,6 +16,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-recover-two-factor", templateUrl: "recover-two-factor.component.html", + standalone: false, }) export class RecoverTwoFactorComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/auth/set-password.component.ts b/apps/web/src/app/auth/set-password.component.ts index ccd329dd640..e297426f2c1 100644 --- a/apps/web/src/app/auth/set-password.component.ts +++ b/apps/web/src/app/auth/set-password.component.ts @@ -11,6 +11,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-set-password", templateUrl: "set-password.component.html", + standalone: false, }) export class SetPasswordComponent extends BaseSetPasswordComponent { routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/settings/account/account.component.ts b/apps/web/src/app/auth/settings/account/account.component.ts index d7ba8058b81..cfc01f17674 100644 --- a/apps/web/src/app/auth/settings/account/account.component.ts +++ b/apps/web/src/app/auth/settings/account/account.component.ts @@ -17,6 +17,7 @@ import { SetAccountVerifyDevicesDialogComponent } from "./set-account-verify-dev @Component({ selector: "app-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts index ba2a34c7143..5d71333c0de 100644 --- a/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/change-avatar-dialog.component.ts @@ -31,6 +31,7 @@ type ChangeAvatarDialogData = { @Component({ templateUrl: "change-avatar-dialog.component.html", encapsulation: ViewEncapsulation.None, + standalone: false, }) export class ChangeAvatarDialogComponent implements OnInit, OnDestroy { profile: ProfileResponse; diff --git a/apps/web/src/app/auth/settings/account/change-email.component.ts b/apps/web/src/app/auth/settings/account/change-email.component.ts index caf7e0933b0..c86c8c2f4f7 100644 --- a/apps/web/src/app/auth/settings/account/change-email.component.ts +++ b/apps/web/src/app/auth/settings/account/change-email.component.ts @@ -17,6 +17,7 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-email", templateUrl: "change-email.component.html", + standalone: false, }) export class ChangeEmailComponent implements OnInit { tokenSent = false; diff --git a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts index a7c466d4ffc..da4d2dce9d7 100644 --- a/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts +++ b/apps/web/src/app/auth/settings/account/deauthorize-sessions.component.ts @@ -12,6 +12,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-deauthorize-sessions", templateUrl: "deauthorize-sessions.component.html", + standalone: false, }) export class DeauthorizeSessionsComponent { deauthForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts index 07e6052e0a1..8a3575af5ba 100644 --- a/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts +++ b/apps/web/src/app/auth/settings/account/delete-account-dialog.component.ts @@ -11,6 +11,7 @@ import { DialogRef, DialogService, ToastService } from "@bitwarden/components"; @Component({ templateUrl: "delete-account-dialog.component.html", + standalone: false, }) export class DeleteAccountDialogComponent { deleteForm = this.formBuilder.group({ diff --git a/apps/web/src/app/auth/settings/account/profile.component.ts b/apps/web/src/app/auth/settings/account/profile.component.ts index a87e571643d..dc3997f58bb 100644 --- a/apps/web/src/app/auth/settings/account/profile.component.ts +++ b/apps/web/src/app/auth/settings/account/profile.component.ts @@ -19,6 +19,7 @@ import { ChangeAvatarDialogComponent } from "./change-avatar-dialog.component"; @Component({ selector: "app-profile", templateUrl: "profile.component.html", + standalone: false, }) export class ProfileComponent implements OnInit, OnDestroy { loading = true; diff --git a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts index 7a746481563..33c307882c5 100644 --- a/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts +++ b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts @@ -24,6 +24,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; > `, + standalone: false, }) export class SelectableAvatarComponent { @Input() id: string; diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index ffa5247ad08..f1ba9281f69 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -32,6 +32,7 @@ import { UserKeyRotationService } from "../../key-management/key-rotation/user-k @Component({ selector: "app-change-password", templateUrl: "change-password.component.html", + standalone: false, }) export class ChangePasswordComponent extends BaseChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index 95afc167374..cd7a585f3b1 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -26,6 +26,7 @@ type EmergencyAccessConfirmDialogData = { @Component({ selector: "emergency-access-confirm", templateUrl: "emergency-access-confirm.component.html", + standalone: false, }) export class EmergencyAccessConfirmComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index 1a6510ef011..2f3f3a20b04 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -36,6 +36,7 @@ export enum EmergencyAccessAddEditDialogResult { @Component({ selector: "emergency-access-add-edit", templateUrl: "emergency-access-add-edit.component.html", + standalone: false, }) export class EmergencyAccessAddEditComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts index f55d731d7f2..23bf0c22bc7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.ts @@ -42,6 +42,7 @@ import { @Component({ selector: "emergency-access", templateUrl: "emergency-access.component.html", + standalone: false, }) export class EmergencyAccessComponent implements OnInit { loaded = false; diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index edb85dc0f1a..d683545db59 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -40,6 +40,7 @@ type EmergencyAccessTakeoverDialogData = { @Component({ selector: "emergency-access-takeover", templateUrl: "emergency-access-takeover.component.html", + standalone: false, }) export class EmergencyAccessTakeoverComponent extends ChangePasswordComponent diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts index 55ebf860cff..607e6e6a2c7 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-access-view.component.ts @@ -15,6 +15,7 @@ import { EmergencyViewDialogComponent } from "./emergency-view-dialog.component" selector: "emergency-access-view", templateUrl: "emergency-access-view.component.html", providers: [{ provide: CipherFormConfigService, useClass: DefaultCipherFormConfigService }], + standalone: false, }) export class EmergencyAccessViewComponent implements OnInit { id: EmergencyAccessId | null = null; diff --git a/apps/web/src/app/auth/settings/security/api-key.component.ts b/apps/web/src/app/auth/settings/security/api-key.component.ts index 26a70476102..4f87c082881 100644 --- a/apps/web/src/app/auth/settings/security/api-key.component.ts +++ b/apps/web/src/app/auth/settings/security/api-key.component.ts @@ -23,6 +23,7 @@ export type ApiKeyDialogData = { @Component({ selector: "app-api-key", templateUrl: "api-key.component.html", + standalone: false, }) export class ApiKeyComponent { clientId: string; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts index debe390d878..0bfc46eea96 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf-confirmation.component.ts @@ -16,6 +16,7 @@ import { KdfConfig, KdfType, KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-change-kdf-confirmation", templateUrl: "change-kdf-confirmation.component.html", + standalone: false, }) export class ChangeKdfConfirmationComponent { kdfConfig: KdfConfig; diff --git a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts index cbbef0e016b..a059ede77b4 100644 --- a/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts +++ b/apps/web/src/app/auth/settings/security/change-kdf/change-kdf.component.ts @@ -21,6 +21,7 @@ import { ChangeKdfConfirmationComponent } from "./change-kdf-confirmation.compon @Component({ selector: "app-change-kdf", templateUrl: "change-kdf.component.html", + standalone: false, }) export class ChangeKdfComponent implements OnInit, OnDestroy { kdfConfig: KdfConfig = DEFAULT_KDF_CONFIG; diff --git a/apps/web/src/app/auth/settings/security/security-keys.component.ts b/apps/web/src/app/auth/settings/security/security-keys.component.ts index 70e33b242b3..98e743f57dc 100644 --- a/apps/web/src/app/auth/settings/security/security-keys.component.ts +++ b/apps/web/src/app/auth/settings/security/security-keys.component.ts @@ -13,6 +13,7 @@ import { ApiKeyComponent } from "./api-key.component"; @Component({ selector: "app-security-keys", templateUrl: "security-keys.component.html", + standalone: false, }) export class SecurityKeysComponent implements OnInit { showChangeKdf = true; diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index d643b565df2..4f70a19e378 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -6,6 +6,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co @Component({ selector: "app-security", templateUrl: "security.component.html", + standalone: false, }) export class SecurityComponent implements OnInit { showChangePassword = true; diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 8e7e25896ab..22c3b4376c5 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -33,6 +33,7 @@ type Step = @Component({ templateUrl: "create-credential-dialog.component.html", + standalone: false, }) export class CreateCredentialDialogComponent implements OnInit { protected readonly NameMaxCharacters = 50; diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts index 8f6bf1778e8..ea766a302ca 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/delete-credential-dialog/delete-credential-dialog.component.ts @@ -26,6 +26,7 @@ export interface DeleteCredentialDialogParams { @Component({ templateUrl: "delete-credential-dialog.component.html", + standalone: false, }) export class DeleteCredentialDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts index b402e53abf2..dd1ac45a9b6 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/enable-encryption-dialog/enable-encryption-dialog.component.ts @@ -23,6 +23,7 @@ export interface EnableEncryptionDialogParams { @Component({ templateUrl: "enable-encryption-dialog.component.html", + standalone: false, }) export class EnableEncryptionDialogComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts index 13c7993768c..94e926ac138 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.ts @@ -23,6 +23,7 @@ import { openEnableCredentialDialogComponent } from "./enable-encryption-dialog/ host: { "aria-live": "polite", }, + standalone: false, }) export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts index 282bce4c95d..77df374f3ed 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification-prompt.component.ts @@ -23,6 +23,7 @@ import { */ @Component({ templateUrl: "user-verification-prompt.component.html", + standalone: false, }) export class UserVerificationPromptComponent extends BaseUserVerificationPrompt { constructor( diff --git a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts index 94d319524f7..42f4b26fb36 100644 --- a/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts +++ b/apps/web/src/app/auth/shared/components/user-verification/user-verification.component.ts @@ -23,5 +23,6 @@ import { UserVerificationComponent as BaseComponent } from "@bitwarden/angular/a transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]), ]), ], + standalone: false, }) export class UserVerificationComponent extends BaseComponent {} diff --git a/apps/web/src/app/auth/update-password.component.ts b/apps/web/src/app/auth/update-password.component.ts index da62a6812f1..c975f7c4168 100644 --- a/apps/web/src/app/auth/update-password.component.ts +++ b/apps/web/src/app/auth/update-password.component.ts @@ -9,6 +9,7 @@ import { AcceptOrganizationInviteService } from "./organization-invite/accept-or @Component({ selector: "app-update-password", templateUrl: "update-password.component.html", + standalone: false, }) export class UpdatePasswordComponent extends BaseUpdatePasswordComponent { private routerService = inject(RouterService); diff --git a/apps/web/src/app/auth/update-temp-password.component.ts b/apps/web/src/app/auth/update-temp-password.component.ts index f7d952b97f4..ead10660b92 100644 --- a/apps/web/src/app/auth/update-temp-password.component.ts +++ b/apps/web/src/app/auth/update-temp-password.component.ts @@ -5,5 +5,6 @@ import { UpdateTempPasswordComponent as BaseUpdateTempPasswordComponent } from " @Component({ selector: "app-update-temp-password", templateUrl: "update-temp-password.component.html", + standalone: false, }) export class UpdateTempPasswordComponent extends BaseUpdateTempPasswordComponent {} diff --git a/apps/web/src/app/auth/verify-email-token.component.ts b/apps/web/src/app/auth/verify-email-token.component.ts index 55569f44c10..9e44cc7a713 100644 --- a/apps/web/src/app/auth/verify-email-token.component.ts +++ b/apps/web/src/app/auth/verify-email-token.component.ts @@ -15,6 +15,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-email-token", templateUrl: "verify-email-token.component.html", + standalone: false, }) export class VerifyEmailTokenComponent implements OnInit { constructor( diff --git a/apps/web/src/app/auth/verify-recover-delete.component.ts b/apps/web/src/app/auth/verify-recover-delete.component.ts index 8d95dd01b77..a475fdfd3e5 100644 --- a/apps/web/src/app/auth/verify-recover-delete.component.ts +++ b/apps/web/src/app/auth/verify-recover-delete.component.ts @@ -14,6 +14,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete", templateUrl: "verify-recover-delete.component.html", + standalone: false, }) export class VerifyRecoverDeleteComponent implements OnInit { email: string; diff --git a/apps/web/src/app/billing/individual/billing-history-view.component.ts b/apps/web/src/app/billing/individual/billing-history-view.component.ts index 89e6b9ca9c3..d615e01d0db 100644 --- a/apps/web/src/app/billing/individual/billing-history-view.component.ts +++ b/apps/web/src/app/billing/individual/billing-history-view.component.ts @@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "billing-history-view.component.html", + standalone: false, }) export class BillingHistoryViewComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/individual/premium/premium.component.ts b/apps/web/src/app/billing/individual/premium/premium.component.ts index 2934e69f0ad..974c22455ff 100644 --- a/apps/web/src/app/billing/individual/premium/premium.component.ts +++ b/apps/web/src/app/billing/individual/premium/premium.component.ts @@ -25,6 +25,7 @@ import { TaxInfoComponent } from "../../shared/tax-info.component"; @Component({ templateUrl: "./premium.component.html", + standalone: false, }) export class PremiumComponent { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/individual/subscription.component.ts b/apps/web/src/app/billing/individual/subscription.component.ts index edd16ca81fe..2a08ec85127 100644 --- a/apps/web/src/app/billing/individual/subscription.component.ts +++ b/apps/web/src/app/billing/individual/subscription.component.ts @@ -9,6 +9,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ templateUrl: "subscription.component.html", + standalone: false, }) export class SubscriptionComponent implements OnInit { hasPremium$: Observable; 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 38f4436fb47..4d1fa97785b 100644 --- a/apps/web/src/app/billing/individual/user-subscription.component.ts +++ b/apps/web/src/app/billing/individual/user-subscription.component.ts @@ -28,6 +28,7 @@ import { UpdateLicenseDialogResult } from "../shared/update-license-types"; @Component({ templateUrl: "user-subscription.component.html", + standalone: false, }) export class UserSubscriptionComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index 02e4e6a9cc1..dddc730168c 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -23,6 +23,7 @@ import { AddSponsorshipDialogComponent } from "./add-sponsorship-dialog.componen @Component({ selector: "app-free-bitwarden-families", templateUrl: "free-bitwarden-families.component.html", + standalone: false, }) export class FreeBitwardenFamiliesComponent implements OnInit { loading = signal(true); diff --git a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts index 786c25c8d4c..d1086a6646b 100644 --- a/apps/web/src/app/billing/organizations/adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/adjust-subscription.component.ts @@ -19,6 +19,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-adjust-subscription", templateUrl: "adjust-subscription.component.html", + standalone: false, }) export class AdjustSubscription implements OnInit, OnDestroy { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts index 1eb1c7124ad..55687f00052 100644 --- a/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts +++ b/apps/web/src/app/billing/organizations/billing-sync-api-key.component.ts @@ -22,6 +22,7 @@ export interface BillingSyncApiModalData { @Component({ templateUrl: "billing-sync-api-key.component.html", + standalone: false, }) export class BillingSyncApiKeyComponent { protected organizationId: string; diff --git a/apps/web/src/app/billing/organizations/billing-sync-key.component.ts b/apps/web/src/app/billing/organizations/billing-sync-key.component.ts index f29a98b2612..37ebefc803a 100644 --- a/apps/web/src/app/billing/organizations/billing-sync-key.component.ts +++ b/apps/web/src/app/billing/organizations/billing-sync-key.component.ts @@ -21,6 +21,7 @@ export interface BillingSyncKeyModalData { @Component({ templateUrl: "billing-sync-key.component.html", + standalone: false, }) export class BillingSyncKeyComponent { protected entityId: string; diff --git a/apps/web/src/app/billing/organizations/change-plan.component.ts b/apps/web/src/app/billing/organizations/change-plan.component.ts index 7c25413079a..31cbf4e94bf 100644 --- a/apps/web/src/app/billing/organizations/change-plan.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan.component.ts @@ -9,6 +9,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" @Component({ selector: "app-change-plan", templateUrl: "change-plan.component.html", + standalone: false, }) export class ChangePlanComponent { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index 66778aec50f..8ada57e8377 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -20,6 +20,7 @@ type DownloadLicenseDialogData = { @Component({ templateUrl: "download-license.component.html", + standalone: false, }) export class DownloadLicenceDialogComponent { licenseForm = this.formBuilder.group({ diff --git a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts index d533badabf8..ce4678ad8ef 100644 --- a/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts +++ b/apps/web/src/app/billing/organizations/organization-billing-history-view.component.ts @@ -12,6 +12,7 @@ import { @Component({ templateUrl: "organization-billing-history-view.component.html", + standalone: false, }) export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy { loading = false; 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 5cbaf940695..792a138c2d1 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 @@ -44,6 +44,7 @@ import { SecretsManagerSubscriptionOptions } from "./sm-adjust-subscription.comp @Component({ templateUrl: "organization-subscription-cloud.component.html", + standalone: false, }) export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy { static readonly QUERY_PARAM_UPGRADE: string = "upgrade"; diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index 2189bfa830f..fa4b633cb7a 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -36,6 +36,7 @@ enum LicenseOptions { @Component({ templateUrl: "organization-subscription-selfhost.component.html", + standalone: false, }) export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDestroy { subscription: SelfHostedOrganizationSubscriptionView; diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts index b4064bb1d87..c896ee6404c 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.ts @@ -37,6 +37,7 @@ import { FreeTrial } from "../../types/free-trial"; @Component({ templateUrl: "./organization-payment-method.component.html", + standalone: false, }) export class OrganizationPaymentMethodComponent implements OnDestroy { organizationId: string; diff --git a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts index c10c9abc9b6..33413832865 100644 --- a/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts +++ b/apps/web/src/app/billing/organizations/sm-adjust-subscription.component.ts @@ -59,6 +59,7 @@ export interface SecretsManagerSubscriptionOptions { @Component({ selector: "app-sm-adjust-subscription", templateUrl: "sm-adjust-subscription.component.html", + standalone: false, }) export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDestroy { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts index 617b68abb37..6f9525e4fce 100644 --- a/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts +++ b/apps/web/src/app/billing/organizations/sm-subscribe-standalone.component.ts @@ -23,6 +23,7 @@ import { secretsManagerSubscribeFormFactory } from "../shared"; @Component({ selector: "sm-subscribe-standalone", templateUrl: "sm-subscribe-standalone.component.html", + standalone: false, }) export class SecretsManagerSubscribeStandaloneComponent { @Input() plan: PlanResponse; diff --git a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts index 894db727b01..1455d76d67e 100644 --- a/apps/web/src/app/billing/organizations/subscription-hidden.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-hidden.component.ts @@ -34,6 +34,7 @@ const SubscriptionHiddenIcon = svgIcon`

{{ "billingManagedByProvider" | i18n: providerName }}

{{ "billingContactProviderForAssistance" | i18n }}

`, + standalone: false, }) export class SubscriptionHiddenComponent { @Input() providerName: string; diff --git a/apps/web/src/app/billing/organizations/subscription-status.component.ts b/apps/web/src/app/billing/organizations/subscription-status.component.ts index a097bf674e2..0b59df3f707 100644 --- a/apps/web/src/app/billing/organizations/subscription-status.component.ts +++ b/apps/web/src/app/billing/organizations/subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-subscription-status", templateUrl: "subscription-status.component.html", + standalone: false, }) export class SubscriptionStatusComponent { @Input({ required: true }) organizationSubscriptionResponse: OrganizationSubscriptionResponse; diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index 7e493168ce2..80e66784ae8 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -36,6 +36,7 @@ interface RequestSponsorshipForm { @Component({ selector: "app-sponsored-families", templateUrl: "sponsored-families.component.html", + standalone: false, }) export class SponsoredFamiliesComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 39a7531082a..70320e7e62e 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -18,6 +18,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "[sponsoring-org-row]", templateUrl: "sponsoring-org-row.component.html", + standalone: false, }) export class SponsoringOrgRowComponent implements OnInit { @Input() sponsoringOrg: Organization = null; diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index ec6e251418b..cdf72168acf 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -35,6 +35,7 @@ export type PayPalConfig = { @Component({ templateUrl: "add-credit-dialog.component.html", + standalone: false, }) export class AddCreditDialogComponent implements OnInit { @ViewChild("ppButtonForm", { read: ElementRef, static: true }) ppButtonFormRef: ElementRef; diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index 9d32becd1bb..94929c58656 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -39,6 +39,7 @@ export enum AdjustPaymentDialogResultType { @Component({ templateUrl: "./adjust-payment-dialog.component.html", + standalone: false, }) export class AdjustPaymentDialogComponent implements OnInit { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 6cd17218f02..1f9172eaf59 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -31,6 +31,7 @@ export enum AdjustStorageDialogResultType { @Component({ templateUrl: "./adjust-storage-dialog.component.html", + standalone: false, }) export class AdjustStorageDialogComponent { protected formGroup = new FormGroup({ diff --git a/apps/web/src/app/billing/shared/billing-history.component.ts b/apps/web/src/app/billing/shared/billing-history.component.ts index 1994c53a375..745939f0d5e 100644 --- a/apps/web/src/app/billing/shared/billing-history.component.ts +++ b/apps/web/src/app/billing/shared/billing-history.component.ts @@ -11,6 +11,7 @@ import { @Component({ selector: "app-billing-history", templateUrl: "billing-history.component.html", + standalone: false, }) export class BillingHistoryComponent { @Input() diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index 62213c1fe94..9f21f2b8cd5 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -49,6 +49,7 @@ export const openOffboardingSurvey = ( @Component({ selector: "app-cancel-subscription-form", templateUrl: "offboarding-survey.component.html", + standalone: false, }) export class OffboardingSurveyComponent { protected ResultType = OffboardingSurveyDialogResultType; diff --git a/apps/web/src/app/billing/shared/payment-method.component.ts b/apps/web/src/app/billing/shared/payment-method.component.ts index f7d4480c4d4..27c9caf7186 100644 --- a/apps/web/src/app/billing/shared/payment-method.component.ts +++ b/apps/web/src/app/billing/shared/payment-method.component.ts @@ -35,6 +35,7 @@ import { @Component({ templateUrl: "payment-method.component.html", + standalone: false, }) export class PaymentMethodComponent implements OnInit, OnDestroy { loading = false; diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts index 0d7698e00fd..75da10a7b09 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/individual-self-hosting-license-uploader.component.ts @@ -17,6 +17,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "individual-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class IndividualSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts index 222aff3fec6..1850b5e526d 100644 --- a/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts +++ b/apps/web/src/app/billing/shared/self-hosting-license-uploader/organization-self-hosting-license-uploader.component.ts @@ -24,6 +24,7 @@ import { AbstractSelfHostingLicenseUploaderComponent } from "../../shared/self-h @Component({ selector: "organization-self-hosting-license-uploader", templateUrl: "./self-hosting-license-uploader.component.html", + standalone: false, }) export class OrganizationSelfHostingLicenseUploaderComponent extends AbstractSelfHostingLicenseUploaderComponent { /** diff --git a/apps/web/src/app/billing/shared/sm-subscribe.component.ts b/apps/web/src/app/billing/shared/sm-subscribe.component.ts index 23041ccb1ae..1ecf3648bd2 100644 --- a/apps/web/src/app/billing/shared/sm-subscribe.component.ts +++ b/apps/web/src/app/billing/shared/sm-subscribe.component.ts @@ -33,6 +33,7 @@ export const secretsManagerSubscribeFormFactory = ( @Component({ selector: "sm-subscribe", templateUrl: "sm-subscribe.component.html", + standalone: false, }) export class SecretsManagerSubscribeComponent implements OnInit, OnDestroy { @Input() formGroup: FormGroup>; diff --git a/apps/web/src/app/billing/shared/update-license-dialog.component.ts b/apps/web/src/app/billing/shared/update-license-dialog.component.ts index afc62b0ca31..11b5e7fd8df 100644 --- a/apps/web/src/app/billing/shared/update-license-dialog.component.ts +++ b/apps/web/src/app/billing/shared/update-license-dialog.component.ts @@ -12,6 +12,7 @@ import { UpdateLicenseComponent } from "./update-license.component"; @Component({ templateUrl: "update-license-dialog.component.html", + standalone: false, }) export class UpdateLicenseDialogComponent extends UpdateLicenseComponent { constructor( diff --git a/apps/web/src/app/billing/shared/update-license.component.ts b/apps/web/src/app/billing/shared/update-license.component.ts index e580d420202..455b38386c6 100644 --- a/apps/web/src/app/billing/shared/update-license.component.ts +++ b/apps/web/src/app/billing/shared/update-license.component.ts @@ -15,6 +15,7 @@ import { UpdateLicenseDialogResult } from "./update-license-types"; @Component({ selector: "app-update-license", templateUrl: "update-license.component.html", + standalone: false, }) export class UpdateLicenseComponent implements OnInit { @Input() organizationId: string; diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 90df6e513c4..93f2bc021cd 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -47,6 +47,7 @@ export type InitiationPath = @Component({ selector: "app-complete-trial-initiation", templateUrl: "complete-trial-initiation.component.html", + standalone: false, }) export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; diff --git a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts index e88c49c4f57..cbb1c84284c 100644 --- a/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts +++ b/apps/web/src/app/billing/trial-initiation/confirmation-details.component.ts @@ -7,6 +7,7 @@ import { ProductType } from "@bitwarden/common/billing/enums"; @Component({ selector: "app-trial-confirmation-details", templateUrl: "confirmation-details.component.html", + standalone: false, }) export class ConfirmationDetailsComponent { @Input() email: string; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts index e43eb4e6cda..0c6e084f5c4 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step-content.component.ts @@ -7,6 +7,7 @@ import { VerticalStep } from "./vertical-step.component"; @Component({ selector: "app-vertical-step-content", templateUrl: "vertical-step-content.component.html", + standalone: false, }) export class VerticalStepContentComponent { @Output() onSelectStep = new EventEmitter(); diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts index 1ff900875df..b4b643b3889 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-step.component.ts @@ -5,6 +5,7 @@ import { Component, Input } from "@angular/core"; selector: "app-vertical-step", templateUrl: "vertical-step.component.html", providers: [{ provide: CdkStep, useExisting: VerticalStep }], + standalone: false, }) export class VerticalStep extends CdkStep { @Input() subLabel = ""; diff --git a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts index 04197827813..333224aac54 100644 --- a/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts +++ b/apps/web/src/app/billing/trial-initiation/vertical-stepper/vertical-stepper.component.ts @@ -9,6 +9,7 @@ import { VerticalStep } from "./vertical-step.component"; selector: "app-vertical-stepper", templateUrl: "vertical-stepper.component.html", providers: [{ provide: CdkStepper, useExisting: VerticalStepperComponent }], + standalone: false, }) export class VerticalStepperComponent extends CdkStepper { readonly steps: QueryList; diff --git a/apps/web/src/app/components/environment-selector/environment-selector.component.ts b/apps/web/src/app/components/environment-selector/environment-selector.component.ts index b86c068911f..3f71a0d719a 100644 --- a/apps/web/src/app/components/environment-selector/environment-selector.component.ts +++ b/apps/web/src/app/components/environment-selector/environment-selector.component.ts @@ -13,6 +13,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "environment-selector", templateUrl: "environment-selector.component.html", + standalone: false, }) export class EnvironmentSelectorComponent implements OnInit { constructor( diff --git a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts index e1da7be06f8..47bf0844468 100644 --- a/apps/web/src/app/dirt/reports/pages/breach-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/breach-report.component.ts @@ -11,6 +11,7 @@ import { BreachAccountResponse } from "@bitwarden/common/models/response/breach- @Component({ selector: "app-breach-report", templateUrl: "breach-report.component.html", + standalone: false, }) export class BreachReportComponent implements OnInit { loading = false; diff --git a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts index 900ff3703f0..5710ea1176e 100644 --- a/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/exposed-passwords-report.component.ts @@ -20,6 +20,7 @@ type ReportResult = CipherView & { exposedXTimes: number }; @Component({ selector: "app-exposed-passwords-report", templateUrl: "exposed-passwords-report.component.html", + standalone: false, }) export class ExposedPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts index 5265326128e..95810625dac 100644 --- a/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/inactive-two-factor-report.component.ts @@ -21,6 +21,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-inactive-two-factor-report", templateUrl: "inactive-two-factor-report.component.html", + standalone: false, }) export class InactiveTwoFactorReportComponent extends CipherReportComponent implements OnInit { services = new Map(); diff --git a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts index 4f0988082b4..b88987e1d25 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/exposed-passwords-report.component.ts @@ -36,6 +36,7 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ExposedPasswordsReportComponent extends BaseExposedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts index 6dc202de0b3..1105e814245 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/inactive-two-factor-report.component.ts @@ -35,6 +35,7 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class InactiveTwoFactorReportComponent extends BaseInactiveTwoFactorReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts index 4e37f53ba61..7fcf3562437 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/reused-passwords-report.component.ts @@ -35,6 +35,7 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent } RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class ReusedPasswordsReportComponent extends BaseReusedPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts index 25e1314fceb..2e916da0294 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/unsecured-websites-report.component.ts @@ -35,6 +35,7 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class UnsecuredWebsitesReportComponent extends BaseUnsecuredWebsitesReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts index ef9bd97008e..80be66e9ad2 100644 --- a/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/organizations/weak-passwords-report.component.ts @@ -36,6 +36,7 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from RoutedVaultFilterService, RoutedVaultFilterBridgeService, ], + standalone: false, }) export class WeakPasswordsReportComponent extends BaseWeakPasswordsReportComponent diff --git a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts index 604d66f6858..acc3efac58a 100644 --- a/apps/web/src/app/dirt/reports/pages/reports-home.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reports-home.component.ts @@ -12,6 +12,7 @@ import { ReportEntry, ReportVariant } from "../shared"; @Component({ selector: "app-reports-home", templateUrl: "reports-home.component.html", + standalone: false, }) export class ReportsHomeComponent implements OnInit { reports: ReportEntry[]; diff --git a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts index 6d70cc23875..3e9abc779ba 100644 --- a/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/reused-passwords-report.component.ts @@ -19,6 +19,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-reused-passwords-report", templateUrl: "reused-passwords-report.component.html", + standalone: false, }) export class ReusedPasswordsReportComponent extends CipherReportComponent implements OnInit { passwordUseMap: Map; diff --git a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts index 02d4117c684..d2cc792198e 100644 --- a/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/unsecured-websites-report.component.ts @@ -18,6 +18,7 @@ import { CipherReportComponent } from "./cipher-report.component"; @Component({ selector: "app-unsecured-websites-report", templateUrl: "unsecured-websites-report.component.html", + standalone: false, }) export class UnsecuredWebsitesReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts index 4144c9ac20f..1716a98190c 100644 --- a/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts +++ b/apps/web/src/app/dirt/reports/pages/weak-passwords-report.component.ts @@ -28,6 +28,7 @@ type ReportResult = CipherView & { score: number; reportValue: ReportScore; scor @Component({ selector: "app-weak-passwords-report", templateUrl: "weak-passwords-report.component.html", + standalone: false, }) export class WeakPasswordsReportComponent extends CipherReportComponent implements OnInit { disabled = true; diff --git a/apps/web/src/app/dirt/reports/reports-layout.component.ts b/apps/web/src/app/dirt/reports/reports-layout.component.ts index 7bfe912c1ad..360898e6057 100644 --- a/apps/web/src/app/dirt/reports/reports-layout.component.ts +++ b/apps/web/src/app/dirt/reports/reports-layout.component.ts @@ -6,6 +6,7 @@ import { filter } from "rxjs/operators"; @Component({ selector: "app-reports-layout", templateUrl: "reports-layout.component.html", + standalone: false, }) export class ReportsLayoutComponent implements OnDestroy { homepage = true; diff --git a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts index da42d92bf84..92e6ddb0028 100644 --- a/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-card/report-card.component.ts @@ -9,6 +9,7 @@ import { ReportVariant } from "../models/report-variant"; @Component({ selector: "app-report-card", templateUrl: "report-card.component.html", + standalone: false, }) export class ReportCardComponent { @Input() title: string; diff --git a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts index cd6b77f9c81..c81c99d50d5 100644 --- a/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts +++ b/apps/web/src/app/dirt/reports/shared/report-list/report-list.component.ts @@ -7,6 +7,7 @@ import { ReportEntry } from "../models/report-entry"; @Component({ selector: "app-report-list", templateUrl: "report-list.component.html", + standalone: false, }) export class ReportListComponent { @Input() reports: ReportEntry[]; diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts index 3ca9d3a5669..1b07f04ba8a 100644 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ b/apps/web/src/app/key-management/key-connector/remove-password.component.ts @@ -5,5 +5,6 @@ import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitward @Component({ selector: "app-remove-password", templateUrl: "remove-password.component.html", + standalone: false, }) export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/web/src/app/layouts/frontend-layout.component.ts b/apps/web/src/app/layouts/frontend-layout.component.ts index 609845f22cd..9e22ab6d43e 100644 --- a/apps/web/src/app/layouts/frontend-layout.component.ts +++ b/apps/web/src/app/layouts/frontend-layout.component.ts @@ -7,6 +7,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ selector: "app-frontend-layout", templateUrl: "frontend-layout.component.html", + standalone: false, }) export class FrontendLayoutComponent implements OnInit, OnDestroy { version: string; diff --git a/apps/web/src/app/layouts/header/web-header.component.ts b/apps/web/src/app/layouts/header/web-header.component.ts index e17d059160f..67d447723e1 100644 --- a/apps/web/src/app/layouts/header/web-header.component.ts +++ b/apps/web/src/app/layouts/header/web-header.component.ts @@ -17,6 +17,7 @@ import { UserId } from "@bitwarden/common/types/guid"; @Component({ selector: "app-header", templateUrl: "./web-header.component.html", + standalone: false, }) export class WebHeaderComponent { /** diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts index 737fa2d8d2e..9d4250087af 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.ts @@ -6,6 +6,7 @@ import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-s @Component({ selector: "navigation-product-switcher", templateUrl: "./navigation-switcher.component.html", + standalone: false, }) export class NavigationProductSwitcherComponent { constructor(private productSwitcherService: ProductSwitcherService) {} diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index fca9063c8cf..b1c1a0a906a 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -24,6 +24,7 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts index 4a22f628570..5a6572e15be 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher-content.component.ts @@ -9,6 +9,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher-content", templateUrl: "./product-switcher-content.component.html", + standalone: false, }) export class ProductSwitcherContentComponent { @ViewChild("menu") diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts index 834571e2cb4..5dd29815ef2 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.component.ts @@ -8,6 +8,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Component({ selector: "product-switcher", templateUrl: "./product-switcher.component.html", + standalone: false, }) export class ProductSwitcherComponent implements AfterViewInit { /** diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index ca27aae4581..4525105e579 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -24,6 +24,7 @@ import { ProductSwitcherService } from "./shared/product-switcher.service"; @Directive({ selector: "[mockOrgs]", + standalone: false, }) class MockOrganizationService implements Partial { private static _orgs = new BehaviorSubject([]); @@ -40,6 +41,7 @@ class MockOrganizationService implements Partial { @Directive({ selector: "[mockProviders]", + standalone: false, }) class MockProviderService implements Partial { private static _providers = new BehaviorSubject([]); @@ -78,12 +80,14 @@ class MockPlatformUtilsService implements Partial { @Component({ selector: "story-layout", template: ``, + standalone: false, }) class StoryLayoutComponent {} @Component({ selector: "story-content", template: ``, + standalone: false, }) class StoryContentComponent {} diff --git a/apps/web/src/app/settings/domain-rules.component.ts b/apps/web/src/app/settings/domain-rules.component.ts index 6dd27cbf19b..2d570f4aeb4 100644 --- a/apps/web/src/app/settings/domain-rules.component.ts +++ b/apps/web/src/app/settings/domain-rules.component.ts @@ -12,6 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Component({ selector: "app-domain-rules", templateUrl: "domain-rules.component.html", + standalone: false, }) export class DomainRulesComponent implements OnInit { loading = true; diff --git a/apps/web/src/app/settings/preferences.component.ts b/apps/web/src/app/settings/preferences.component.ts index 4d4e0c3d711..9ab23c76795 100644 --- a/apps/web/src/app/settings/preferences.component.ts +++ b/apps/web/src/app/settings/preferences.component.ts @@ -37,6 +37,7 @@ import { DialogService } from "@bitwarden/components"; @Component({ selector: "app-preferences", templateUrl: "preferences.component.html", + standalone: false, }) export class PreferencesComponent implements OnInit, OnDestroy { // For use in template diff --git a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts index 7bb86c9f669..f9798ec7f0f 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding-task.component.ts @@ -8,6 +8,7 @@ import { Component, Input } from "@angular/core"; host: { class: "tw-max-w-max", }, + standalone: false, }) export class OnboardingTaskComponent { @Input() diff --git a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts index 23f9015b024..5ead9fcc10b 100644 --- a/apps/web/src/app/shared/components/onboarding/onboarding.component.ts +++ b/apps/web/src/app/shared/components/onboarding/onboarding.component.ts @@ -7,6 +7,7 @@ import { OnboardingTaskComponent } from "./onboarding-task.component"; @Component({ selector: "app-onboarding", templateUrl: "./onboarding.component.html", + standalone: false, }) export class OnboardingComponent { @ContentChildren(OnboardingTaskComponent) tasks: QueryList; diff --git a/apps/web/src/app/vault/components/premium-badge.component.ts b/apps/web/src/app/vault/components/premium-badge.component.ts index 6deff43489d..ec444404aea 100644 --- a/apps/web/src/app/vault/components/premium-badge.component.ts +++ b/apps/web/src/app/vault/components/premium-badge.component.ts @@ -9,6 +9,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag {{ "premium" | i18n }} `, + standalone: false, }) export class PremiumBadgeComponent { constructor(private messagingService: MessagingService) {} diff --git a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts index 317b02356ba..ac1774cd244 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.ts @@ -20,6 +20,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCipherRow]", templateUrl: "vault-cipher-row.component.html", + standalone: false, }) export class VaultCipherRowComponent implements OnInit { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts index d07ba46d136..06c78ea0351 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-collection-row.component.ts @@ -18,6 +18,7 @@ import { RowHeightClass } from "./vault-items.component"; @Component({ selector: "tr[appVaultCollectionRow]", templateUrl: "vault-collection-row.component.html", + standalone: false, }) export class VaultCollectionRowComponent { protected RowHeightClass = RowHeightClass; diff --git a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts index d27f332f13f..1c63ac85d34 100644 --- a/apps/web/src/app/vault/components/vault-items/vault-items.component.ts +++ b/apps/web/src/app/vault/components/vault-items/vault-items.component.ts @@ -32,8 +32,7 @@ type ItemPermission = CollectionPermission | "NoAccess"; @Component({ selector: "app-vault-items", templateUrl: "vault-items.component.html", - // TODO: Improve change detection, see: https://bitwarden.atlassian.net/browse/TDL-220 - // changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class VaultItemsComponent { protected RowHeight = RowHeight; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index 1650b0f371f..43a44cf5066 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -53,6 +53,7 @@ export const openBulkDeleteDialog = ( @Component({ templateUrl: "bulk-delete-dialog.component.html", + standalone: false, }) export class BulkDeleteDialogComponent { cipherIds: string[]; diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index d287c430d49..dc262b01334 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -47,6 +47,7 @@ export const openBulkMoveDialog = ( @Component({ templateUrl: "bulk-move-dialog.component.html", + standalone: false, }) export class BulkMoveDialogComponent implements OnInit { cipherIds: string[] = []; diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index 6a3c5663d93..3050c00dd6c 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -23,6 +23,7 @@ import { KeyService } from "@bitwarden/key-management"; @Component({ selector: "app-folder-add-edit", templateUrl: "folder-add-edit.component.html", + standalone: false, }) export class FolderAddEditComponent extends BaseFolderAddEditComponent { protected override componentName = "app-folder-add-edit"; diff --git a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts index c1f935f2001..915bc00bacd 100644 --- a/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts +++ b/apps/web/src/app/vault/individual-vault/organization-badge/organization-name-badge.component.ts @@ -12,6 +12,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-org-badge", templateUrl: "organization-name-badge.component.html", + standalone: false, }) export class OrganizationNameBadgeComponent implements OnChanges { @Input() organizationId?: string; diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts index 81d3a8de749..09bce96728a 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-group-name.pipe.ts @@ -5,6 +5,7 @@ import { GroupView } from "../../../admin-console/organizations/core"; @Pipe({ name: "groupNameFromId", pure: true, + standalone: false, }) export class GetGroupNameFromIdPipe implements PipeTransform { transform(value: string, groups: GroupView[]) { diff --git a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts index 4d6c0b7d8d7..bf9dc82c527 100644 --- a/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts +++ b/apps/web/src/app/vault/individual-vault/pipes/get-organization-name.pipe.ts @@ -5,6 +5,7 @@ import { Organization } from "@bitwarden/common/admin-console/models/domain/orga @Pipe({ name: "orgNameFromId", pure: true, + standalone: false, }) export class GetOrgNameFromIdPipe implements PipeTransform { transform(value: string, organizations: Organization[]) { 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 c7d91237578..e95ea669725 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 @@ -33,6 +33,7 @@ import { OrganizationFilter } from "../shared/models/vault-filter.type"; @Component({ selector: "app-organization-options", templateUrl: "organization-options.component.html", + standalone: false, }) export class OrganizationOptionsComponent implements OnInit, OnDestroy { protected actionPromise?: Promise; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts index 3f1e7755c8f..6b974296f21 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/components/vault-filter.component.ts @@ -38,6 +38,7 @@ import { OrganizationOptionsComponent } from "./organization-options.component"; @Component({ selector: "app-vault-filter", templateUrl: "vault-filter.component.html", + standalone: false, }) export class VaultFilterComponent implements OnInit, OnDestroy { filters?: VaultFilterList; diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts index 41329319805..1a0a96fa19c 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/components/vault-filter-section.component.ts @@ -16,6 +16,7 @@ import { VaultFilter } from "../models/vault-filter.model"; @Component({ selector: "app-filter-section", templateUrl: "vault-filter-section.component.html", + standalone: false, }) export class VaultFilterSectionComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/apps/web/src/app/vault/settings/purge-vault.component.ts b/apps/web/src/app/vault/settings/purge-vault.component.ts index 11e75c16720..0a25122788c 100644 --- a/apps/web/src/app/vault/settings/purge-vault.component.ts +++ b/apps/web/src/app/vault/settings/purge-vault.component.ts @@ -25,6 +25,7 @@ export interface PurgeVaultDialogData { @Component({ selector: "app-purge-vault", templateUrl: "purge-vault.component.html", + standalone: false, }) export class PurgeVaultComponent { organizationId: string = null; diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts index b9b7460f7ed..970a476df22 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.ts @@ -24,6 +24,7 @@ export interface DomainAddEditDialogData { @Component({ templateUrl: "domain-add-edit-dialog.component.html", + standalone: false, }) export class DomainAddEditDialogComponent implements OnInit, OnDestroy { private componentDestroyed$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 8fc9b6bba0f..1f644f55a9f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -35,6 +35,7 @@ import { @Component({ selector: "app-org-manage-domain-verification", templateUrl: "domain-verification.component.html", + standalone: false, }) export class DomainVerificationComponent implements OnInit, OnDestroy { private componentDestroyed$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts index 76bcd7383f3..de870cdbdcb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/scim.component.ts @@ -24,6 +24,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "app-org-manage-scim", templateUrl: "scim.component.html", + standalone: false, }) export class ScimComponent implements OnInit { loading = true; diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts index c276f25663a..61e2133d059 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/activate-autofill.component.ts @@ -21,5 +21,6 @@ export class ActivateAutofillPolicy extends BasePolicy { @Component({ selector: "policy-activate-autofill", templateUrl: "activate-autofill.component.html", + standalone: false, }) export class ActivateAutofillPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts index 354192ff860..1a478fb4393 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/automatic-app-login.component.ts @@ -19,6 +19,7 @@ export class AutomaticAppLoginPolicy extends BasePolicy { @Component({ selector: "policy-automatic-app-login", templateUrl: "automatic-app-login.component.html", + standalone: false, }) export class AutomaticAppLoginPolicyComponent extends BasePolicyComponent { data = this.formBuilder.group({ diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts index 848b1d173a1..c274e58ccac 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/disable-personal-vault-export.component.ts @@ -16,5 +16,6 @@ export class DisablePersonalVaultExportPolicy extends BasePolicy { @Component({ selector: "policy-disable-personal-vault-export", templateUrl: "disable-personal-vault-export.component.html", + standalone: false, }) export class DisablePersonalVaultExportPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts index 9d09ead800e..a5b9ad47f6e 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/policies/maximum-vault-timeout.component.ts @@ -22,6 +22,7 @@ export class MaximumVaultTimeoutPolicy extends BasePolicy { @Component({ selector: "policy-maximum-timeout", templateUrl: "maximum-vault-timeout.component.html", + standalone: false, }) export class MaximumVaultTimeoutPolicyComponent extends BasePolicyComponent { vaultTimeoutActionOptions: { name: string; value: string }[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts index 88738aa897c..51296c52281 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/add-organization.component.ts @@ -19,6 +19,7 @@ interface AddOrganizationDialogData { @Component({ templateUrl: "add-organization.component.html", + standalone: false, }) export class AddOrganizationComponent implements OnInit { protected provider: Provider; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts index d22665b432f..9f3582f84bb 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/clients/create-organization.component.ts @@ -8,6 +8,7 @@ import { OrganizationPlansComponent } from "@bitwarden/web-vault/app/billing"; @Component({ selector: "app-create-organization", templateUrl: "create-organization.component.html", + standalone: false, }) export class CreateOrganizationComponent implements OnInit { @ViewChild(OrganizationPlansComponent, { static: true }) diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts index a3ef074b975..7bfac8f4b32 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/accept-provider.component.ts @@ -14,6 +14,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-accept-provider", templateUrl: "accept-provider.component.html", + standalone: false, }) export class AcceptProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 67f2382cf91..e21837f7226 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -35,6 +35,7 @@ export enum AddEditMemberDialogResultType { @Component({ templateUrl: "add-edit-member-dialog.component.html", + standalone: false, }) export class AddEditMemberDialogComponent { editing = false; 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 8271869f1b4..8bbc299269d 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 @@ -29,6 +29,7 @@ type BulkConfirmDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-confirm-dialog.component.html", + standalone: false, }) export class BulkConfirmDialogComponent extends BaseBulkConfirmComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts index c3f6409f296..e000d918414 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/bulk-remove-dialog.component.ts @@ -19,6 +19,7 @@ type BulkRemoveDialogParams = { @Component({ templateUrl: "../../../../../../../../apps/web/src/app/admin-console/organizations/members/components/bulk/bulk-remove-dialog.component.html", + standalone: false, }) export class BulkRemoveDialogComponent extends BaseBulkRemoveComponent { providerId: string; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts index 87f29fd91e9..2ad2ecdccbd 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/events.component.ts @@ -19,6 +19,7 @@ import { EventExportService } from "@bitwarden/web-vault/app/tools/event-export" @Component({ selector: "provider-events", templateUrl: "events.component.html", + standalone: false, }) export class EventsComponent extends BaseEventsComponent implements OnInit { exportFileName = "provider-events"; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts index 4a184d2dd16..9cbe8115008 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/members.component.ts @@ -45,6 +45,7 @@ class MembersTableDataSource extends PeopleTableDataSource { @Component({ templateUrl: "members.component.html", + standalone: false, }) export class MembersComponent extends BaseMembersComponent { accessEvents = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts index ac2fb333fae..f3d67abd4e1 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/providers.component.ts @@ -10,6 +10,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Component({ selector: "app-providers", templateUrl: "providers.component.html", + standalone: false, }) export class ProvidersComponent implements OnInit { providers: Provider[]; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts index e72e1c7c326..12dada12aa9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts @@ -20,6 +20,7 @@ import { DialogService, ToastService } from "@bitwarden/components"; @Component({ selector: "provider-account", templateUrl: "account.component.html", + standalone: false, }) export class AccountComponent implements OnDestroy, OnInit { selfHosted = false; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts index a41c7cba362..473380ff288 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup-provider.component.ts @@ -7,6 +7,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ selector: "app-setup-provider", templateUrl: "setup-provider.component.html", + standalone: false, }) export class SetupProviderComponent extends BaseAcceptComponent { protected logo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts index 0b6483b9f48..cb47b3fe28f 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts @@ -23,6 +23,7 @@ import { PaymentComponent } from "@bitwarden/web-vault/app/billing/shared/paymen @Component({ selector: "provider-setup", templateUrl: "setup.component.html", + standalone: false, }) export class SetupComponent implements OnInit, OnDestroy { @ViewChild(PaymentComponent) paymentComponent: PaymentComponent; diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts index b27a7ddd0f4..5c0d0982fb5 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/verify-recover-delete-provider.component.ts @@ -13,6 +13,7 @@ import { ToastService } from "@bitwarden/components"; @Component({ selector: "app-verify-recover-delete-provider", templateUrl: "verify-recover-delete-provider.component.html", + standalone: false, }) export class VerifyRecoverDeleteProviderComponent implements OnInit { name: string; diff --git a/bitwarden_license/bit-web/src/app/app.component.ts b/bitwarden_license/bit-web/src/app/app.component.ts index dd814f5c0d2..2d0dfd967a1 100644 --- a/bitwarden_license/bit-web/src/app/app.component.ts +++ b/bitwarden_license/bit-web/src/app/app.component.ts @@ -12,6 +12,7 @@ import { FreeFamiliesSponsorshipPolicy } from "./billing/policies/free-families- @Component({ selector: "app-root", templateUrl: "../../../../apps/web/src/app/app.component.html", + standalone: false, }) export class AppComponent extends BaseAppComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts index 03e09f8de4b..f0761757c65 100644 --- a/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts +++ b/bitwarden_license/bit-web/src/app/auth/sso/sso.component.ts @@ -51,6 +51,7 @@ const defaultSigningAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha2 @Component({ selector: "app-org-manage-sso", templateUrl: "sso.component.html", + standalone: false, }) export class SsoComponent implements OnInit, OnDestroy { readonly ssoType = SsoType; diff --git a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts index 521f8658890..53d2b0ab66c 100644 --- a/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/policies/free-families-sponsorship.component.ts @@ -16,5 +16,6 @@ export class FreeFamiliesSponsorshipPolicy extends BasePolicy { @Component({ selector: "policy-personal-ownership", templateUrl: "free-families-sponsorship.component.html", + standalone: false, }) export class FreeFamiliesSponsorshipPolicyComponent extends BasePolicyComponent {} diff --git a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts index fa3a617b45f..d1a9d43a6fc 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/billing-history/provider-billing-history.component.ts @@ -12,6 +12,7 @@ import { BillingNotificationService } from "@bitwarden/web-vault/app/billing/ser @Component({ templateUrl: "./provider-billing-history.component.html", + standalone: false, }) export class ProviderBillingHistoryComponent { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts index 4bb2c36ef15..f0eda893d67 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -27,6 +27,7 @@ export enum AddExistingOrganizationDialogResultType { @Component({ templateUrl: "./add-existing-organization-dialog.component.html", + standalone: false, }) export class AddExistingOrganizationDialogComponent implements OnInit { protected loading: boolean = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index e87ee6a1187..c7d82c3ec09 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -99,6 +99,7 @@ export class PlanCard { @Component({ templateUrl: "./create-client-dialog.component.html", + standalone: false, }) export class CreateClientDialogComponent implements OnInit { protected discountPercentage: number | null | undefined; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index 45abeab1f4a..06d1f80c101 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -41,6 +41,7 @@ export const openManageClientNameDialog = ( @Component({ templateUrl: "manage-client-name-dialog.component.html", + standalone: false, }) export class ManageClientNameDialogComponent { protected ResultType = ManageClientNameDialogResultType; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index ced48bfdbea..71e549b563b 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -36,6 +36,7 @@ export const openManageClientSubscriptionDialog = ( @Component({ templateUrl: "./manage-client-subscription-dialog.component.html", + standalone: false, }) export class ManageClientSubscriptionDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts index f262ba1abd0..056339b6fb7 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/setup/setup-business-unit.component.ts @@ -19,6 +19,7 @@ import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept @Component({ templateUrl: "./setup-business-unit.component.html", + standalone: false, }) export class SetupBusinessUnitComponent extends BaseAcceptComponent { protected bitwardenLogo = BitwardenLogo; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts index c49509427b8..974dc9c460f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts @@ -26,6 +26,7 @@ type ComponentData = { @Component({ selector: "app-provider-subscription-status", templateUrl: "provider-subscription-status.component.html", + standalone: false, }) export class ProviderSubscriptionStatusComponent { @Input({ required: true }) subscription: ProviderSubscriptionResponse; diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts index 657d16d9380..74368ef7839 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription.component.ts @@ -26,6 +26,7 @@ import { @Component({ selector: "app-provider-subscription", templateUrl: "./provider-subscription.component.html", + standalone: false, }) export class ProviderSubscriptionComponent implements OnInit, OnDestroy { private providerId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts index 2d0a681460a..b563591f32f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.spec.ts @@ -19,12 +19,14 @@ import { IntegrationsComponent } from "./integrations.component"; @Component({ selector: "app-header", template: "
", + standalone: false, }) class MockHeaderComponent {} @Component({ selector: "sm-new-menu", template: "
", + standalone: false, }) class MockNewMenuComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts index cdae129de4f..01a2edb5d5d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/integrations/integrations.component.ts @@ -6,6 +6,7 @@ import { Integration } from "@bitwarden/web-vault/app/admin-console/organization @Component({ selector: "sm-integrations", templateUrl: "./integrations.component.html", + standalone: false, }) export class IntegrationsComponent { private integrationsAndSdks: Integration[] = []; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts index 6a90eb2a78f..b50e586c337 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/layout.component.ts @@ -3,6 +3,7 @@ import { Component, OnInit } from "@angular/core"; @Component({ selector: "sm-layout", templateUrl: "./layout.component.html", + standalone: false, }) export class LayoutComponent implements OnInit { ngOnInit() { diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts index 6594b71a14c..1eef528b639 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/layout/navigation.component.ts @@ -34,6 +34,7 @@ import { CountService } from "../shared/counts/count.service"; @Component({ selector: "sm-navigation", templateUrl: "./navigation.component.html", + standalone: false, }) export class NavigationComponent implements OnInit, OnDestroy { protected readonly logo = SecretsManagerLogo; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 698448c2d06..a96f9a08919 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -88,6 +88,7 @@ type OrganizationTasks = { @Component({ selector: "sm-overview", templateUrl: "./overview.component.html", + standalone: false, }) export class OverviewComponent implements OnInit, OnDestroy { private destroy$: Subject = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts index bd68e58ea87..6b71c81f09e 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/section.component.ts @@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core"; @Component({ selector: "sm-section", templateUrl: "./section.component.html", + standalone: false, }) export class SectionComponent { @Input() open = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts index d6ca31399e1..8cdb1bb4d69 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectDeleteOperation { @Component({ templateUrl: "./project-delete-dialog.component.html", + standalone: false, }) export class ProjectDeleteDialogComponent implements OnInit { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index c96887cc9ac..ec420d653cc 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -27,6 +27,7 @@ export interface ProjectOperation { @Component({ templateUrl: "./project-dialog.component.html", + standalone: false, }) export class ProjectDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts index 159b90d5432..7523fa14a21 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/guards/project-access.guard.spec.ts @@ -22,11 +22,13 @@ import { projectAccessGuard } from "./project-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts index 4a0c37cb4a9..13f80920558 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-people.component.ts @@ -27,6 +27,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-people", templateUrl: "./project-people.component.html", + standalone: false, }) export class ProjectPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts index f4950bf53f2..536739715ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-secrets.component.ts @@ -45,6 +45,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project-secrets", templateUrl: "./project-secrets.component.html", + standalone: false, }) export class ProjectSecretsComponent implements OnInit { secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts index d289f9f7b1f..fc3a489bce9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project-service-accounts.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-project-service-accounts", templateUrl: "./project-service-accounts.component.html", + standalone: false, }) export class ProjectServiceAccountsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts index 9e60cf0535c..2d008dd219a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/project/project.component.ts @@ -37,6 +37,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-project", templateUrl: "./project.component.html", + standalone: false, }) export class ProjectComponent implements OnInit, OnDestroy { protected project$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts index e09aaea6e2b..ea5294624af 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/projects/projects.component.ts @@ -40,6 +40,7 @@ import { ProjectService } from "../project.service"; @Component({ selector: "sm-projects", templateUrl: "./projects.component.html", + standalone: false, }) export class ProjectsComponent implements OnInit { protected projects$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts index 9dfc80c9f88..6340cc42f3b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-delete.component.ts @@ -20,6 +20,7 @@ export interface SecretDeleteOperation { @Component({ templateUrl: "./secret-delete.component.html", + standalone: false, }) export class SecretDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 09a78e02c44..9172d44965d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -69,6 +69,7 @@ export interface SecretOperation { @Component({ templateUrl: "./secret-dialog.component.html", + standalone: false, }) export class SecretDialogComponent implements OnInit, OnDestroy { loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts index 7f11d69d59c..b719014a382 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-view-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretViewDialogParams { @Component({ templateUrl: "./secret-view-dialog.component.html", + standalone: false, }) export class SecretViewDialogComponent implements OnInit { protected loading = true; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts index b58173a1cc0..18ecd2e3b51 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/secrets.component.ts @@ -36,6 +36,7 @@ import { SecretService } from "./secret.service"; @Component({ selector: "sm-secrets", templateUrl: "./secrets.component.html", + standalone: false, }) export class SecretsComponent implements OnInit { protected secrets$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts index caca8c92aa8..a714729d96f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-list.component.ts @@ -8,6 +8,7 @@ import { AccessTokenView } from "../models/view/access-token.view"; @Component({ selector: "sm-access-list", templateUrl: "./access-list.component.html", + standalone: false, }) export class AccessListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts index a4f0c077efd..b9643ce8fd8 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/access-tokens.component.ts @@ -27,6 +27,7 @@ import { AccessTokenCreateDialogComponent } from "./dialogs/access-token-create- @Component({ selector: "sm-access-tokens", templateUrl: "./access-tokens.component.html", + standalone: false, }) export class AccessTokenComponent implements OnInit, OnDestroy { accessTokens$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts index 140e357edac..dfbe0a1511d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-create-dialog.component.ts @@ -17,6 +17,7 @@ export interface AccessTokenOperation { @Component({ templateUrl: "./access-token-create-dialog.component.html", + standalone: false, }) export class AccessTokenCreateDialogComponent implements OnInit { protected formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts index bfc120f33e2..0259b8d6e90 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/access-token-dialog.component.ts @@ -14,6 +14,7 @@ export interface AccessTokenDetails { @Component({ templateUrl: "./access-token-dialog.component.html", + standalone: false, }) export class AccessTokenDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts index 2273b42897c..891501874ff 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/access/dialogs/expiration-options.component.ts @@ -33,6 +33,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic useExisting: ExpirationOptionsComponent, }, ], + standalone: false, }) export class ExpirationOptionsComponent implements ControlValueAccessor, Validator, OnInit, OnDestroy diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts index 96e3b58b633..e796c4758ea 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/config/config.component.ts @@ -24,6 +24,7 @@ class ServiceAccountConfig { @Component({ selector: "sm-service-account-config", templateUrl: "./config.component.html", + standalone: false, }) export class ServiceAccountConfigComponent implements OnInit, OnDestroy { identityUrl: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts index 6f30aacccca..5edc57d8c74 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-delete-dialog.component.ts @@ -27,6 +27,7 @@ export interface ServiceAccountDeleteOperation { @Component({ templateUrl: "./service-account-delete-dialog.component.html", + standalone: false, }) export class ServiceAccountDeleteDialogComponent { formGroup = new FormGroup({ diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index 241c736fb7a..815ea1dc60c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -26,6 +26,7 @@ export interface ServiceAccountOperation { @Component({ templateUrl: "./service-account-dialog.component.html", + standalone: false, }) export class ServiceAccountDialogComponent implements OnInit { protected formGroup = new FormGroup( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts index 6538ae49adb..ddaa0937e6f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/event-logs/service-accounts-events.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountEventLogApiService } from "./service-account-event-log-ap @Component({ selector: "sm-service-accounts-events", templateUrl: "./service-accounts-events.component.html", + standalone: false, }) export class ServiceAccountEventsComponent extends BaseEventsComponent diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts index 4301b5ae81a..e0bcad8d6e9 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/guards/service-account-access.guard.spec.ts @@ -22,11 +22,13 @@ import { serviceAccountAccessGuard } from "./service-account-access.guard"; @Component({ template: "", + standalone: false, }) export class GuardedRouteTestComponent {} @Component({ template: "", + standalone: false, }) export class RedirectTestComponent {} diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts index a57251c4f77..4449757167d 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/people/service-account-people.component.ts @@ -28,6 +28,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-people", templateUrl: "./service-account-people.component.html", + standalone: false, }) export class ServiceAccountPeopleComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts index 34a48af3055..af334b22c63 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/projects/service-account-projects.component.ts @@ -25,6 +25,7 @@ import { AccessPolicyService } from "../../shared/access-policies/access-policy. @Component({ selector: "sm-service-account-projects", templateUrl: "./service-account-projects.component.html", + standalone: false, }) export class ServiceAccountProjectsComponent implements OnInit, OnDestroy { private currentAccessPolicies: ApItemViewType[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts index 74465e8ecef..5eb074e3e99 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-account.component.ts @@ -18,6 +18,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-account", templateUrl: "./service-account.component.html", + standalone: false, }) export class ServiceAccountComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts index cbffc80aa05..47d5dd63806 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts-list.component.ts @@ -16,6 +16,7 @@ import { @Component({ selector: "sm-service-accounts-list", templateUrl: "./service-accounts-list.component.html", + standalone: false, }) export class ServiceAccountsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts index 7deaefae823..2813ece001f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/service-accounts.component.ts @@ -31,6 +31,7 @@ import { ServiceAccountService } from "./service-account.service"; @Component({ selector: "sm-service-accounts", templateUrl: "./service-accounts.component.html", + standalone: false, }) export class ServiceAccountsComponent implements OnInit { protected serviceAccounts$: Observable; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts index 2576ca8e629..0bed0355a8c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/dialog/sm-import-error-dialog.component.ts @@ -12,6 +12,7 @@ export interface SecretsManagerImportErrorDialogOperation { @Component({ templateUrl: "./sm-import-error-dialog.component.html", + standalone: false, }) export class SecretsManagerImportErrorDialogComponent { errorLines: SecretsManagerImportErrorLine[]; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts index 266962c8268..c2b726803c5 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-export.component.ts @@ -29,6 +29,7 @@ type ExportFormat = { @Component({ selector: "sm-export", templateUrl: "./sm-export.component.html", + standalone: false, }) export class SecretsManagerExportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts index 262f1a6b161..65075d12bf6 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/porting/sm-import.component.ts @@ -21,6 +21,7 @@ import { SecretsManagerPortingApiService } from "../services/sm-porting-api.serv @Component({ selector: "sm-import", templateUrl: "./sm-import.component.html", + standalone: false, }) export class SecretsManagerImportComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts index 34de4e4860b..fba3ff03ee0 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/access-policy-selector.component.ts @@ -30,6 +30,7 @@ import { ApPermissionEnum } from "./models/enums/ap-permission.enum"; multi: true, }, ], + standalone: false, }) export class AccessPolicySelectorComponent implements ControlValueAccessor, OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts index 935ee1c8518..9d2a3715e16 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts @@ -25,6 +25,7 @@ export enum BulkConfirmationResult { @Component({ selector: "sm-bulk-confirmation-dialog", templateUrl: "./bulk-confirmation-dialog.component.html", + standalone: false, }) export class BulkConfirmationDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts index 0a00c7f0431..fc7890f1654 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-status-dialog.component.ts @@ -20,6 +20,7 @@ export class BulkOperationStatus { @Component({ templateUrl: "./bulk-status-dialog.component.html", + standalone: false, }) export class BulkStatusDialogComponent implements OnInit { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts index b0a481d077d..18823130d22 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/new-menu.component.ts @@ -29,6 +29,7 @@ import { @Component({ selector: "sm-new-menu", templateUrl: "./new-menu.component.html", + standalone: false, }) export class NewMenuComponent implements OnInit, OnDestroy { private organizationId: string; diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts index 2eb9d6017b7..89baf969c34 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/org-suspended.component.ts @@ -12,6 +12,7 @@ import { Icon, Icons } from "@bitwarden/components"; @Component({ templateUrl: "./org-suspended.component.html", + standalone: false, }) export class OrgSuspendedComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts index 0ca5ba09074..9774172cd4b 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/projects-list.component.ts @@ -13,6 +13,7 @@ import { ProjectListView } from "../models/view/project-list.view"; @Component({ selector: "sm-projects-list", templateUrl: "./projects-list.component.html", + standalone: false, }) export class ProjectsListComponent { @Input() diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts index 37b9524238f..a7ee818a01f 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/secrets-list.component.ts @@ -15,6 +15,7 @@ import { SecretService } from "../secrets/secret.service"; @Component({ selector: "sm-secrets-list", templateUrl: "./secrets-list.component.html", + standalone: false, }) export class SecretsListComponent implements OnDestroy { protected dataSource = new TableDataSource(); diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts index 7c4fff4d3b3..29f9a85250c 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-hard-delete.component.ts @@ -15,6 +15,7 @@ export interface SecretHardDeleteOperation { @Component({ templateUrl: "./secret-hard-delete.component.html", + standalone: false, }) export class SecretHardDeleteDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts index 6b5efe5e133..712757445be 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/dialog/secret-restore.component.ts @@ -15,6 +15,7 @@ export interface SecretRestoreOperation { @Component({ templateUrl: "./secret-restore.component.html", + standalone: false, }) export class SecretRestoreDialogComponent { constructor( diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts index 3a21dbe3b68..4392ae8b1bb 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/trash/trash.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "sm-trash", templateUrl: "./trash.component.html", + standalone: false, }) export class TrashComponent implements OnInit { secrets$: Observable; diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index 16a249dda97..e6438b3e634 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -57,6 +57,7 @@ export interface EnvironmentSelectorRouteData { transition("* => void", animate("100ms linear", style({ opacity: 0 }))), ]), ], + standalone: false, }) export class EnvironmentSelectorComponent implements OnInit, OnDestroy { @Output() onOpenSelfHostedSettings = new EventEmitter(); diff --git a/libs/angular/src/auth/components/two-factor-icon.component.ts b/libs/angular/src/auth/components/two-factor-icon.component.ts index c75078e413a..0e811595bea 100644 --- a/libs/angular/src/auth/components/two-factor-icon.component.ts +++ b/libs/angular/src/auth/components/two-factor-icon.component.ts @@ -10,6 +10,7 @@ import { WebAuthnIcon } from "../icons/webauthn.icon"; @Component({ selector: "auth-two-factor-icon", templateUrl: "./two-factor-icon.component.html", + standalone: false, }) export class TwoFactorIconComponent { @Input() provider: any; diff --git a/libs/angular/src/auth/components/user-verification.component.ts b/libs/angular/src/auth/components/user-verification.component.ts index 408d8403b88..6f5021340c7 100644 --- a/libs/angular/src/auth/components/user-verification.component.ts +++ b/libs/angular/src/auth/components/user-verification.component.ts @@ -22,6 +22,7 @@ import { KeyService } from "@bitwarden/key-management"; */ @Directive({ selector: "app-user-verification", + standalone: false, }) export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy { private _invalidSecret = false; diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index 871895c2ede..a6f1db54241 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -48,6 +48,7 @@ type PayPalConfig = { @Component({ templateUrl: "./add-account-credit-dialog.component.html", + standalone: false, }) export class AddAccountCreditDialogComponent implements OnInit { @ViewChild("payPalForm", { read: ElementRef, static: true }) payPalForm: ElementRef; diff --git a/libs/angular/src/billing/components/invoices/invoices.component.ts b/libs/angular/src/billing/components/invoices/invoices.component.ts index 8984c1afe65..fc3352048d6 100644 --- a/libs/angular/src/billing/components/invoices/invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/invoices.component.ts @@ -11,6 +11,7 @@ import { FileDownloadService } from "@bitwarden/common/platform/abstractions/fil @Component({ selector: "app-invoices", templateUrl: "./invoices.component.html", + standalone: false, }) export class InvoicesComponent implements OnInit { @Input() startWith?: InvoicesResponse; diff --git a/libs/angular/src/billing/components/invoices/no-invoices.component.ts b/libs/angular/src/billing/components/invoices/no-invoices.component.ts index fffaaf65cfe..987650d1c83 100644 --- a/libs/angular/src/billing/components/invoices/no-invoices.component.ts +++ b/libs/angular/src/billing/components/invoices/no-invoices.component.ts @@ -30,6 +30,7 @@ const partnerTrustIcon = svgIcon`

{{ "noInvoicesToList" | i18n }}

`, + standalone: false, }) export class NoInvoicesComponent { icon = partnerTrustIcon; diff --git a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts index e966b4e0a75..7088d8edfcc 100644 --- a/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts +++ b/libs/angular/src/billing/components/manage-tax-information/manage-tax-information.component.ts @@ -11,6 +11,7 @@ import { CountryListItem, TaxInformation } from "@bitwarden/common/billing/model @Component({ selector: "app-manage-tax-information", templateUrl: "./manage-tax-information.component.html", + standalone: false, }) export class ManageTaxInformationComponent implements OnInit, OnDestroy { @Input() startWith: TaxInformation; diff --git a/libs/angular/src/billing/directives/not-premium.directive.ts b/libs/angular/src/billing/directives/not-premium.directive.ts index 5a1c636c009..41d62bb773e 100644 --- a/libs/angular/src/billing/directives/not-premium.directive.ts +++ b/libs/angular/src/billing/directives/not-premium.directive.ts @@ -9,6 +9,7 @@ import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abs */ @Directive({ selector: "[appNotPremium]", + standalone: false, }) export class NotPremiumDirective implements OnInit { constructor( diff --git a/libs/angular/src/components/callout.component.ts b/libs/angular/src/components/callout.component.ts index 1e5285cc3a9..215de49f676 100644 --- a/libs/angular/src/components/callout.component.ts +++ b/libs/angular/src/components/callout.component.ts @@ -12,6 +12,7 @@ import { CalloutTypes } from "@bitwarden/components"; @Component({ selector: "app-callout", templateUrl: "callout.component.html", + standalone: false, }) export class DeprecatedCalloutComponent implements OnInit { @Input() type: CalloutTypes = "info"; diff --git a/libs/angular/src/components/modal/dynamic-modal.component.ts b/libs/angular/src/components/modal/dynamic-modal.component.ts index ccbfa19868f..77491193916 100644 --- a/libs/angular/src/components/modal/dynamic-modal.component.ts +++ b/libs/angular/src/components/modal/dynamic-modal.component.ts @@ -18,6 +18,7 @@ import { ModalRef } from "./modal.ref"; @Component({ selector: "app-modal", template: "", + standalone: false, }) export class DynamicModalComponent implements AfterViewInit, OnDestroy { componentRef: ComponentRef; diff --git a/libs/angular/src/directives/a11y-invalid.directive.ts b/libs/angular/src/directives/a11y-invalid.directive.ts index 60580fcb60e..032c08d5332 100644 --- a/libs/angular/src/directives/a11y-invalid.directive.ts +++ b/libs/angular/src/directives/a11y-invalid.directive.ts @@ -6,6 +6,7 @@ import { Subscription } from "rxjs"; @Directive({ selector: "[appA11yInvalid]", + standalone: false, }) export class A11yInvalidDirective implements OnDestroy, OnInit { private sub: Subscription; diff --git a/libs/angular/src/directives/api-action.directive.ts b/libs/angular/src/directives/api-action.directive.ts index a07ab7d0413..85ba8a7489c 100644 --- a/libs/angular/src/directives/api-action.directive.ts +++ b/libs/angular/src/directives/api-action.directive.ts @@ -15,6 +15,7 @@ import { ValidationService } from "@bitwarden/common/platform/abstractions/valid */ @Directive({ selector: "[appApiAction]", + standalone: false, }) export class ApiActionDirective implements OnChanges { @Input() appApiAction: Promise; diff --git a/libs/angular/src/directives/box-row.directive.ts b/libs/angular/src/directives/box-row.directive.ts index 81fb93596f2..d36dcb7ff88 100644 --- a/libs/angular/src/directives/box-row.directive.ts +++ b/libs/angular/src/directives/box-row.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, OnInit } from "@angular/core"; @Directive({ selector: "[appBoxRow]", + standalone: false, }) export class BoxRowDirective implements OnInit { el: HTMLElement = null; diff --git a/libs/angular/src/directives/copy-text.directive.ts b/libs/angular/src/directives/copy-text.directive.ts index de26973e2c9..0f9018e19ad 100644 --- a/libs/angular/src/directives/copy-text.directive.ts +++ b/libs/angular/src/directives/copy-text.directive.ts @@ -7,6 +7,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl @Directive({ selector: "[appCopyText]", + standalone: false, }) export class CopyTextDirective { constructor( diff --git a/libs/angular/src/directives/fallback-src.directive.ts b/libs/angular/src/directives/fallback-src.directive.ts index 600782f3404..f1225245912 100644 --- a/libs/angular/src/directives/fallback-src.directive.ts +++ b/libs/angular/src/directives/fallback-src.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, HostListener, Input } from "@angular/core"; @Directive({ selector: "[appFallbackSrc]", + standalone: false, }) export class FallbackSrcDirective { @Input("appFallbackSrc") appFallbackSrc: string; diff --git a/libs/angular/src/directives/if-feature.directive.ts b/libs/angular/src/directives/if-feature.directive.ts index 0186592d2d0..aa10c9e8081 100644 --- a/libs/angular/src/directives/if-feature.directive.ts +++ b/libs/angular/src/directives/if-feature.directive.ts @@ -14,6 +14,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" */ @Directive({ selector: "[appIfFeature]", + standalone: false, }) export class IfFeatureDirective implements OnInit { /** diff --git a/libs/angular/src/directives/input-strip-spaces.directive.ts b/libs/angular/src/directives/input-strip-spaces.directive.ts index 3b8fee851c0..1718aaa49d6 100644 --- a/libs/angular/src/directives/input-strip-spaces.directive.ts +++ b/libs/angular/src/directives/input-strip-spaces.directive.ts @@ -5,6 +5,7 @@ import { NgControl } from "@angular/forms"; @Directive({ selector: "input[appInputStripSpaces]", + standalone: false, }) export class InputStripSpacesDirective { constructor( diff --git a/libs/angular/src/directives/input-verbatim.directive.ts b/libs/angular/src/directives/input-verbatim.directive.ts index deecae624f1..7bd18b12659 100644 --- a/libs/angular/src/directives/input-verbatim.directive.ts +++ b/libs/angular/src/directives/input-verbatim.directive.ts @@ -4,6 +4,7 @@ import { Directive, ElementRef, Input, OnInit, Renderer2 } from "@angular/core"; @Directive({ selector: "[appInputVerbatim]", + standalone: false, }) export class InputVerbatimDirective implements OnInit { @Input() set appInputVerbatim(condition: boolean | string) { diff --git a/libs/angular/src/directives/launch-click.directive.ts b/libs/angular/src/directives/launch-click.directive.ts index e748afabf49..b270dbba5e3 100644 --- a/libs/angular/src/directives/launch-click.directive.ts +++ b/libs/angular/src/directives/launch-click.directive.ts @@ -5,6 +5,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; @Directive({ selector: "[appLaunchClick]", + standalone: false, }) export class LaunchClickDirective { constructor(private platformUtilsService: PlatformUtilsService) {} diff --git a/libs/angular/src/directives/stop-click.directive.ts b/libs/angular/src/directives/stop-click.directive.ts index 0e88dde33a4..58ae8082aa7 100644 --- a/libs/angular/src/directives/stop-click.directive.ts +++ b/libs/angular/src/directives/stop-click.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopClick]", + standalone: false, }) export class StopClickDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/stop-prop.directive.ts b/libs/angular/src/directives/stop-prop.directive.ts index 8393e799038..6ea9bc87bec 100644 --- a/libs/angular/src/directives/stop-prop.directive.ts +++ b/libs/angular/src/directives/stop-prop.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostListener } from "@angular/core"; @Directive({ selector: "[appStopProp]", + standalone: false, }) export class StopPropDirective { @HostListener("click", ["$event"]) onClick($event: MouseEvent) { diff --git a/libs/angular/src/directives/true-false-value.directive.ts b/libs/angular/src/directives/true-false-value.directive.ts index dab88567a5c..5d25ac2a385 100644 --- a/libs/angular/src/directives/true-false-value.directive.ts +++ b/libs/angular/src/directives/true-false-value.directive.ts @@ -11,6 +11,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; multi: true, }, ], + standalone: false, }) export class TrueFalseValueDirective implements ControlValueAccessor { @Input() trueValue: boolean | string = true; diff --git a/libs/angular/src/pipes/color-password-count.pipe.ts b/libs/angular/src/pipes/color-password-count.pipe.ts index ed94e6dadff..61be6db2e2c 100644 --- a/libs/angular/src/pipes/color-password-count.pipe.ts +++ b/libs/angular/src/pipes/color-password-count.pipe.ts @@ -7,7 +7,10 @@ import { ColorPasswordPipe } from "./color-password.pipe"; /* An updated pipe that extends ColourPasswordPipe to include a character count */ -@Pipe({ name: "colorPasswordCount" }) +@Pipe({ + name: "colorPasswordCount", + standalone: false, +}) export class ColorPasswordCountPipe extends ColorPasswordPipe { transform(password: string) { const template = (character: string, type: string, index: number) => diff --git a/libs/angular/src/pipes/color-password.pipe.ts b/libs/angular/src/pipes/color-password.pipe.ts index 8407eddd9ca..6bb7bd4120d 100644 --- a/libs/angular/src/pipes/color-password.pipe.ts +++ b/libs/angular/src/pipes/color-password.pipe.ts @@ -6,7 +6,10 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; An updated pipe that sanitizes HTML, highlights numbers and special characters (in different colors each) and handles Unicode / Emoji characters correctly. */ -@Pipe({ name: "colorPassword" }) +@Pipe({ + name: "colorPassword", + standalone: false, +}) export class ColorPasswordPipe implements PipeTransform { transform(password: string) { const template = (character: string, type: string) => diff --git a/libs/angular/src/pipes/credit-card-number.pipe.ts b/libs/angular/src/pipes/credit-card-number.pipe.ts index 11500ef9510..80718612d35 100644 --- a/libs/angular/src/pipes/credit-card-number.pipe.ts +++ b/libs/angular/src/pipes/credit-card-number.pipe.ts @@ -28,7 +28,10 @@ const numberFormats: Record = { Other: [{ cardLength: 16, blocks: [4, 4, 4, 4] }], }; -@Pipe({ name: "creditCardNumber" }) +@Pipe({ + name: "creditCardNumber", + standalone: false, +}) export class CreditCardNumberPipe implements PipeTransform { transform(creditCardNumber: string, brand: string): string { let rules = numberFormats[brand]; diff --git a/libs/angular/src/pipes/search-ciphers.pipe.ts b/libs/angular/src/pipes/search-ciphers.pipe.ts index 04349e9842a..cbb595280af 100644 --- a/libs/angular/src/pipes/search-ciphers.pipe.ts +++ b/libs/angular/src/pipes/search-ciphers.pipe.ts @@ -4,6 +4,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @Pipe({ name: "searchCiphers", + standalone: false, }) export class SearchCiphersPipe implements PipeTransform { transform(ciphers: CipherView[], searchText: string, deleted = false): CipherView[] { diff --git a/libs/angular/src/pipes/search.pipe.ts b/libs/angular/src/pipes/search.pipe.ts index 9f640a5b48e..2c652bc6382 100644 --- a/libs/angular/src/pipes/search.pipe.ts +++ b/libs/angular/src/pipes/search.pipe.ts @@ -6,6 +6,7 @@ type PropertyValueFunction = (item: T) => { toString: () => string }; @Pipe({ name: "search", + standalone: false, }) export class SearchPipe implements PipeTransform { transform( diff --git a/libs/angular/src/pipes/user-name.pipe.ts b/libs/angular/src/pipes/user-name.pipe.ts index 29b884a3115..4c295c7ef13 100644 --- a/libs/angular/src/pipes/user-name.pipe.ts +++ b/libs/angular/src/pipes/user-name.pipe.ts @@ -9,6 +9,7 @@ export interface User { @Pipe({ name: "userName", + standalone: false, }) export class UserNamePipe implements PipeTransform { transform(user?: User): string { diff --git a/libs/angular/src/pipes/user-type.pipe.ts b/libs/angular/src/pipes/user-type.pipe.ts index 109abc3eeff..d9b5faa2ea9 100644 --- a/libs/angular/src/pipes/user-type.pipe.ts +++ b/libs/angular/src/pipes/user-type.pipe.ts @@ -5,6 +5,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic @Pipe({ name: "userType", + standalone: false, }) export class UserTypePipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/platform/pipes/ellipsis.pipe.ts b/libs/angular/src/platform/pipes/ellipsis.pipe.ts index dd271f94627..a7050b2f037 100644 --- a/libs/angular/src/platform/pipes/ellipsis.pipe.ts +++ b/libs/angular/src/platform/pipes/ellipsis.pipe.ts @@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from "@angular/core"; @Pipe({ name: "ellipsis", + standalone: false, }) /** * @deprecated Use the tailwind class 'tw-truncate' instead diff --git a/libs/angular/src/platform/pipes/fingerprint.pipe.ts b/libs/angular/src/platform/pipes/fingerprint.pipe.ts index 8f1a07cfd6b..90289ee212b 100644 --- a/libs/angular/src/platform/pipes/fingerprint.pipe.ts +++ b/libs/angular/src/platform/pipes/fingerprint.pipe.ts @@ -5,6 +5,7 @@ import { KeyService } from "@bitwarden/key-management"; @Pipe({ name: "fingerprint", + standalone: false, }) export class FingerprintPipe { constructor(private keyService: KeyService) {} diff --git a/libs/angular/src/platform/pipes/i18n.pipe.ts b/libs/angular/src/platform/pipes/i18n.pipe.ts index a6fdbc78255..a1c6122ba16 100644 --- a/libs/angular/src/platform/pipes/i18n.pipe.ts +++ b/libs/angular/src/platform/pipes/i18n.pipe.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", + standalone: false, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/angular/src/tools/password-strength/password-strength.component.ts b/libs/angular/src/tools/password-strength/password-strength.component.ts index d23225b7c0c..ca9892d9c6c 100644 --- a/libs/angular/src/tools/password-strength/password-strength.component.ts +++ b/libs/angular/src/tools/password-strength/password-strength.component.ts @@ -16,6 +16,7 @@ export interface PasswordColorText { @Component({ selector: "app-password-strength", templateUrl: "password-strength.component.html", + standalone: false, }) export class PasswordStrengthComponent implements OnChanges { @Input() showText = false; diff --git a/libs/angular/src/vault/components/icon.component.ts b/libs/angular/src/vault/components/icon.component.ts index 248378bf5ee..fd178db23b6 100644 --- a/libs/angular/src/vault/components/icon.component.ts +++ b/libs/angular/src/vault/components/icon.component.ts @@ -19,6 +19,7 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; selector: "app-vault-icon", templateUrl: "icon.component.html", changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, }) export class IconComponent { /** diff --git a/libs/components/src/app/app.component.ts b/libs/components/src/app/app.component.ts index d4c9ddd2f41..ed8cf595d5e 100644 --- a/libs/components/src/app/app.component.ts +++ b/libs/components/src/app/app.component.ts @@ -3,6 +3,7 @@ import { Component } from "@angular/core"; @Component({ selector: "app-root", template: "", + standalone: false, }) export class AppComponent { title = "components"; diff --git a/libs/components/src/input/autofocus.directive.ts b/libs/components/src/input/autofocus.directive.ts index 46eb1b15b16..3fd06156f39 100644 --- a/libs/components/src/input/autofocus.directive.ts +++ b/libs/components/src/input/autofocus.directive.ts @@ -19,6 +19,7 @@ import { FocusableElement } from "../shared/focusable-element"; */ @Directive({ selector: "[appAutofocus], [bitAutofocus]", + standalone: false, }) export class AutofocusDirective implements AfterContentChecked { @Input() set appAutofocus(condition: boolean | string) { diff --git a/libs/tools/generator/components/src/catchall-settings.component.ts b/libs/tools/generator/components/src/catchall-settings.component.ts index 92d0909e661..3bf586c5a29 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.ts +++ b/libs/tools/generator/components/src/catchall-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-catchall-settings", templateUrl: "catchall-settings.component.html", + standalone: false, }) export class CatchallSettingsComponent implements OnInit, OnDestroy, OnChanges { /** Instantiates the component diff --git a/libs/tools/generator/components/src/credential-generator.component.ts b/libs/tools/generator/components/src/credential-generator.component.ts index 4a83f2a9a92..0b48b4cf0f7 100644 --- a/libs/tools/generator/components/src/credential-generator.component.ts +++ b/libs/tools/generator/components/src/credential-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-credential-generator", templateUrl: "credential-generator.component.html", + standalone: false, }) export class CredentialGeneratorComponent implements OnInit, OnChanges, OnDestroy { private readonly destroyed = new Subject(); diff --git a/libs/tools/generator/components/src/forwarder-settings.component.ts b/libs/tools/generator/components/src/forwarder-settings.component.ts index 8a5311fb7f3..689cc7e258c 100644 --- a/libs/tools/generator/components/src/forwarder-settings.component.ts +++ b/libs/tools/generator/components/src/forwarder-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-forwarder-settings", templateUrl: "forwarder-settings.component.html", + standalone: false, }) export class ForwarderSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/passphrase-settings.component.ts b/libs/tools/generator/components/src/passphrase-settings.component.ts index 0509ceab60f..405914977c5 100644 --- a/libs/tools/generator/components/src/passphrase-settings.component.ts +++ b/libs/tools/generator/components/src/passphrase-settings.component.ts @@ -33,6 +33,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-passphrase-settings", templateUrl: "passphrase-settings.component.html", + standalone: false, }) export class PassphraseSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/password-generator.component.ts b/libs/tools/generator/components/src/password-generator.component.ts index e4e173829a6..9643c857473 100644 --- a/libs/tools/generator/components/src/password-generator.component.ts +++ b/libs/tools/generator/components/src/password-generator.component.ts @@ -53,6 +53,7 @@ import { GeneratorHistoryService } from "@bitwarden/generator-history"; @Component({ selector: "tools-password-generator", templateUrl: "password-generator.component.html", + standalone: false, }) export class PasswordGeneratorComponent implements OnInit, OnChanges, OnDestroy { constructor( diff --git a/libs/tools/generator/components/src/password-settings.component.ts b/libs/tools/generator/components/src/password-settings.component.ts index e4005c6fda7..346e9549cd8 100644 --- a/libs/tools/generator/components/src/password-settings.component.ts +++ b/libs/tools/generator/components/src/password-settings.component.ts @@ -37,6 +37,7 @@ const Controls = Object.freeze({ @Component({ selector: "tools-password-settings", templateUrl: "password-settings.component.html", + standalone: false, }) export class PasswordSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/subaddress-settings.component.ts b/libs/tools/generator/components/src/subaddress-settings.component.ts index 8bde260693c..b09ecc86f9e 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.ts +++ b/libs/tools/generator/components/src/subaddress-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-subaddress-settings", templateUrl: "subaddress-settings.component.html", + standalone: false, }) export class SubaddressSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component diff --git a/libs/tools/generator/components/src/username-generator.component.ts b/libs/tools/generator/components/src/username-generator.component.ts index 0c06b89adb4..de48a9bd6b1 100644 --- a/libs/tools/generator/components/src/username-generator.component.ts +++ b/libs/tools/generator/components/src/username-generator.component.ts @@ -67,6 +67,7 @@ const NONE_SELECTED = "none"; @Component({ selector: "tools-username-generator", templateUrl: "username-generator.component.html", + standalone: false, }) export class UsernameGeneratorComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the username generator diff --git a/libs/tools/generator/components/src/username-settings.component.ts b/libs/tools/generator/components/src/username-settings.component.ts index 7e59ef9c379..ea3cfbd35fb 100644 --- a/libs/tools/generator/components/src/username-settings.component.ts +++ b/libs/tools/generator/components/src/username-settings.component.ts @@ -24,6 +24,7 @@ import { @Component({ selector: "tools-username-settings", templateUrl: "username-settings.component.html", + standalone: false, }) export class UsernameSettingsComponent implements OnInit, OnChanges, OnDestroy { /** Instantiates the component From b30faeb62ba2c257f691454960b082f799618651 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Thu, 15 May 2025 10:09:51 -0700 Subject: [PATCH 30/54] [PM-21554] - Creating a new item while editing edits the item (#14770) * fix adding new cipher while editing a cipher * don't set updatedCipherView if the cached cipher has an id and the new one doesn't * fix cipher form config --- apps/desktop/src/vault/app/vault/vault-v2.component.ts | 1 + .../src/cipher-form/components/cipher-form.component.ts | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 66e77580d1c..6c60aaf0f02 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -538,6 +538,7 @@ export class VaultV2Component implements OnInit, OnDestroy { } this.addType = type || this.activeFilter.cipherType; this.cipher = new CipherView(); + this.cipherId = null; await this.buildFormConfig("add"); this.action = "add"; this.prefillCipherFromFilter(); diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts index 8b99b60bc16..eebfca65f36 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts @@ -275,11 +275,6 @@ export class CipherFormComponent implements AfterViewInit, OnInit, OnChanges, Ci if (this.updatedCipherView.id === cachedCipher.id) { this.updatedCipherView = cachedCipher; } - - // `id` is null when a cipher is being added - if (this.updatedCipherView.id === null) { - this.updatedCipherView = cachedCipher; - } } constructor( From 82d0925f4eb2727b8c635d6aeef0355fb8e8c602 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Thu, 15 May 2025 13:32:05 -0400 Subject: [PATCH 31/54] PM-21620 finalize a11y UX concerns for option selection (#14777) * PM-21620 finalize a11y UX concerns for option selection * SR should announce that the button has a menu popup collapsed and expanded when they open it * support up and down keys -close menu when other menu expanded * dynamic aria label * type safety * instanceOf to replace as Node for type * default aria hidden prop that can be overridden * update mock and make message more descriptive * Update apps/browser/src/autofill/content/components/icons/collection-shared.ts Co-authored-by: Jonathan Prusik --------- Co-authored-by: Jonathan Prusik --- apps/browser/src/_locales/en/messages.json | 17 ++++++-- .../buttons/option-selection-button.ts | 3 ++ .../content/components/common-types.ts | 1 + .../content/components/icons/angle-down.ts | 9 +++- .../content/components/icons/angle-up.ts | 9 +++- .../content/components/icons/business.ts | 9 +++- .../content/components/icons/close.ts | 9 +++- .../components/icons/collection-shared.ts | 9 +++- .../components/icons/exclamation-triangle.ts | 9 +++- .../content/components/icons/external-link.ts | 9 +++- .../content/components/icons/family.ts | 9 +++- .../content/components/icons/folder.ts | 9 +++- .../content/components/icons/globe.ts | 9 +++- .../content/components/icons/pencil-square.ts | 9 +++- .../content/components/icons/shield.ts | 9 +++- .../autofill/content/components/icons/user.ts | 9 +++- .../components/lit-stories/mock-data.ts | 1 + .../option-selection/option-item.ts | 16 +++++++- .../option-selection/option-items.ts | 28 ++++++++++++- .../option-selection/option-selection.ts | 41 ++++++++++++++++--- 20 files changed, 187 insertions(+), 37 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index df35facff3c..a14d769d4cd 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1090,13 +1090,24 @@ }, "notificationLoginSaveConfirmation": { "message": "saved to Bitwarden.", - "description": "Shown to user after item is saved." }, "notificationLoginUpdatedConfirmation": { "message": "updated in Bitwarden.", "description": "Shown to user after item is updated." }, + "selectItemAriaLabel": { + "message": "Select $ITEMTYPE$, $ITEMNAME$", + "description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.", + "placeholders": { + "itemType": { + "content": "$1" + }, + "itemName": { + "content": "$2" + } + } + }, "saveAsNewLoginAction": { "message": "Save as new login", "description": "Button text for saving login details as a new entry." @@ -3585,7 +3596,7 @@ "orgTrustWarning1": { "message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint." }, - "trustUser":{ + "trustUser": { "message": "Trust user" }, "sendsNoItemsTitle": { @@ -5325,4 +5336,4 @@ "noPermissionsViewPage": { "message": "You do not have permissions to view this page. Try logging in with a different account." } -} +} \ No newline at end of file diff --git a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts index e3c7e0d54e6..3912c791d34 100644 --- a/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/option-selection-button.ts @@ -33,6 +33,9 @@ export function OptionSelectionButton({ class=${selectionButtonStyles({ disabled, toggledOn, theme })} title=${text} type="button" + aria-haspopup="menu" + aria-expanded=${toggledOn} + aria-controls="option-menu" @click=${handleButtonClick} > ${buttonIcon ?? nothing} diff --git a/apps/browser/src/autofill/content/components/common-types.ts b/apps/browser/src/autofill/content/components/common-types.ts index 740b6963b16..5967f6205a9 100644 --- a/apps/browser/src/autofill/content/components/common-types.ts +++ b/apps/browser/src/autofill/content/components/common-types.ts @@ -11,6 +11,7 @@ export type IconProps = { color?: string; disabled?: boolean; theme: Theme; + ariaHidden?: boolean; }; export type Option = { diff --git a/apps/browser/src/autofill/content/components/icons/angle-down.ts b/apps/browser/src/autofill/content/components/icons/angle-down.ts index 27cd5ab81c5..2bd54f7dbee 100644 --- a/apps/browser/src/autofill/content/components/icons/angle-down.ts +++ b/apps/browser/src/autofill/content/components/icons/angle-down.ts @@ -4,11 +4,16 @@ import { html } from "lit"; import { IconProps } from "../common-types"; import { buildIconColorRule, ruleNames, themes } from "../constants/styles"; -export function AngleDown({ color, disabled, theme }: IconProps) { +export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) { const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main; return html` - + + + + + + + + + +
diff --git a/apps/browser/src/autofill/content/components/option-selection/option-items.ts b/apps/browser/src/autofill/content/components/option-selection/option-items.ts index bb7f5afd0fd..854b7c05df1 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-items.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-items.ts @@ -33,17 +33,41 @@ export function OptionItems({ const isSafari = false; return html` -
+
handleMenuKeyUp(e)} + > ${label ? html`
${label}
` : nothing}
${options.map((option) => - OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }), + OptionItem({ + ...option, + theme, + contextLabel: label, + handleSelection: () => handleOptionSelection(option), + }), )}
`; } +function handleMenuKeyUp(event: KeyboardEvent) { + const items = [ + ...(event.currentTarget as HTMLElement).querySelectorAll('[tabindex="0"]'), + ]; + const index = items.indexOf(document.activeElement as HTMLElement); + const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0; + + if (index === -1 || direction === 0) { + return; + } + + event.preventDefault(); + items[(index + direction + items.length) % items.length]?.focus(); +} + const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css` ${typography.body1} diff --git a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts index 49b51852a39..c7dceb2b5b4 100644 --- a/apps/browser/src/autofill/content/components/option-selection/option-selection.ts +++ b/apps/browser/src/autofill/content/components/option-selection/option-selection.ts @@ -48,10 +48,18 @@ export class OptionSelection extends LitElement { @state() private selection?: Option; - private handleButtonClick = (event: Event) => { + private static currentOpenInstance: OptionSelection | null = null; + + private handleButtonClick = async (event: Event) => { if (!this.disabled) { - // Menu is about to be shown - if (!this.showMenu) { + const isOpening = !this.showMenu; + + if (isOpening) { + if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) { + OptionSelection.currentOpenInstance.showMenu = false; + } + OptionSelection.currentOpenInstance = this; + this.menuTopOffset = this.offsetTop; // Distance from right edge of button to left edge of the viewport @@ -71,9 +79,29 @@ export class OptionSelection extends LitElement { optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2; this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold; + } else { + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } - this.showMenu = !this.showMenu; + this.showMenu = isOpening; + + if (this.showMenu) { + await this.updateComplete; + const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement; + firstItem?.focus(); + } + } + }; + + private handleFocusOut = (event: FocusEvent) => { + const relatedTarget = event.relatedTarget; + if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) { + this.showMenu = false; + if (OptionSelection.currentOpenInstance === this) { + OptionSelection.currentOpenInstance = null; + } } }; @@ -95,7 +123,10 @@ export class OptionSelection extends LitElement { } return html` -
+
${OptionSelectionButton({ disabled: this.disabled, icon: this.selection?.icon, From ee4c3cfd9475b57118f06eb5dedc82707af92e26 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 15 May 2025 15:10:38 -0400 Subject: [PATCH 32/54] [PM-21663] nudge service name refactor (#14789) * update names of vault nudge service and their corresponding files, convert components using showNudge$ to instead target spotlight and badges directly with new observables. Core logic for dismiss remains the same --- .../popup/settings/autofill.component.ts | 20 ++--- apps/browser/src/popup/tabs-v2.component.ts | 6 +- .../popup/settings/settings-v2.component.html | 6 +- .../popup/settings/settings-v2.component.ts | 26 +++--- .../vault-v2/vault-v2.component.html | 4 +- .../components/vault-v2/vault-v2.component.ts | 18 ++-- .../settings/download-bitwarden.component.ts | 6 +- .../src/platform/state/state-definitions.ts | 2 +- .../src/cipher-form/cipher-form.stories.ts | 4 +- .../new-item-nudge.component.spec.ts | 23 +++-- .../new-item-nudge.component.ts | 28 +++--- libs/vault/src/index.ts | 2 +- .../autofill-nudge.service.ts | 6 +- .../download-bitwarden-nudge.service.ts | 4 +- .../empty-vault-nudge.service.ts | 4 +- .../has-items-nudge.service.ts | 6 +- .../new-item-nudge.service.ts | 14 +-- .../services/default-single-nudge.service.ts | 24 ++---- ...service.spec.ts => nudges.service.spec.ts} | 62 +++++++++---- ...lt-nudges.service.ts => nudges.service.ts} | 86 +++++++++++++------ 20 files changed, 199 insertions(+), 152 deletions(-) rename libs/vault/src/services/{vault-nudges.service.spec.ts => nudges.service.spec.ts} (72%) rename libs/vault/src/services/{vault-nudges.service.ts => nudges.service.ts} (56%) diff --git a/apps/browser/src/autofill/popup/settings/autofill.component.ts b/apps/browser/src/autofill/popup/settings/autofill.component.ts index d63f9a4589d..2d29067cf0f 100644 --- a/apps/browser/src/autofill/popup/settings/autofill.component.ts +++ b/apps/browser/src/autofill/popup/settings/autofill.component.ts @@ -4,14 +4,14 @@ import { CommonModule } from "@angular/common"; import { Component, DestroyRef, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { + FormBuilder, + FormControl, + FormGroup, FormsModule, ReactiveFormsModule, - FormBuilder, - FormGroup, - FormControl, } from "@angular/forms"; import { RouterModule } from "@angular/router"; -import { Observable, filter, firstValueFrom, map, switchMap } from "rxjs"; +import { filter, firstValueFrom, Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -55,7 +55,7 @@ import { SelectModule, TypographyModule, } from "@bitwarden/components"; -import { SpotlightComponent, VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType, SpotlightComponent } from "@bitwarden/vault"; import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service"; import { BrowserApi } from "../../../platform/browser/browser-api"; @@ -108,9 +108,7 @@ export class AutofillComponent implements OnInit { protected showSpotlightNudge$: Observable = this.accountService.activeAccount$.pipe( filter((account): account is Account => account !== null), switchMap((account) => - this.vaultNudgesService - .showNudge$(VaultNudgeType.AutofillNudge, account.id) - .pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)), + this.nudgesService.showNudgeSpotlight$(NudgeType.AutofillNudge, account.id), ), ); @@ -155,7 +153,7 @@ export class AutofillComponent implements OnInit { private configService: ConfigService, private formBuilder: FormBuilder, private destroyRef: DestroyRef, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private autofillBrowserSettingsService: AutofillBrowserSettingsService, ) { @@ -343,8 +341,8 @@ export class AutofillComponent implements OnInit { } async dismissSpotlight() { - await this.vaultNudgesService.dismissNudge( - VaultNudgeType.AutofillNudge, + await this.nudgesService.dismissNudge( + NudgeType.AutofillNudge, await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)), ); } diff --git a/apps/browser/src/popup/tabs-v2.component.ts b/apps/browser/src/popup/tabs-v2.component.ts index 5df8fb85d6d..0ca763d510d 100644 --- a/apps/browser/src/popup/tabs-v2.component.ts +++ b/apps/browser/src/popup/tabs-v2.component.ts @@ -6,7 +6,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Icons } from "@bitwarden/components"; -import { VaultNudgesService } from "@bitwarden/vault"; +import { NudgesService } from "@bitwarden/vault"; import { NavButton } from "../platform/popup/layout/popup-tab-navigation.component"; @@ -18,7 +18,7 @@ import { NavButton } from "../platform/popup/layout/popup-tab-navigation.compone export class TabsV2Component { private hasActiveBadges$ = this.accountService.activeAccount$ .pipe(getUserId) - .pipe(switchMap((userId) => this.vaultNudgesService.hasActiveBadges$(userId))); + .pipe(switchMap((userId) => this.nudgesService.hasActiveBadges$(userId))); protected navButtons$: Observable = combineLatest([ this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge), this.hasActiveBadges$, @@ -54,7 +54,7 @@ export class TabsV2Component { }), ); constructor( - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, private readonly configService: ConfigService, ) {} diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 8d31ccf8371..dc53f95a7cf 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -41,7 +41,7 @@
@@ -51,7 +51,7 @@ Will make this dynamic when more nudges are added -->

{{ "downloadBitwardenOnAllDevices" | i18n }}

= this.authenticatedAccount$.pipe( + downloadBitwardenNudgeStatus$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.DownloadBitwarden, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id), ), ); - showVaultBadge$: Observable = this.authenticatedAccount$.pipe( + showVaultBadge$: Observable = this.authenticatedAccount$.pipe( switchMap((account) => - this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, account.id), + this.nudgesService.showNudgeBadge$(NudgeType.EmptyVaultNudge, account.id), ), ); @@ -68,9 +68,9 @@ export class SettingsV2Component implements OnInit { this.authenticatedAccount$, ]).pipe( switchMap(([defaultBrowserAutofillDisabled, account]) => - this.vaultNudgesService.showNudge$(VaultNudgeType.AutofillNudge, account.id).pipe( - map((nudgeStatus) => { - return !defaultBrowserAutofillDisabled && nudgeStatus.hasBadgeDismissed === false; + this.nudgesService.showNudgeBadge$(NudgeType.AutofillNudge, account.id).pipe( + map((badgeStatus) => { + return !defaultBrowserAutofillDisabled && badgeStatus; }), ), ), @@ -81,7 +81,7 @@ export class SettingsV2Component implements OnInit { ); constructor( - private readonly vaultNudgesService: VaultNudgesService, + private readonly nudgesService: NudgesService, private readonly accountService: AccountService, private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService, private readonly configService: ConfigService, @@ -94,10 +94,10 @@ export class SettingsV2Component implements OnInit { ); } - async dismissBadge(type: VaultNudgeType) { - if (!(await firstValueFrom(this.showVaultBadge$)).hasBadgeDismissed) { + async dismissBadge(type: NudgeType) { + if (await firstValueFrom(this.showVaultBadge$)) { const account = await firstValueFrom(this.authenticatedAccount$); - await this.vaultNudgesService.dismissNudge(type, account.id as UserId, true); + await this.nudgesService.dismissNudge(type, account.id as UserId, true); } } } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html index 43a96fc616e..42e772be062 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.html @@ -36,7 +36,7 @@ [subtitle]="'emptyVaultNudgeBody' | i18n" [buttonText]="'emptyVaultNudgeButton' | i18n" (onButtonClick)="navigateToImport()" - (onDismiss)="dismissVaultNudgeSpotlight(VaultNudgeType.EmptyVaultNudge)" + (onDismiss)="dismissVaultNudgeSpotlight(NudgeType.EmptyVaultNudge)" > @@ -44,7 +44,7 @@
  • {{ "hasItemsVaultNudgeBodyOne" | i18n }}
  • diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index ec4f3939204..8dc4c639574 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -32,10 +32,10 @@ import { } from "@bitwarden/components"; import { DecryptionFailureDialogComponent, + NudgesService, + NudgeType, SpotlightComponent, VaultIcons, - VaultNudgesService, - VaultNudgeType, } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../../auth/popup/account-switching/current-account.component"; @@ -96,18 +96,16 @@ enum VaultState { export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { @ViewChild(CdkVirtualScrollableElement) virtualScrollElement?: CdkVirtualScrollableElement; - VaultNudgeType = VaultNudgeType; + NudgeType = NudgeType; cipherType = CipherType; private activeUserId$ = this.accountService.activeAccount$.pipe(getUserId); showEmptyVaultSpotlight$: Observable = this.activeUserId$.pipe( switchMap((userId) => - this.vaultNudgesService.showNudge$(VaultNudgeType.EmptyVaultNudge, userId), + this.nudgesService.showNudgeSpotlight$(NudgeType.EmptyVaultNudge, userId), ), - map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), ); showHasItemsVaultSpotlight$: Observable = this.activeUserId$.pipe( - switchMap((userId) => this.vaultNudgesService.showNudge$(VaultNudgeType.HasVaultItems, userId)), - map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed), + switchMap((userId) => this.nudgesService.showNudgeSpotlight$(NudgeType.HasVaultItems, userId)), ); activeUserId: UserId | null = null; @@ -159,7 +157,7 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { private dialogService: DialogService, private vaultCopyButtonsService: VaultPopupCopyButtonsService, private introCarouselService: IntroCarouselService, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, private router: Router, private i18nService: I18nService, ) { @@ -229,8 +227,8 @@ export class VaultV2Component implements OnInit, AfterViewInit, OnDestroy { } } - async dismissVaultNudgeSpotlight(type: VaultNudgeType) { - await this.vaultNudgesService.dismissNudge(type, this.activeUserId as UserId); + async dismissVaultNudgeSpotlight(type: NudgeType) { + await this.nudgesService.dismissNudge(type, this.activeUserId as UserId); } protected readonly FeatureFlag = FeatureFlag; diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index 9f04bb58c34..a79aa1d3f14 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -7,7 +7,7 @@ import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; -import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; +import { NudgesService, NudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; import { PopOutComponent } from "../../../platform/popup/components/pop-out.component"; @@ -32,12 +32,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co }) export class DownloadBitwardenComponent implements OnInit { constructor( - private vaultNudgeService: VaultNudgesService, + private nudgesService: NudgesService, private accountService: AccountService, ) {} async ngOnInit() { const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - await this.vaultNudgeService.dismissNudge(VaultNudgeType.DownloadBitwarden, userId); + await this.nudgesService.dismissNudge(NudgeType.DownloadBitwarden, userId); } } diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index d93a3f4da77..d7a5b4795e5 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -199,7 +199,7 @@ export const VAULT_APPEARANCE = new StateDefinition("vaultAppearance", "disk"); export const SECURITY_TASKS_DISK = new StateDefinition("securityTasks", "disk"); export const AT_RISK_PASSWORDS_PAGE_DISK = new StateDefinition("atRiskPasswordsPage", "disk"); export const NOTIFICATION_DISK = new StateDefinition("notifications", "disk"); -export const VAULT_NUDGES_DISK = new StateDefinition("vaultNudges", "disk", { web: "disk-local" }); +export const NUDGES_DISK = new StateDefinition("nudges", "disk", { web: "disk-local" }); export const VAULT_BROWSER_INTRO_CAROUSEL = new StateDefinition( "vaultBrowserIntroCarousel", "disk", diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 7090502ef14..137b220014a 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -36,7 +36,7 @@ import { CipherFormGenerationService, NudgeStatus, PasswordRepromptService, - VaultNudgesService, + NudgesService, } from "@bitwarden/vault"; // FIXME: remove `/apps` import from `/libs` // FIXME: remove `src` and fix import @@ -144,7 +144,7 @@ export default { ], providers: [ { - provide: VaultNudgesService, + provide: NudgesService, useValue: { showNudge$: new BehaviorSubject({ hasBadgeDismissed: true, diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts index 169d3b69a9b..4d5bb49c337 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.spec.ts @@ -8,7 +8,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/sdk-internal"; -import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; +import { NudgesService, NudgeType } from "../../../services/nudges.service"; import { NewItemNudgeComponent } from "./new-item-nudge.component"; @@ -18,19 +18,19 @@ describe("NewItemNudgeComponent", () => { let i18nService: MockProxy; let accountService: MockProxy; - let vaultNudgesService: MockProxy; + let nudgesService: MockProxy; beforeEach(async () => { i18nService = mock({ t: (key: string) => key }); accountService = mock(); - vaultNudgesService = mock(); + nudgesService = mock(); await TestBed.configureTestingModule({ imports: [NewItemNudgeComponent, CommonModule], providers: [ { provide: I18nService, useValue: i18nService }, { provide: AccountService, useValue: accountService }, - { provide: VaultNudgesService, useValue: vaultNudgesService }, + { provide: NudgesService, useValue: nudgesService }, ], }).compileComponents(); }); @@ -58,7 +58,7 @@ describe("NewItemNudgeComponent", () => { expect(component.nudgeBody).toBe( "newLoginNudgeBodyOne newLoginNudgeBodyBold newLoginNudgeBodyTwo", ); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newLoginItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewLoginItemStatus); }); it("should set nudge title and body for CipherType.Card type", async () => { @@ -71,7 +71,7 @@ describe("NewItemNudgeComponent", () => { expect(component.showNewItemSpotlight).toBe(true); expect(component.nudgeTitle).toBe("newCardNudgeTitle"); expect(component.nudgeBody).toBe("newCardNudgeBody"); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newCardItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewCardItemStatus); }); it("should not show anything if spotlight has been dismissed", async () => { @@ -82,22 +82,19 @@ describe("NewItemNudgeComponent", () => { await component.ngOnInit(); expect(component.showNewItemSpotlight).toBe(false); - expect(component.dismissalNudgeType).toBe(VaultNudgeType.newIdentityItemStatus); + expect(component.dismissalNudgeType).toBe(NudgeType.NewIdentityItemStatus); }); it("should set showNewItemSpotlight to false when user dismisses spotlight", async () => { component.showNewItemSpotlight = true; - component.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + component.dismissalNudgeType = NudgeType.NewLoginItemStatus; component.activeUserId = "test-user-id" as UserId; - const dismissSpy = jest.spyOn(vaultNudgesService, "dismissNudge").mockResolvedValue(); + const dismissSpy = jest.spyOn(nudgesService, "dismissNudge").mockResolvedValue(); await component.dismissNewItemSpotlight(); expect(component.showNewItemSpotlight).toBe(false); - expect(dismissSpy).toHaveBeenCalledWith( - VaultNudgeType.newLoginItemStatus, - component.activeUserId, - ); + expect(dismissSpy).toHaveBeenCalledWith(NudgeType.NewLoginItemStatus, component.activeUserId); }); }); diff --git a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts index 3029927320e..9657b7571c5 100644 --- a/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts +++ b/libs/vault/src/cipher-form/components/new-item-nudge/new-item-nudge.component.ts @@ -9,7 +9,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherType } from "@bitwarden/sdk-internal"; import { SpotlightComponent } from "../../../components/spotlight/spotlight.component"; -import { VaultNudgesService, VaultNudgeType } from "../../../services/vault-nudges.service"; +import { NudgesService, NudgeType } from "../../../services/nudges.service"; @Component({ selector: "vault-new-item-nudge", @@ -23,12 +23,12 @@ export class NewItemNudgeComponent implements OnInit { showNewItemSpotlight: boolean = false; nudgeTitle: string = ""; nudgeBody: string = ""; - dismissalNudgeType: VaultNudgeType | null = null; + dismissalNudgeType: NudgeType | null = null; constructor( private i18nService: I18nService, private accountService: AccountService, - private vaultNudgesService: VaultNudgesService, + private nudgesService: NudgesService, ) {} async ngOnInit() { @@ -39,25 +39,25 @@ export class NewItemNudgeComponent implements OnInit { const nudgeBodyOne = this.i18nService.t("newLoginNudgeBodyOne"); const nudgeBodyBold = this.i18nService.t("newLoginNudgeBodyBold"); const nudgeBodyTwo = this.i18nService.t("newLoginNudgeBodyTwo"); - this.dismissalNudgeType = VaultNudgeType.newLoginItemStatus; + this.dismissalNudgeType = NudgeType.NewLoginItemStatus; this.nudgeTitle = this.i18nService.t("newLoginNudgeTitle"); this.nudgeBody = `${nudgeBodyOne} ${nudgeBodyBold} ${nudgeBodyTwo}`; break; } case CipherType.Card: - this.dismissalNudgeType = VaultNudgeType.newCardItemStatus; + this.dismissalNudgeType = NudgeType.NewCardItemStatus; this.nudgeTitle = this.i18nService.t("newCardNudgeTitle"); this.nudgeBody = this.i18nService.t("newCardNudgeBody"); break; case CipherType.Identity: - this.dismissalNudgeType = VaultNudgeType.newIdentityItemStatus; + this.dismissalNudgeType = NudgeType.NewIdentityItemStatus; this.nudgeTitle = this.i18nService.t("newIdentityNudgeTitle"); this.nudgeBody = this.i18nService.t("newIdentityNudgeBody"); break; case CipherType.SecureNote: - this.dismissalNudgeType = VaultNudgeType.newNoteItemStatus; + this.dismissalNudgeType = NudgeType.NewNoteItemStatus; this.nudgeTitle = this.i18nService.t("newNoteNudgeTitle"); this.nudgeBody = this.i18nService.t("newNoteNudgeBody"); break; @@ -66,7 +66,7 @@ export class NewItemNudgeComponent implements OnInit { const sshPartOne = this.i18nService.t("newSshNudgeBodyOne"); const sshPartTwo = this.i18nService.t("newSshNudgeBodyTwo"); - this.dismissalNudgeType = VaultNudgeType.newSshItemStatus; + this.dismissalNudgeType = NudgeType.NewSshItemStatus; this.nudgeTitle = this.i18nService.t("newSshNudgeTitle"); this.nudgeBody = `${sshPartOne}
    ${sshPartTwo}`; break; @@ -75,23 +75,19 @@ export class NewItemNudgeComponent implements OnInit { throw new Error("Unsupported cipher type"); } this.showNewItemSpotlight = await this.checkHasSpotlightDismissed( - this.dismissalNudgeType as VaultNudgeType, + this.dismissalNudgeType as NudgeType, this.activeUserId, ); } async dismissNewItemSpotlight() { if (this.dismissalNudgeType && this.activeUserId) { - await this.vaultNudgesService.dismissNudge( - this.dismissalNudgeType, - this.activeUserId as UserId, - ); + await this.nudgesService.dismissNudge(this.dismissalNudgeType, this.activeUserId as UserId); this.showNewItemSpotlight = false; } } - async checkHasSpotlightDismissed(nudgeType: VaultNudgeType, userId: UserId): Promise { - return !(await firstValueFrom(this.vaultNudgesService.showNudge$(nudgeType, userId))) - .hasSpotlightDismissed; + async checkHasSpotlightDismissed(nudgeType: NudgeType, userId: UserId): Promise { + return await firstValueFrom(this.nudgesService.showNudgeSpotlight$(nudgeType, userId)); } } diff --git a/libs/vault/src/index.ts b/libs/vault/src/index.ts index bdbd5aa36c8..b344a30836a 100644 --- a/libs/vault/src/index.ts +++ b/libs/vault/src/index.ts @@ -21,7 +21,7 @@ export * from "./components/add-edit-folder-dialog/add-edit-folder-dialog.compon export * from "./components/carousel"; export * as VaultIcons from "./icons"; -export * from "./services/vault-nudges.service"; +export * from "./services/nudges.service"; export * from "./services/custom-nudges-services"; export { DefaultSshImportPromptService } from "./services/default-ssh-import-prompt.service"; diff --git a/libs/vault/src/services/custom-nudges-services/autofill-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/autofill-nudge.service.ts index b5595e590c9..0a04fb2be47 100644 --- a/libs/vault/src/services/custom-nudges-services/autofill-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/autofill-nudge.service.ts @@ -7,7 +7,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { UserId } from "@bitwarden/common/types/guid"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; @@ -21,7 +21,7 @@ export class AutofillNudgeService extends DefaultSingleNudgeService { vaultProfileService = inject(VaultProfileService); logService = inject(LogService); - nudgeStatus$(_: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(_: NudgeType, userId: UserId): Observable { const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe( catchError(() => { this.logService.error("Error getting profile creation date"); @@ -32,7 +32,7 @@ export class AutofillNudgeService extends DefaultSingleNudgeService { return combineLatest([ profileDate$, - this.getNudgeStatus$(VaultNudgeType.AutofillNudge, userId), + this.getNudgeStatus$(NudgeType.AutofillNudge, userId), of(Date.now() - THIRTY_DAYS_MS), ]).pipe( map(([profileCreationDate, status, profileCutoff]) => { diff --git a/libs/vault/src/services/custom-nudges-services/download-bitwarden-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/download-bitwarden-nudge.service.ts index ff5533edc89..706b23437a1 100644 --- a/libs/vault/src/services/custom-nudges-services/download-bitwarden-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/download-bitwarden-nudge.service.ts @@ -7,7 +7,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { UserId } from "@bitwarden/common/types/guid"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; @@ -16,7 +16,7 @@ export class DownloadBitwardenNudgeService extends DefaultSingleNudgeService { private vaultProfileService = inject(VaultProfileService); private logService = inject(LogService); - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe( catchError(() => { this.logService.error("Failed to load profile date:"); diff --git a/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts index 3d1617bd8c1..a0e25aa841c 100644 --- a/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/empty-vault-nudge.service.ts @@ -7,7 +7,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; /** * Custom Nudge Service Checking Nudge Status For Empty Vault @@ -20,7 +20,7 @@ export class EmptyVaultNudgeService extends DefaultSingleNudgeService { organizationService = inject(OrganizationService); collectionService = inject(CollectionService); - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { return combineLatest([ this.getNudgeStatus$(nudgeType, userId), this.cipherService.cipherViews$(userId), diff --git a/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts index bdcf7da59f2..61fb08ae8c1 100644 --- a/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/has-items-nudge.service.ts @@ -8,7 +8,7 @@ import { UserId } from "@bitwarden/common/types/guid"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; @@ -23,7 +23,7 @@ export class HasItemsNudgeService extends DefaultSingleNudgeService { vaultProfileService = inject(VaultProfileService); logService = inject(LogService); - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { const profileDate$ = from(this.vaultProfileService.getProfileCreationDate(userId)).pipe( catchError(() => { this.logService.error("Error getting profile creation date"); @@ -51,7 +51,7 @@ export class HasItemsNudgeService extends DefaultSingleNudgeService { }; // permanently dismiss both the Empty Vault Nudge and Has Items Vault Nudge if the profile is older than 30 days await this.setNudgeStatus(nudgeType, dismissedStatus, userId); - await this.setNudgeStatus(VaultNudgeType.EmptyVaultNudge, dismissedStatus, userId); + await this.setNudgeStatus(NudgeType.EmptyVaultNudge, dismissedStatus, userId); return dismissedStatus; } else if (nudgeStatus.hasSpotlightDismissed) { return nudgeStatus; diff --git a/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts b/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts index 93ef5d81dc4..2202e88111e 100644 --- a/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts +++ b/libs/vault/src/services/custom-nudges-services/new-item-nudge.service.ts @@ -6,7 +6,7 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { CipherType } from "@bitwarden/common/vault/enums"; import { DefaultSingleNudgeService } from "../default-single-nudge.service"; -import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; +import { NudgeStatus, NudgeType } from "../nudges.service"; /** * Custom Nudge Service Checking Nudge Status For Vault New Item Types @@ -17,7 +17,7 @@ import { NudgeStatus, VaultNudgeType } from "../vault-nudges.service"; export class NewItemNudgeService extends DefaultSingleNudgeService { cipherService = inject(CipherService); - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { return combineLatest([ this.getNudgeStatus$(nudgeType, userId), this.cipherService.cipherViews$(userId), @@ -30,19 +30,19 @@ export class NewItemNudgeService extends DefaultSingleNudgeService { let currentType: CipherType; switch (nudgeType) { - case VaultNudgeType.newLoginItemStatus: + case NudgeType.NewLoginItemStatus: currentType = CipherType.Login; break; - case VaultNudgeType.newCardItemStatus: + case NudgeType.NewCardItemStatus: currentType = CipherType.Card; break; - case VaultNudgeType.newIdentityItemStatus: + case NudgeType.NewIdentityItemStatus: currentType = CipherType.Identity; break; - case VaultNudgeType.newNoteItemStatus: + case NudgeType.NewNoteItemStatus: currentType = CipherType.SecureNote; break; - case VaultNudgeType.newSshItemStatus: + case NudgeType.NewSshItemStatus: currentType = CipherType.SshKey; break; } diff --git a/libs/vault/src/services/default-single-nudge.service.ts b/libs/vault/src/services/default-single-nudge.service.ts index 9a1759cab38..8abc344c4a0 100644 --- a/libs/vault/src/services/default-single-nudge.service.ts +++ b/libs/vault/src/services/default-single-nudge.service.ts @@ -4,19 +4,15 @@ import { map, Observable } from "rxjs"; import { StateProvider } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; -import { - NudgeStatus, - VAULT_NUDGE_DISMISSED_DISK_KEY, - VaultNudgeType, -} from "./vault-nudges.service"; +import { NudgeStatus, NUDGE_DISMISSED_DISK_KEY, NudgeType } from "./nudges.service"; /** * Base interface for handling a nudge's status */ export interface SingleNudgeService { - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable; + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable; - setNudgeStatus(nudgeType: VaultNudgeType, newStatus: NudgeStatus, userId: UserId): Promise; + setNudgeStatus(nudgeType: NudgeType, newStatus: NudgeStatus, userId: UserId): Promise; } /** @@ -28,9 +24,9 @@ export interface SingleNudgeService { export class DefaultSingleNudgeService implements SingleNudgeService { stateProvider = inject(StateProvider); - protected getNudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + protected getNudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { return this.stateProvider - .getUser(userId, VAULT_NUDGE_DISMISSED_DISK_KEY) + .getUser(userId, NUDGE_DISMISSED_DISK_KEY) .state$.pipe( map( (nudges) => @@ -39,16 +35,12 @@ export class DefaultSingleNudgeService implements SingleNudgeService { ); } - nudgeStatus$(nudgeType: VaultNudgeType, userId: UserId): Observable { + nudgeStatus$(nudgeType: NudgeType, userId: UserId): Observable { return this.getNudgeStatus$(nudgeType, userId); } - async setNudgeStatus( - nudgeType: VaultNudgeType, - status: NudgeStatus, - userId: UserId, - ): Promise { - await this.stateProvider.getUser(userId, VAULT_NUDGE_DISMISSED_DISK_KEY).update((nudges) => { + async setNudgeStatus(nudgeType: NudgeType, status: NudgeStatus, userId: UserId): Promise { + await this.stateProvider.getUser(userId, NUDGE_DISMISSED_DISK_KEY).update((nudges) => { nudges ??= {}; nudges[nudgeType] = status; return nudges; diff --git a/libs/vault/src/services/vault-nudges.service.spec.ts b/libs/vault/src/services/nudges.service.spec.ts similarity index 72% rename from libs/vault/src/services/vault-nudges.service.spec.ts rename to libs/vault/src/services/nudges.service.spec.ts index 40c58644309..897a5befa2a 100644 --- a/libs/vault/src/services/vault-nudges.service.spec.ts +++ b/libs/vault/src/services/nudges.service.spec.ts @@ -18,7 +18,7 @@ import { DownloadBitwardenNudgeService, } from "./custom-nudges-services"; import { DefaultSingleNudgeService } from "./default-single-nudge.service"; -import { VaultNudgesService, VaultNudgeType } from "./vault-nudges.service"; +import { NudgesService, NudgeType } from "./nudges.service"; describe("Vault Nudges Service", () => { let fakeStateProvider: FakeStateProvider; @@ -29,7 +29,7 @@ describe("Vault Nudges Service", () => { getFeatureFlag: jest.fn().mockReturnValue(true), }; - const vaultNudgeServices = [EmptyVaultNudgeService, DownloadBitwardenNudgeService]; + const nudgeServices = [EmptyVaultNudgeService, DownloadBitwardenNudgeService]; beforeEach(async () => { fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); @@ -38,7 +38,7 @@ describe("Vault Nudges Service", () => { imports: [], providers: [ { - provide: VaultNudgesService, + provide: NudgesService, }, { provide: DefaultSingleNudgeService, @@ -83,13 +83,13 @@ describe("Vault Nudges Service", () => { const service = testBed.inject(DefaultSingleNudgeService); await service.setNudgeStatus( - VaultNudgeType.EmptyVaultNudge, + NudgeType.EmptyVaultNudge, { hasBadgeDismissed: true, hasSpotlightDismissed: true }, "user-id" as UserId, ); const result = await firstValueFrom( - service.nudgeStatus$(VaultNudgeType.EmptyVaultNudge, "user-id" as UserId), + service.nudgeStatus$(NudgeType.EmptyVaultNudge, "user-id" as UserId), ); expect(result).toEqual({ hasBadgeDismissed: true, hasSpotlightDismissed: true }); }); @@ -98,27 +98,27 @@ describe("Vault Nudges Service", () => { const service = testBed.inject(DefaultSingleNudgeService); await service.setNudgeStatus( - VaultNudgeType.EmptyVaultNudge, + NudgeType.EmptyVaultNudge, { hasBadgeDismissed: false, hasSpotlightDismissed: false }, "user-id" as UserId, ); const result = await firstValueFrom( - service.nudgeStatus$(VaultNudgeType.EmptyVaultNudge, "user-id" as UserId), + service.nudgeStatus$(NudgeType.EmptyVaultNudge, "user-id" as UserId), ); expect(result).toEqual({ hasBadgeDismissed: false, hasSpotlightDismissed: false }); }); }); - describe("VaultNudgesService", () => { + describe("NudgesService", () => { it("should return true, the proper value from the custom nudge service nudgeStatus$", async () => { TestBed.overrideProvider(HasItemsNudgeService, { useValue: { nudgeStatus$: () => of(true) }, }); - const service = testBed.inject(VaultNudgesService); + const service = testBed.inject(NudgesService); const result = await firstValueFrom( - service.showNudge$(VaultNudgeType.HasVaultItems, "user-id" as UserId), + service.showNudgeStatus$(NudgeType.HasVaultItems, "user-id" as UserId), ); expect(result).toBe(true); @@ -128,10 +128,40 @@ describe("Vault Nudges Service", () => { TestBed.overrideProvider(HasItemsNudgeService, { useValue: { nudgeStatus$: () => of(false) }, }); - const service = testBed.inject(VaultNudgesService); + const service = testBed.inject(NudgesService); const result = await firstValueFrom( - service.showNudge$(VaultNudgeType.HasVaultItems, "user-id" as UserId), + service.showNudgeStatus$(NudgeType.HasVaultItems, "user-id" as UserId), + ); + + expect(result).toBe(false); + }); + + it("should return showNudgeSpotlight$ false if hasSpotLightDismissed is true", async () => { + TestBed.overrideProvider(HasItemsNudgeService, { + useValue: { + nudgeStatus$: () => of({ hasSpotlightDismissed: true, hasBadgeDismissed: true }), + }, + }); + const service = testBed.inject(NudgesService); + + const result = await firstValueFrom( + service.showNudgeSpotlight$(NudgeType.HasVaultItems, "user-id" as UserId), + ); + + expect(result).toBe(false); + }); + + it("should return showNudgeBadge$ false when hasBadgeDismissed is true", async () => { + TestBed.overrideProvider(HasItemsNudgeService, { + useValue: { + nudgeStatus$: () => of({ hasSpotlightDismissed: true, hasBadgeDismissed: true }), + }, + }); + const service = testBed.inject(NudgesService); + + const result = await firstValueFrom( + service.showNudgeBadge$(NudgeType.HasVaultItems, "user-id" as UserId), ); expect(result).toBe(false); @@ -140,7 +170,7 @@ describe("Vault Nudges Service", () => { describe("HasActiveBadges", () => { it("should return true if a nudgeType with hasBadgeDismissed === false", async () => { - vaultNudgeServices.forEach((service) => { + nudgeServices.forEach((service) => { TestBed.overrideProvider(service, { useValue: { nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), @@ -148,21 +178,21 @@ describe("Vault Nudges Service", () => { }); }); - const service = testBed.inject(VaultNudgesService); + const service = testBed.inject(NudgesService); const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); expect(result).toBe(true); }); it("should return false if all nudgeTypes have hasBadgeDismissed === true", async () => { - vaultNudgeServices.forEach((service) => { + nudgeServices.forEach((service) => { TestBed.overrideProvider(service, { useValue: { nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: false }), }, }); }); - const service = testBed.inject(VaultNudgesService); + const service = testBed.inject(NudgesService); const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/nudges.service.ts similarity index 56% rename from libs/vault/src/services/vault-nudges.service.ts rename to libs/vault/src/services/nudges.service.ts index d27cd09e954..6784e9c7b4e 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/nudges.service.ts @@ -3,7 +3,7 @@ import { combineLatest, map, Observable, of, shareReplay, switchMap } from "rxjs import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { UserKeyDefinition, VAULT_NUDGES_DISK } from "@bitwarden/common/platform/state"; +import { UserKeyDefinition, NUDGES_DISK } from "@bitwarden/common/platform/state"; import { UserId } from "@bitwarden/common/types/guid"; import { @@ -25,7 +25,7 @@ export type NudgeStatus = { */ // FIXME: update to use a const object instead of a typescript enum // eslint-disable-next-line @bitwarden/platform/no-enums -export enum VaultNudgeType { +export enum NudgeType { /** Nudge to show when user has no items in their vault * Add future nudges here */ @@ -33,16 +33,16 @@ export enum VaultNudgeType { HasVaultItems = "has-vault-items", AutofillNudge = "autofill-nudge", DownloadBitwarden = "download-bitwarden", - newLoginItemStatus = "new-login-item-status", - newCardItemStatus = "new-card-item-status", - newIdentityItemStatus = "new-identity-item-status", - newNoteItemStatus = "new-note-item-status", - newSshItemStatus = "new-ssh-item-status", + NewLoginItemStatus = "new-login-item-status", + NewCardItemStatus = "new-card-item-status", + NewIdentityItemStatus = "new-identity-item-status", + NewNoteItemStatus = "new-note-item-status", + NewSshItemStatus = "new-ssh-item-status", } -export const VAULT_NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< - Partial> ->(VAULT_NUDGES_DISK, "vaultNudgeDismissed", { +export const NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< + Partial> +>(NUDGES_DISK, "vaultNudgeDismissed", { deserializer: (nudge) => nudge, clearOn: [], // Do not clear dismissals }); @@ -50,7 +50,7 @@ export const VAULT_NUDGE_DISMISSED_DISK_KEY = new UserKeyDefinition< @Injectable({ providedIn: "root", }) -export class VaultNudgesService { +export class NudgesService { private newItemNudgeService = inject(NewItemNudgeService); /** @@ -58,16 +58,16 @@ export class VaultNudgesService { * Each nudge type can have its own service to determine when to show the nudge * @private */ - private customNudgeServices: Partial> = { - [VaultNudgeType.HasVaultItems]: inject(HasItemsNudgeService), - [VaultNudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), - [VaultNudgeType.AutofillNudge]: inject(AutofillNudgeService), - [VaultNudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService), - [VaultNudgeType.newLoginItemStatus]: this.newItemNudgeService, - [VaultNudgeType.newCardItemStatus]: this.newItemNudgeService, - [VaultNudgeType.newIdentityItemStatus]: this.newItemNudgeService, - [VaultNudgeType.newNoteItemStatus]: this.newItemNudgeService, - [VaultNudgeType.newSshItemStatus]: this.newItemNudgeService, + private customNudgeServices: Partial> = { + [NudgeType.HasVaultItems]: inject(HasItemsNudgeService), + [NudgeType.EmptyVaultNudge]: inject(EmptyVaultNudgeService), + [NudgeType.AutofillNudge]: inject(AutofillNudgeService), + [NudgeType.DownloadBitwarden]: inject(DownloadBitwardenNudgeService), + [NudgeType.NewLoginItemStatus]: this.newItemNudgeService, + [NudgeType.NewCardItemStatus]: this.newItemNudgeService, + [NudgeType.NewIdentityItemStatus]: this.newItemNudgeService, + [NudgeType.NewNoteItemStatus]: this.newItemNudgeService, + [NudgeType.NewSshItemStatus]: this.newItemNudgeService, }; /** @@ -78,16 +78,52 @@ export class VaultNudgesService { private defaultNudgeService = inject(DefaultSingleNudgeService); private configService = inject(ConfigService); - private getNudgeService(nudge: VaultNudgeType): SingleNudgeService { + private getNudgeService(nudge: NudgeType): SingleNudgeService { return this.customNudgeServices[nudge] ?? this.defaultNudgeService; } + /** + * Check if a nudge Spotlight should be shown to the user + * @param nudge + * @param userId + */ + showNudgeSpotlight$(nudge: NudgeType, userId: UserId): Observable { + return this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge).pipe( + switchMap((hasVaultNudgeFlag) => { + if (!hasVaultNudgeFlag) { + return of(false); + } + return this.getNudgeService(nudge) + .nudgeStatus$(nudge, userId) + .pipe(map((nudgeStatus) => !nudgeStatus.hasSpotlightDismissed)); + }), + ); + } + + /** + * Check if a nudge Badge should be shown to the user + * @param nudge + * @param userId + */ + showNudgeBadge$(nudge: NudgeType, userId: UserId): Observable { + return this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge).pipe( + switchMap((hasVaultNudgeFlag) => { + if (!hasVaultNudgeFlag) { + return of(false); + } + return this.getNudgeService(nudge) + .nudgeStatus$(nudge, userId) + .pipe(map((nudgeStatus) => !nudgeStatus.hasBadgeDismissed)); + }), + ); + } + /** * Check if a nudge should be shown to the user * @param nudge * @param userId */ - showNudge$(nudge: VaultNudgeType, userId: UserId) { + showNudgeStatus$(nudge: NudgeType, userId: UserId) { return this.configService.getFeatureFlag$(FeatureFlag.PM8851_BrowserOnboardingNudge).pipe( switchMap((hasVaultNudgeFlag) => { if (!hasVaultNudgeFlag) { @@ -103,7 +139,7 @@ export class VaultNudgesService { * @param nudge * @param userId */ - async dismissNudge(nudge: VaultNudgeType, userId: UserId, onlyBadge: boolean = false) { + async dismissNudge(nudge: NudgeType, userId: UserId, onlyBadge: boolean = false) { const dismissedStatus = onlyBadge ? { hasBadgeDismissed: true, hasSpotlightDismissed: false } : { hasBadgeDismissed: true, hasSpotlightDismissed: true }; @@ -116,7 +152,7 @@ export class VaultNudgesService { */ hasActiveBadges$(userId: UserId): Observable { // Add more nudge types here if they have the settings badge feature - const nudgeTypes = [VaultNudgeType.EmptyVaultNudge, VaultNudgeType.DownloadBitwarden]; + const nudgeTypes = [NudgeType.EmptyVaultNudge, NudgeType.DownloadBitwarden]; const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => { return this.getNudgeService(nudge) From 0d19432f1482f9fc4f99343a06aa55270ea6271c Mon Sep 17 00:00:00 2001 From: sportshead <32637656+sportshead@users.noreply.github.com> Date: Thu, 15 May 2025 20:54:41 +0100 Subject: [PATCH 33/54] fix(browser): use appCopyField instead of appCopyClick for singleCopiableLogin (#14692) Fixes #14167 Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .../item-copy-actions.component.html | 9 +++---- .../item-copy-actions.component.ts | 27 ++++++++++++------- 2 files changed, 20 insertions(+), 16 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 bb3a7b12096..576f6b7def6 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 @@ -40,12 +40,9 @@ type="button" bitIconButton="bwi-clone" size="small" - [appA11yTitle]=" - 'copyFieldValue' | i18n: singleCopiableLogin.key : singleCopiableLogin.value - " - [appCopyClick]="singleCopiableLogin.value" - [valueLabel]="singleCopiableLogin.key" - showToast + [appA11yTitle]="singleCopiableLogin.key" + [appCopyField]="$any(singleCopiableLogin.field)" + [cipher]="cipher" > `; diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts index 94bfa2c73c1..34ad5e1c9a9 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -8,8 +8,10 @@ import { I18n } from "../common-types"; export type CipherActionProps = { handleAction?: (e: Event) => void; i18n: I18n; + itemName: string; notificationType: typeof NotificationTypes.Change | typeof NotificationTypes.Add; theme: Theme; + username?: string; }; export function CipherAction({ @@ -17,14 +19,18 @@ export function CipherAction({ /* no-op */ }, i18n, + itemName, notificationType, theme, + username, }: CipherActionProps) { return notificationType === NotificationTypes.Change ? BadgeButton({ buttonAction: handleAction, buttonText: i18n.notificationUpdate, + itemName, theme, + username, }) : EditButton({ buttonAction: handleAction, diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts index c290b68a291..ab3b57f535c 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -32,14 +32,21 @@ export function CipherItem({ notificationType, theme = ThemeTypes.Light, }: CipherItemProps) { - const { icon } = cipher; + const { icon, name, login } = cipher; const uri = (icon.imageEnabled && icon.image) || undefined; let cipherActionButton = null; if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { cipherActionButton = html`
    - ${CipherAction({ handleAction, i18n, notificationType, theme })} + ${CipherAction({ + handleAction, + i18n, + itemName: name, + notificationType, + theme, + username: login?.username, + })}
    `; } diff --git a/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts b/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts index 56e66654cb8..886c16c7515 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/buttons/close-button.lit-stories.ts @@ -3,6 +3,7 @@ import { Meta, StoryObj } from "@storybook/web-components"; import { ThemeTypes } from "@bitwarden/common/platform/enums/theme-type.enum"; import { CloseButton, CloseButtonProps } from "../../buttons/close-button"; +import { mockI18n } from "../mock-data"; export default { title: "Components/Buttons/Close Button", @@ -15,6 +16,7 @@ export default { handleCloseNotification: () => { alert("Close button clicked!"); }, + i18n: mockI18n, }, parameters: { design: { diff --git a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts index 7ca347ecb14..81cdf5a50f3 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts @@ -127,6 +127,7 @@ export const mockI18n = { notificationUnlock: "Unlock", notificationUnlockDesc: "Unlock your Bitwarden vault to complete the autofill request.", notificationViewAria: `View $ITEMNAME$, opens in new window`, + notificationNewItemAria: "New Item, opens in new window", saveAction: "Save", saveAsNewLoginAction: "Save as new login", saveFailure: "Error saving", diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts index d4d66c7a7be..fc6ae4a353b 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -45,7 +45,9 @@ export function NotificationConfirmationContainer({ const headerMessage = getHeaderMessage(i18n, type, error); const confirmationMessage = getConfirmationMessage(i18n, type, error); const buttonText = error ? i18n.newItem : i18n.view; - const buttonAria = chrome.i18n.getMessage("notificationViewAria", [itemName]); + const buttonAria = error + ? i18n.notificationNewItemAria + : chrome.i18n.getMessage("notificationViewAria", [itemName]); let messageDetails: string | undefined; let remainingTasksCount: number | undefined; @@ -68,6 +70,7 @@ export function NotificationConfirmationContainer({
    ${NotificationHeader({ handleCloseNotification, + i18n, message: headerMessage, theme, })} diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index 94adb1e2549..313e3eecf01 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -53,6 +53,7 @@ export function NotificationContainer({
    ${NotificationHeader({ handleCloseNotification, + i18n, message: headerMessage, theme, })} diff --git a/apps/browser/src/autofill/content/components/notification/header.ts b/apps/browser/src/autofill/content/components/notification/header.ts index b2f8962917b..3d657b77ecd 100644 --- a/apps/browser/src/autofill/content/components/notification/header.ts +++ b/apps/browser/src/autofill/content/components/notification/header.ts @@ -4,6 +4,7 @@ import { html } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { CloseButton } from "../buttons/close-button"; +import { I18n } from "../common-types"; import { spacing, themes } from "../constants/styles"; import { BrandIconContainer } from "../icons/brand-icon-container"; @@ -16,6 +17,7 @@ const { css } = createEmotion({ }); export type NotificationHeaderProps = { + i18n: I18n; message?: string; standalone?: boolean; theme: Theme; @@ -23,6 +25,7 @@ export type NotificationHeaderProps = { }; export function NotificationHeader({ + i18n, message, standalone = false, theme = ThemeTypes.Light, @@ -35,7 +38,7 @@ export function NotificationHeader({
    ${showIcon ? BrandIconContainer({ theme }) : null} ${message ? NotificationHeaderMessage({ message, theme }) : null} - ${isDismissable ? CloseButton({ handleCloseNotification, theme }) : null} + ${isDismissable ? CloseButton({ handleCloseNotification, i18n, theme }) : null}
    `; } From 27042e8023e4bfabdaea6583417d06fa65518819 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Fri, 16 May 2025 12:52:30 -0400 Subject: [PATCH 52/54] add useOrganizationDomains (#14023) --- .../src/admin-console/models/data/organization.data.spec.ts | 1 + .../common/src/admin-console/models/data/organization.data.ts | 2 ++ libs/common/src/admin-console/models/domain/organization.ts | 4 +++- .../models/response/profile-organization.response.ts | 2 ++ .../key-connector/services/key-connector.service.spec.ts | 1 + 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index 7d66e7bc0d5..a6a2caa49c7 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -58,6 +58,7 @@ describe("ORGANIZATIONS state", () => { familySponsorshipLastSyncDate: new Date(), userIsManagedByOrganization: false, useRiskInsights: false, + useOrganizationDomains: false, useAdminSponsoredFamilies: false, isAdminInitiated: false, }, diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index e0783957117..16f6f90f347 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -21,6 +21,7 @@ export class OrganizationData { use2fa: boolean; useApi: boolean; useSso: boolean; + useOrganizationDomains: boolean; useKeyConnector: boolean; useScim: boolean; useCustomPermissions: boolean; @@ -87,6 +88,7 @@ export class OrganizationData { this.use2fa = response.use2fa; this.useApi = response.useApi; this.useSso = response.useSso; + this.useOrganizationDomains = response.useOrganizationDomains; this.useKeyConnector = response.useKeyConnector; this.useScim = response.useScim; this.useCustomPermissions = response.useCustomPermissions; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 1864d56649b..f8de5293913 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -28,6 +28,7 @@ export class Organization { use2fa: boolean; useApi: boolean; useSso: boolean; + useOrganizationDomains: boolean; useKeyConnector: boolean; useScim: boolean; useCustomPermissions: boolean; @@ -111,6 +112,7 @@ export class Organization { this.use2fa = obj.use2fa; this.useApi = obj.useApi; this.useSso = obj.useSso; + this.useOrganizationDomains = obj.useOrganizationDomains; this.useKeyConnector = obj.useKeyConnector; this.useScim = obj.useScim; this.useCustomPermissions = obj.useCustomPermissions; @@ -281,7 +283,7 @@ export class Organization { } get canManageDomainVerification() { - return (this.isAdmin || this.permissions.manageSso) && this.useSso; + return (this.isAdmin || this.permissions.manageSso) && this.useOrganizationDomains; } get canManageScim() { diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index 3a86c764eb8..6e451ce9808 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -14,6 +14,7 @@ export class ProfileOrganizationResponse extends BaseResponse { use2fa: boolean; useApi: boolean; useSso: boolean; + useOrganizationDomains: boolean; useKeyConnector: boolean; useScim: boolean; useCustomPermissions: boolean; @@ -70,6 +71,7 @@ export class ProfileOrganizationResponse extends BaseResponse { this.use2fa = this.getResponseProperty("Use2fa"); this.useApi = this.getResponseProperty("UseApi"); this.useSso = this.getResponseProperty("UseSso"); + this.useOrganizationDomains = this.getResponseProperty("UseOrganizationDomains"); this.useKeyConnector = this.getResponseProperty("UseKeyConnector") ?? false; this.useScim = this.getResponseProperty("UseScim") ?? false; this.useCustomPermissions = this.getResponseProperty("UseCustomPermissions") ?? false; diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index b88ada56129..eff3fc7f47c 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -315,6 +315,7 @@ describe("KeyConnectorService", () => { name: "TEST_KEY_CONNECTOR_ORG", usePolicies: true, useSso: true, + useOrganizationDomains: true, useKeyConnector: usesKeyConnector, useScim: true, useGroups: true, From d16a5cb73e54724171202d90d1f6ff1f192befd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Fri, 16 May 2025 19:30:47 +0200 Subject: [PATCH 53/54] Revert "PM-21553: Added support for credential.toJSON() (#14734)" (#14819) This reverts commit df8d184889ef9accf5d07c3d740eb976d30dffea. --- .../autofill/fido2/utils/webauthn-utils.ts | 2 - .../platform/services/fido2/fido2-utils.ts | 39 ------------------- 2 files changed, 41 deletions(-) diff --git a/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts index 0cccd91876d..c8bcf5faa4b 100644 --- a/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts +++ b/apps/browser/src/autofill/fido2/utils/webauthn-utils.ts @@ -88,7 +88,6 @@ export class WebauthnUtils { getClientExtensionResults: () => ({ credProps: result.extensions.credProps, }), - toJSON: () => Fido2Utils.createResultToJson(result), } as PublicKeyCredential; // Modify prototype chains to fix `instanceof` calls. @@ -135,7 +134,6 @@ export class WebauthnUtils { } as AuthenticatorAssertionResponse, getClientExtensionResults: () => ({}), authenticatorAttachment: "platform", - toJSON: () => Fido2Utils.getResultToJson(result), } as PublicKeyCredential; // Modify prototype chains to fix `instanceof` calls. diff --git a/libs/common/src/platform/services/fido2/fido2-utils.ts b/libs/common/src/platform/services/fido2/fido2-utils.ts index 6413eeade04..b9f3c8f8c48 100644 --- a/libs/common/src/platform/services/fido2/fido2-utils.ts +++ b/libs/common/src/platform/services/fido2/fido2-utils.ts @@ -1,45 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line -import type { - AssertCredentialResult, - CreateCredentialResult, -} from "../../abstractions/fido2/fido2-client.service.abstraction"; - // @ts-strict-ignore export class Fido2Utils { - static createResultToJson(result: CreateCredentialResult): any { - return { - id: result.credentialId, - rawId: result.credentialId, - response: { - clientDataJSON: result.clientDataJSON, - authenticatorData: result.authData, - transports: result.transports, - publicKey: result.publicKey, - publicKeyAlgorithm: result.publicKeyAlgorithm, - attestationObject: result.attestationObject, - }, - authenticatorAttachment: "platform", - clientExtensionResults: result.extensions, - type: "public-key", - }; - } - - static getResultToJson(result: AssertCredentialResult): any { - return { - id: result.credentialId, - rawId: result.credentialId, - response: { - clientDataJSON: result.clientDataJSON, - authenticatorData: result.authenticatorData, - signature: result.signature, - userHandle: result.userHandle, - }, - authenticatorAttachment: "platform", - clientExtensionResults: {}, - type: "public-key", - }; - } - static bufferToString(bufferSource: BufferSource): string { return Fido2Utils.fromBufferToB64(Fido2Utils.bufferSourceToUint8Array(bufferSource)) .replace(/\+/g, "-") From afbddeaf86d9e4092d262a0ac275cbf21803d00b Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Fri, 16 May 2025 10:41:46 -0700 Subject: [PATCH 54/54] refactor(set-change-password): [Auth/PM-18458] Create new ChangePasswordComponent (#14226) This PR creates a new ChangePasswordComponent. The first use-case of the ChangePasswordComponent is to place it inside a new PasswordSettingsComponent, which is accessed by going to Account Settings > Security. The ChangePasswordComponent will be updated in future PRs to handle more change password scenarios. Feature Flags: PM16117_ChangeExistingPasswordRefactor --- .../core/services/change-password/index.ts | 1 + .../web-change-password.service.spec.ts | 63 +++ .../web-change-password.service.ts | 34 ++ apps/web/src/app/auth/core/services/index.ts | 1 + .../web-registration-finish.service.spec.ts | 28 +- .../settings/change-password.component.ts | 5 +- .../password-settings.component.html | 10 + .../password-settings.component.ts | 35 ++ .../security/security-routing.module.ts | 27 +- .../settings/security/security.component.html | 2 +- .../settings/security/security.component.ts | 9 + .../complete-trial-initiation.component.html | 177 ++++---- .../complete-trial-initiation.component.ts | 5 +- apps/web/src/app/core/core.module.ts | 13 + apps/web/src/locales/en/messages.json | 4 +- .../src/services/jslib-services.module.ts | 11 + .../change-password.component.html | 20 + .../change-password.component.ts | 110 +++++ .../change-password.service.abstraction.ts | 36 ++ .../default-change-password.service.spec.ts | 177 ++++++++ .../default-change-password.service.ts | 59 +++ libs/auth/src/angular/index.ts | 5 + .../input-password.component.html | 36 +- .../input-password.component.ts | 411 ++++++++++++++---- .../angular/input-password/input-password.mdx | 169 +++++-- .../input-password/input-password.stories.ts | 94 +++- .../input-password/password-input-result.ts | 19 +- ...efault-registration-finish.service.spec.ts | 14 +- .../default-registration-finish.service.ts | 6 +- .../registration-finish.component.html | 2 +- .../registration-finish.component.ts | 3 +- .../default-set-password-jit.service.spec.ts | 14 +- .../default-set-password-jit.service.ts | 26 +- .../set-password-jit.component.html | 5 +- .../set-password-jit.component.ts | 14 +- .../set-password-jit.service.abstraction.ts | 12 +- libs/common/src/enums/feature-flag.enum.ts | 2 + 37 files changed, 1349 insertions(+), 310 deletions(-) create mode 100644 apps/web/src/app/auth/core/services/change-password/index.ts create mode 100644 apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts create mode 100644 apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts create mode 100644 apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html create mode 100644 apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts create mode 100644 libs/auth/src/angular/change-password/change-password.component.html create mode 100644 libs/auth/src/angular/change-password/change-password.component.ts create mode 100644 libs/auth/src/angular/change-password/change-password.service.abstraction.ts create mode 100644 libs/auth/src/angular/change-password/default-change-password.service.spec.ts create mode 100644 libs/auth/src/angular/change-password/default-change-password.service.ts diff --git a/apps/web/src/app/auth/core/services/change-password/index.ts b/apps/web/src/app/auth/core/services/change-password/index.ts new file mode 100644 index 00000000000..9b2aa1c0143 --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/index.ts @@ -0,0 +1 @@ +export * from "./web-change-password.service"; diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts new file mode 100644 index 00000000000..45abfe4720a --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.spec.ts @@ -0,0 +1,63 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +import { WebChangePasswordService } from "./web-change-password.service"; + +describe("WebChangePasswordService", () => { + let keyService: MockProxy; + let masterPasswordApiService: MockProxy; + let masterPasswordService: MockProxy; + let userKeyRotationService: MockProxy; + + let sut: ChangePasswordService; + + const userId = "userId" as UserId; + const user: Account = { + id: userId, + email: "email", + emailVerified: false, + name: "name", + }; + + const currentPassword = "currentPassword"; + const newPassword = "newPassword"; + const newPasswordHint = "newPasswordHint"; + + beforeEach(() => { + keyService = mock(); + masterPasswordApiService = mock(); + masterPasswordService = mock(); + userKeyRotationService = mock(); + + sut = new WebChangePasswordService( + keyService, + masterPasswordApiService, + masterPasswordService, + userKeyRotationService, + ); + }); + + describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { + it("should call the method with the same name on the UserKeyRotationService with the correct arguments", async () => { + // Arrange & Act + await sut.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + + // Assert + expect( + userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData, + ).toHaveBeenCalledWith(currentPassword, newPassword, user, newPasswordHint); + }); + }); +}); diff --git a/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts new file mode 100644 index 00000000000..b75aef0f1fc --- /dev/null +++ b/apps/web/src/app/auth/core/services/change-password/web-change-password.service.ts @@ -0,0 +1,34 @@ +import { ChangePasswordService, DefaultChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { KeyService } from "@bitwarden/key-management"; +import { UserKeyRotationService } from "@bitwarden/web-vault/app/key-management/key-rotation/user-key-rotation.service"; + +export class WebChangePasswordService + extends DefaultChangePasswordService + implements ChangePasswordService +{ + constructor( + protected keyService: KeyService, + protected masterPasswordApiService: MasterPasswordApiService, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, + private userKeyRotationService: UserKeyRotationService, + ) { + super(keyService, masterPasswordApiService, masterPasswordService); + } + + override async rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + newPasswordHint: string, + ): Promise { + await this.userKeyRotationService.rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword, + newPassword, + user, + newPasswordHint, + ); + } +} diff --git a/apps/web/src/app/auth/core/services/index.ts b/apps/web/src/app/auth/core/services/index.ts index 11c8dd98872..5539e3b76ea 100644 --- a/apps/web/src/app/auth/core/services/index.ts +++ b/apps/web/src/app/auth/core/services/index.ts @@ -1,3 +1,4 @@ +export * from "./change-password"; export * from "./login"; export * from "./login-decryption-options"; export * from "./webauthn-login"; diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts index edce551342e..fe3b0837aa8 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.spec.ts @@ -185,11 +185,11 @@ describe("WebRegistrationFinishService", () => { emailVerificationToken = "emailVerificationToken"; masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey; passwordInputResult = { - masterKey: masterKey, - serverMasterKeyHash: "serverMasterKeyHash", - localMasterKeyHash: "localMasterKeyHash", + newMasterKey: masterKey, + newServerMasterKeyHash: "newServerMasterKeyHash", + newLocalMasterKeyHash: "newLocalMasterKeyHash", kdfConfig: DEFAULT_KDF_CONFIG, - hint: "hint", + newPasswordHint: "newPasswordHint", newPassword: "newPassword", }; @@ -231,8 +231,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: emailVerificationToken, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -267,8 +267,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -308,8 +308,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -351,8 +351,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], @@ -396,8 +396,8 @@ describe("WebRegistrationFinishService", () => { expect.objectContaining({ email, emailVerificationToken: undefined, - masterPasswordHash: passwordInputResult.serverMasterKeyHash, - masterPasswordHint: passwordInputResult.hint, + masterPasswordHash: passwordInputResult.newServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, userSymmetricKey: userKeyEncString.encryptedString, userAsymmetricKeys: { publicKey: userKeyPair[0], diff --git a/apps/web/src/app/auth/settings/change-password.component.ts b/apps/web/src/app/auth/settings/change-password.component.ts index f1ba9281f69..1d95a498694 100644 --- a/apps/web/src/app/auth/settings/change-password.component.ts +++ b/apps/web/src/app/auth/settings/change-password.component.ts @@ -29,6 +29,9 @@ import { KdfConfigService, KeyService } from "@bitwarden/key-management"; import { UserKeyRotationService } from "../../key-management/key-rotation/user-key-rotation.service"; +/** + * @deprecated use the auth `PasswordSettingsComponent` instead + */ @Component({ selector: "app-change-password", templateUrl: "change-password.component.html", @@ -132,7 +135,7 @@ export class ChangePasswordComponent content: this.i18nService.t("updateEncryptionKeyWarning") + " " + - this.i18nService.t("updateEncryptionKeyExportWarning") + + this.i18nService.t("updateEncryptionKeyAccountExportWarning") + " " + this.i18nService.t("rotateEncKeyConfirmation"), type: "warning", diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html new file mode 100644 index 00000000000..94cf08b5871 --- /dev/null +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.html @@ -0,0 +1,10 @@ +
    +

    {{ "changeMasterPassword" | i18n }}

    +
    + +
    + {{ "loggedOutWarning" | i18n }} + +
    + + diff --git a/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts new file mode 100644 index 00000000000..ee30543fba2 --- /dev/null +++ b/apps/web/src/app/auth/settings/security/password-settings/password-settings.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from "@angular/core"; +import { Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { ChangePasswordComponent, InputPasswordFlow } from "@bitwarden/auth/angular"; +import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common"; +import { CalloutModule } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { WebauthnLoginSettingsModule } from "../../webauthn-login-settings"; + +@Component({ + standalone: true, + selector: "app-password-settings", + templateUrl: "password-settings.component.html", + imports: [CalloutModule, ChangePasswordComponent, I18nPipe, WebauthnLoginSettingsModule], +}) +export class PasswordSettingsComponent implements OnInit { + inputPasswordFlow = InputPasswordFlow.ChangePasswordWithOptionalUserKeyRotation; + + constructor( + private router: Router, + private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, + ) {} + + async ngOnInit() { + const userHasMasterPassword = await firstValueFrom( + this.userDecryptionOptionsService.hasMasterPassword$, + ); + if (!userHasMasterPassword) { + await this.router.navigate(["/settings/security/two-factor"]); + return; + } + } +} diff --git a/apps/web/src/app/auth/settings/security/security-routing.module.ts b/apps/web/src/app/auth/settings/security/security-routing.module.ts index 6ed21605184..14d4aab8a36 100644 --- a/apps/web/src/app/auth/settings/security/security-routing.module.ts +++ b/apps/web/src/app/auth/settings/security/security-routing.module.ts @@ -1,10 +1,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; +import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; + import { ChangePasswordComponent } from "../change-password.component"; import { TwoFactorSetupComponent } from "../two-factor/two-factor-setup.component"; import { DeviceManagementComponent } from "./device-management.component"; +import { PasswordSettingsComponent } from "./password-settings/password-settings.component"; import { SecurityKeysComponent } from "./security-keys.component"; import { SecurityComponent } from "./security.component"; @@ -14,10 +18,31 @@ const routes: Routes = [ component: SecurityComponent, data: { titleId: "security" }, children: [ - { path: "", pathMatch: "full", redirectTo: "change-password" }, + { path: "", pathMatch: "full", redirectTo: "password" }, { path: "change-password", component: ChangePasswordComponent, + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + false, + "/settings/security/password", + false, + ), + ], + data: { titleId: "masterPassword" }, + }, + { + path: "password", + component: PasswordSettingsComponent, + canActivate: [ + canAccessFeature( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + true, + "/settings/security/change-password", + false, + ), + ], data: { titleId: "masterPassword" }, }, { diff --git a/apps/web/src/app/auth/settings/security/security.component.html b/apps/web/src/app/auth/settings/security/security.component.html index 6bd7c1daf36..355a33d4427 100644 --- a/apps/web/src/app/auth/settings/security/security.component.html +++ b/apps/web/src/app/auth/settings/security/security.component.html @@ -1,7 +1,7 @@ - {{ "masterPassword" | i18n }} + {{ "masterPassword" | i18n }} {{ "twoStepLogin" | i18n }} {{ "devices" | i18n }} diff --git a/apps/web/src/app/auth/settings/security/security.component.ts b/apps/web/src/app/auth/settings/security/security.component.ts index 4f70a19e378..41b1af17abb 100644 --- a/apps/web/src/app/auth/settings/security/security.component.ts +++ b/apps/web/src/app/auth/settings/security/security.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; @Component({ @@ -10,6 +11,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co }) export class SecurityComponent implements OnInit { showChangePassword = true; + changePasswordRoute = "change-password"; constructor( private userVerificationService: UserVerificationService, @@ -18,5 +20,12 @@ export class SecurityComponent implements OnInit { async ngOnInit() { this.showChangePassword = await this.userVerificationService.hasMasterPassword(); + + const changePasswordRefreshFlag = await this.configService.getFeatureFlag( + FeatureFlag.PM16117_ChangeExistingPasswordRefactor, + ); + if (changePasswordRefreshFlag) { + this.changePasswordRoute = "password"; + } } } diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html index 078c926891a..7f093842b6a 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.html @@ -1,89 +1,100 @@ -
    - -
    - + + + + + + + +
    +} diff --git a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts index 93f2bc021cd..215035a0d16 100644 --- a/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts +++ b/apps/web/src/app/billing/trial-initiation/complete-trial-initiation/complete-trial-initiation.component.ts @@ -52,7 +52,8 @@ export type InitiationPath = export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { @ViewChild("stepper", { static: false }) verticalStepper: VerticalStepperComponent; - InputPasswordFlow = InputPasswordFlow; + inputPasswordFlow = InputPasswordFlow.AccountRegistration; + initializing = true; /** Password Manager or Secrets Manager */ product: ProductType; @@ -203,6 +204,8 @@ export class CompleteTrialInitiationComponent implements OnInit, OnDestroy { .subscribe(() => { this.orgInfoFormGroup.controls.name.markAsTouched(); }); + + this.initializing = false; } ngOnDestroy(): void { diff --git a/apps/web/src/app/core/core.module.ts b/apps/web/src/app/core/core.module.ts index 48e884f252c..e812edd8f32 100644 --- a/apps/web/src/app/core/core.module.ts +++ b/apps/web/src/app/core/core.module.ts @@ -34,6 +34,7 @@ import { LoginDecryptionOptionsService, TwoFactorAuthComponentService, TwoFactorAuthDuoComponentService, + ChangePasswordService, } from "@bitwarden/auth/angular"; import { InternalUserDecryptionOptionsServiceAbstraction, @@ -110,6 +111,7 @@ import { DefaultSshImportPromptService, SshImportPromptService } from "@bitwarde import { flagEnabled } from "../../utils/flags"; import { PolicyListService } from "../admin-console/core/policy-list.service"; import { + WebChangePasswordService, WebSetPasswordJitService, WebRegistrationFinishService, WebLoginComponentService, @@ -123,6 +125,7 @@ import { AcceptOrganizationInviteService } from "../auth/organization-invite/acc import { HtmlStorageService } from "../core/html-storage.service"; import { I18nService } from "../core/i18n.service"; import { WebFileDownloadService } from "../core/web-file-download.service"; +import { UserKeyRotationService } from "../key-management/key-rotation/user-key-rotation.service"; import { WebLockComponentService } from "../key-management/lock/services/web-lock-component.service"; import { WebProcessReloadService } from "../key-management/services/web-process-reload.service"; import { WebBiometricsService } from "../key-management/web-biometric.service"; @@ -373,6 +376,16 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultSshImportPromptService, deps: [DialogService, ToastService, PlatformUtilsService, I18nServiceAbstraction], }), + safeProvider({ + provide: ChangePasswordService, + useClass: WebChangePasswordService, + deps: [ + KeyServiceAbstraction, + MasterPasswordApiService, + InternalMasterPasswordServiceAbstraction, + UserKeyRotationService, + ], + }), ]; @NgModule({ diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index cf2174cc1db..d526b2b76ff 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -4549,8 +4549,8 @@ "updateEncryptionKeyWarning": { "message": "After updating your encryption key, you are required to log out and back in to all Bitwarden applications that you are currently using (such as the mobile app or browser extensions). Failure to log out and back in (which downloads your new encryption key) may result in data corruption. We will attempt to log you out automatically, however, it may be delayed." }, - "updateEncryptionKeyExportWarning": { - "message": "Any encrypted exports that you have saved will also become invalid." + "updateEncryptionKeyAccountExportWarning": { + "message": "Any account restricted exports you have saved will become invalid." }, "subscription": { "message": "Subscription" diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 920d35a1017..a8638efba18 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -27,6 +27,8 @@ import { TwoFactorAuthComponentService, TwoFactorAuthEmailComponentService, TwoFactorAuthWebAuthnComponentService, + ChangePasswordService, + DefaultChangePasswordService, } from "@bitwarden/auth/angular"; import { AuthRequestApiService, @@ -1538,6 +1540,15 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCipherEncryptionService, deps: [SdkService, LogService], }), + safeProvider({ + provide: ChangePasswordService, + useClass: DefaultChangePasswordService, + deps: [ + KeyService, + MasterPasswordApiServiceAbstraction, + InternalMasterPasswordServiceAbstraction, + ], + }), ]; @NgModule({ diff --git a/libs/auth/src/angular/change-password/change-password.component.html b/libs/auth/src/angular/change-password/change-password.component.html new file mode 100644 index 00000000000..fff873225be --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.component.html @@ -0,0 +1,20 @@ +@if (initializing) { + + {{ "loading" | i18n }} +} @else { + + +} diff --git a/libs/auth/src/angular/change-password/change-password.component.ts b/libs/auth/src/angular/change-password/change-password.component.ts new file mode 100644 index 00000000000..51c4d03d16f --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.component.ts @@ -0,0 +1,110 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { firstValueFrom } from "rxjs"; + +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"; +import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { SyncService } from "@bitwarden/common/platform/sync"; +import { UserId } from "@bitwarden/common/types/guid"; +import { ToastService } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; + +import { + InputPasswordComponent, + InputPasswordFlow, +} from "../input-password/input-password.component"; +import { PasswordInputResult } from "../input-password/password-input-result"; + +import { ChangePasswordService } from "./change-password.service.abstraction"; + +@Component({ + standalone: true, + selector: "auth-change-password", + templateUrl: "change-password.component.html", + imports: [InputPasswordComponent, I18nPipe], +}) +export class ChangePasswordComponent implements OnInit { + @Input() inputPasswordFlow: InputPasswordFlow = InputPasswordFlow.ChangePassword; + + activeAccount: Account | null = null; + email?: string; + userId?: UserId; + masterPasswordPolicyOptions?: MasterPasswordPolicyOptions; + initializing = true; + submitting = false; + + constructor( + private accountService: AccountService, + private changePasswordService: ChangePasswordService, + private i18nService: I18nService, + private messagingService: MessagingService, + private policyService: PolicyService, + private toastService: ToastService, + private syncService: SyncService, + ) {} + + async ngOnInit() { + this.activeAccount = await firstValueFrom(this.accountService.activeAccount$); + this.userId = this.activeAccount?.id; + this.email = this.activeAccount?.email; + + if (!this.userId) { + throw new Error("userId not found"); + } + + this.masterPasswordPolicyOptions = await firstValueFrom( + this.policyService.masterPasswordPolicyOptions$(this.userId), + ); + + this.initializing = false; + } + + async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) { + this.submitting = true; + + try { + if (passwordInputResult.rotateUserKey) { + if (this.activeAccount == null) { + throw new Error("activeAccount not found"); + } + + if (passwordInputResult.currentPassword == null) { + throw new Error("currentPassword not found"); + } + + await this.syncService.fullSync(true); + + await this.changePasswordService.rotateUserKeyMasterPasswordAndEncryptedData( + passwordInputResult.currentPassword, + passwordInputResult.newPassword, + this.activeAccount, + passwordInputResult.newPasswordHint, + ); + } else { + if (!this.userId) { + throw new Error("userId not found"); + } + + await this.changePasswordService.changePassword(passwordInputResult, this.userId); + + this.toastService.showToast({ + variant: "success", + title: this.i18nService.t("masterPasswordChanged"), + message: this.i18nService.t("masterPasswordChangedDesc"), + }); + + this.messagingService.send("logout"); + } + } catch { + this.toastService.showToast({ + variant: "error", + title: "", + message: this.i18nService.t("errorOccurred"), + }); + } finally { + this.submitting = false; + } + } +} diff --git a/libs/auth/src/angular/change-password/change-password.service.abstraction.ts b/libs/auth/src/angular/change-password/change-password.service.abstraction.ts new file mode 100644 index 00000000000..b036db439f8 --- /dev/null +++ b/libs/auth/src/angular/change-password/change-password.service.abstraction.ts @@ -0,0 +1,36 @@ +import { PasswordInputResult } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { UserId } from "@bitwarden/common/types/guid"; + +export abstract class ChangePasswordService { + /** + * Creates a new user key and re-encrypts all required data with it. + * - does so by calling the underlying method on the `UserKeyRotationService` + * - implemented in Web only + * + * @param currentPassword the current password + * @param newPassword the new password + * @param user the user account + * @param newPasswordHint the new password hint + * @throws if called from a non-Web client + */ + abstract rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + newPasswordHint: string, + ): Promise; + + /** + * Changes the user's password and re-encrypts the user key with the `newMasterKey`. + * - Specifically, this method uses credentials from the `passwordInputResult` to: + * 1. Decrypt the user key with the `currentMasterKey` + * 2. Re-encrypt that user key with the `newMasterKey`, resulting in a `newMasterKeyEncryptedUserKey` + * 3. Build a `PasswordRequest` object that gets POSTed to `"/accounts/password"` + * + * @param passwordInputResult credentials object received from the `InputPasswordComponent` + * @param userId the `userId` + * @throws if the `userId`, `currentMasterKey`, or `currentServerMasterKeyHash` is not found + */ + abstract changePassword(passwordInputResult: PasswordInputResult, userId: UserId): Promise; +} diff --git a/libs/auth/src/angular/change-password/default-change-password.service.spec.ts b/libs/auth/src/angular/change-password/default-change-password.service.spec.ts new file mode 100644 index 00000000000..ab993859d70 --- /dev/null +++ b/libs/auth/src/angular/change-password/default-change-password.service.spec.ts @@ -0,0 +1,177 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { UserId } from "@bitwarden/common/types/guid"; +import { MasterKey, UserKey } from "@bitwarden/common/types/key"; +import { KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management"; + +import { PasswordInputResult } from "../input-password/password-input-result"; + +import { ChangePasswordService } from "./change-password.service.abstraction"; +import { DefaultChangePasswordService } from "./default-change-password.service"; + +describe("DefaultChangePasswordService", () => { + let keyService: MockProxy; + let masterPasswordApiService: MockProxy; + let masterPasswordService: MockProxy; + + let sut: ChangePasswordService; + + const userId = "userId" as UserId; + + const user: Account = { + id: userId, + email: "email", + emailVerified: false, + name: "name", + }; + + const passwordInputResult: PasswordInputResult = { + currentMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, + currentServerMasterKeyHash: "currentServerMasterKeyHash", + + newPassword: "newPassword", + newPasswordHint: "newPasswordHint", + newMasterKey: new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey, + newServerMasterKeyHash: "newServerMasterKeyHash", + newLocalMasterKeyHash: "newLocalMasterKeyHash", + + kdfConfig: new PBKDF2KdfConfig(), + }; + + const decryptedUserKey = new SymmetricCryptoKey(new Uint8Array(64)) as UserKey; + const newMasterKeyEncryptedUserKey: [UserKey, EncString] = [ + decryptedUserKey, + { encryptedString: "newMasterKeyEncryptedUserKey" } as EncString, + ]; + + beforeEach(() => { + keyService = mock(); + masterPasswordApiService = mock(); + masterPasswordService = mock(); + + sut = new DefaultChangePasswordService( + keyService, + masterPasswordApiService, + masterPasswordService, + ); + + masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(decryptedUserKey); + keyService.encryptUserKeyWithMasterKey.mockResolvedValue(newMasterKeyEncryptedUserKey); + }); + + describe("changePassword()", () => { + it("should call the postPassword() API method with a the correct PasswordRequest credentials", async () => { + // Act + await sut.changePassword(passwordInputResult, userId); + + // Assert + expect(masterPasswordApiService.postPassword).toHaveBeenCalledWith( + expect.objectContaining({ + masterPasswordHash: passwordInputResult.currentServerMasterKeyHash, + masterPasswordHint: passwordInputResult.newPasswordHint, + newMasterPasswordHash: passwordInputResult.newServerMasterKeyHash, + key: newMasterKeyEncryptedUserKey[1].encryptedString, + }), + ); + }); + + it("should call decryptUserKeyWithMasterKey and encryptUserKeyWithMasterKey", async () => { + // Act + await sut.changePassword(passwordInputResult, userId); + + // Assert + expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith( + passwordInputResult.currentMasterKey, + userId, + ); + expect(keyService.encryptUserKeyWithMasterKey).toHaveBeenCalledWith( + passwordInputResult.newMasterKey, + decryptedUserKey, + ); + }); + + it("should throw if a userId was not found", async () => { + // Arrange + const userId: null = null; + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("userId not found"); + }); + + it("should throw if a currentMasterKey was not found", async () => { + // Arrange + const incorrectPasswordInputResult = { ...passwordInputResult }; + incorrectPasswordInputResult.currentMasterKey = null; + + // Act + const testFn = sut.changePassword(incorrectPasswordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow( + "currentMasterKey or currentServerMasterKeyHash not found", + ); + }); + + it("should throw if a currentServerMasterKeyHash was not found", async () => { + // Arrange + const incorrectPasswordInputResult = { ...passwordInputResult }; + incorrectPasswordInputResult.currentServerMasterKeyHash = null; + + // Act + const testFn = sut.changePassword(incorrectPasswordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow( + "currentMasterKey or currentServerMasterKeyHash not found", + ); + }); + + it("should throw an error if user key decryption fails", async () => { + // Arrange + masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(null); + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("Could not decrypt user key"); + }); + + it("should throw an error if postPassword() fails", async () => { + // Arrange + masterPasswordApiService.postPassword.mockRejectedValueOnce(new Error("error")); + + // Act + const testFn = sut.changePassword(passwordInputResult, userId); + + // Assert + await expect(testFn).rejects.toThrow("Could not change password"); + expect(masterPasswordApiService.postPassword).toHaveBeenCalled(); + }); + }); + + describe("rotateUserKeyMasterPasswordAndEncryptedData()", () => { + it("should throw an error (the method is only implemented in Web)", async () => { + // Act + const testFn = sut.rotateUserKeyMasterPasswordAndEncryptedData( + "currentPassword", + "newPassword", + user, + "newPasswordHint", + ); + + // Assert + await expect(testFn).rejects.toThrow( + "rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web", + ); + }); + }); +}); diff --git a/libs/auth/src/angular/change-password/default-change-password.service.ts b/libs/auth/src/angular/change-password/default-change-password.service.ts new file mode 100644 index 00000000000..315f979aad9 --- /dev/null +++ b/libs/auth/src/angular/change-password/default-change-password.service.ts @@ -0,0 +1,59 @@ +import { PasswordInputResult, ChangePasswordService } from "@bitwarden/auth/angular"; +import { Account } from "@bitwarden/common/auth/abstractions/account.service"; +import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction"; +import { PasswordRequest } from "@bitwarden/common/auth/models/request/password.request"; +import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; +import { UserId } from "@bitwarden/common/types/guid"; +import { KeyService } from "@bitwarden/key-management"; + +export class DefaultChangePasswordService implements ChangePasswordService { + constructor( + protected keyService: KeyService, + protected masterPasswordApiService: MasterPasswordApiService, + protected masterPasswordService: InternalMasterPasswordServiceAbstraction, + ) {} + + async rotateUserKeyMasterPasswordAndEncryptedData( + currentPassword: string, + newPassword: string, + user: Account, + hint: string, + ): Promise { + throw new Error("rotateUserKeyMasterPasswordAndEncryptedData() is only implemented in Web"); + } + + async changePassword(passwordInputResult: PasswordInputResult, userId: UserId) { + if (!userId) { + throw new Error("userId not found"); + } + if (!passwordInputResult.currentMasterKey || !passwordInputResult.currentServerMasterKeyHash) { + throw new Error("currentMasterKey or currentServerMasterKeyHash not found"); + } + + const decryptedUserKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( + passwordInputResult.currentMasterKey, + userId, + ); + + if (decryptedUserKey == null) { + throw new Error("Could not decrypt user key"); + } + + const newMasterKeyEncryptedUserKey = await this.keyService.encryptUserKeyWithMasterKey( + passwordInputResult.newMasterKey, + decryptedUserKey, + ); + + const request = new PasswordRequest(); + request.masterPasswordHash = passwordInputResult.currentServerMasterKeyHash; + request.newMasterPasswordHash = passwordInputResult.newServerMasterKeyHash; + request.masterPasswordHint = passwordInputResult.newPasswordHint; + request.key = newMasterKeyEncryptedUserKey[1].encryptedString as string; + + try { + await this.masterPasswordApiService.postPassword(request); + } catch { + throw new Error("Could not change password"); + } + } +} diff --git a/libs/auth/src/angular/index.ts b/libs/auth/src/angular/index.ts index 91d34a34838..f4f6cc71a42 100644 --- a/libs/auth/src/angular/index.ts +++ b/libs/auth/src/angular/index.ts @@ -8,6 +8,11 @@ export * from "./anon-layout/anon-layout-wrapper.component"; export * from "./anon-layout/anon-layout-wrapper-data.service"; export * from "./anon-layout/default-anon-layout-wrapper-data.service"; +// change password +export * from "./change-password/change-password.component"; +export * from "./change-password/change-password.service.abstraction"; +export * from "./change-password/default-change-password.service"; + // fingerprint dialog export * from "./fingerprint-dialog/fingerprint-dialog.component"; diff --git a/libs/auth/src/angular/input-password/input-password.component.html b/libs/auth/src/angular/input-password/input-password.component.html index 39995f9f44f..8955a7b40b1 100644 --- a/libs/auth/src/angular/input-password/input-password.component.html +++ b/libs/auth/src/angular/input-password/input-password.component.html @@ -6,8 +6,8 @@ {{ "currentMasterPass" | i18n }} @@ -58,12 +58,12 @@
    - {{ "confirmMasterPassword" | i18n }} + {{ "confirmNewMasterPass" | i18n }}