From 01d6b3dd1e203c7cae15e953e4588cb495a7a6f8 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 23 Jan 2026 15:42:08 +0100 Subject: [PATCH 1/9] Disable rounded corner in desktop (#18498) --- apps/desktop/src/app/layout/desktop-layout.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/app/layout/desktop-layout.component.html b/apps/desktop/src/app/layout/desktop-layout.component.html index f9921ac11ef..cb969f573fc 100644 --- a/apps/desktop/src/app/layout/desktop-layout.component.html +++ b/apps/desktop/src/app/layout/desktop-layout.component.html @@ -1,4 +1,4 @@ - + From d1c7f995c3398cd35037e67163c188c66a7c6899 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 23 Jan 2026 16:26:10 +0100 Subject: [PATCH 2/9] [PM-31158] Change default send to file for old ui (#18526) * Change default send to file for old ui * Trigger change detection to ensure signals are refreshed --- .../src/app/tools/send-v2/send-v2.component.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts index 95c0c971d2c..0df71a78412 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.ts +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.ts @@ -79,7 +79,6 @@ export class SendV2Component { protected readonly sendId = signal(null); protected readonly action = signal(Action.None); - private readonly selectedSendTypeOverride = signal(undefined); private sendFormConfigService = inject(DefaultSendFormConfigService); private sendItemsService = inject(SendItemsService); @@ -151,10 +150,9 @@ export class SendV2Component { protected readonly selectedSendType = computed(() => { const action = this.action(); - const typeOverride = this.selectedSendTypeOverride(); - if (action === Action.Add && typeOverride !== undefined) { - return typeOverride; + if (action === Action.Add) { + return undefined; } const sendId = this.sendId(); @@ -173,24 +171,20 @@ export class SendV2Component { } else { this.action.set(Action.Add); this.sendId.set(null); - this.selectedSendTypeOverride.set(type); - const component = this.addEditComponent(); - if (component) { - await component.resetAndLoad(); - } + this.cdr.detectChanges(); + void this.addEditComponent()?.resetAndLoad(); } } - /** Used by old UI to add a send without specifying type (defaults to Text) */ + /** Used by old UI to add a send without specifying type (defaults to File) */ protected async addSendWithoutType(): Promise { - await this.addSend(SendType.Text); + await this.addSend(SendType.File); } protected closeEditPanel(): void { this.action.set(Action.None); this.sendId.set(null); - this.selectedSendTypeOverride.set(undefined); } protected async savedSend(send: SendView): Promise { From a8f0bc8d4bb74f0a42963e2903fbd3bb5fa6a805 Mon Sep 17 00:00:00 2001 From: adudek-bw Date: Fri, 23 Jan 2026 10:40:41 -0500 Subject: [PATCH 3/9] Change send success page text to not mention email verification (#18382) * Change send success page text to not mention email verification * [PM-30706] replacing message and fixing double word bug * [PM-30706] improving message parameters --------- Co-authored-by: Alex Dragovich <46065570+itsadrago@users.noreply.github.com> Co-authored-by: Alex Dragovich --- .../send/shared/send-success-drawer-dialog.component.html | 2 +- apps/web/src/locales/en/messages.json | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/tools/send/shared/send-success-drawer-dialog.component.html b/apps/web/src/app/tools/send/shared/send-success-drawer-dialog.component.html index b9326ca08ac..a484f210f62 100644 --- a/apps/web/src/app/tools/send/shared/send-success-drawer-dialog.component.html +++ b/apps/web/src/app/tools/send/shared/send-success-drawer-dialog.component.html @@ -17,7 +17,7 @@

- {{ "sendCreatedDescription" | i18n: formattedExpirationTime }} + {{ "sendCreatedDescriptionV2" | i18n: formattedExpirationTime }}

diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e7cc7c6cb5c..db7332f4c2b 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -5626,13 +5626,13 @@ "message": "Send created successfully!", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendCreatedDescription": { - "message": "Copy and share this Send link. It can be viewed by the people you specified for the next $TIME$.", + "sendCreatedDescriptionV2": { + "message": "Copy and share this Send link. The Send will be available to anyone with the link for the next $TIME$.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated.", "placeholders": { "time": { "content": "$1", - "example": "7 days" + "example": "7 days, 1 hour, 1 day" } } }, From c2f68e0bf66cdddf4c6ea3459144af023a19424f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Fri, 23 Jan 2026 16:54:24 +0100 Subject: [PATCH 4/9] Use correct key for new send (#18523) --- apps/desktop/src/app/tools/send-v2/send-v2.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/app/tools/send-v2/send-v2.component.html b/apps/desktop/src/app/tools/send-v2/send-v2.component.html index dad0e541a4d..53dee854012 100644 --- a/apps/desktop/src/app/tools/send-v2/send-v2.component.html +++ b/apps/desktop/src/app/tools/send-v2/send-v2.component.html @@ -60,7 +60,7 @@ buttonType="primary" (click)="addSendWithoutType()" > - {{ "newSend" | i18n }} + {{ "createSend" | i18n }} From f57cb83d460d746fc7802a28ad4cb20fb5af408d Mon Sep 17 00:00:00 2001 From: Leslie Xiong Date: Fri, 23 Jan 2026 10:55:41 -0500 Subject: [PATCH 5/9] [BUG FIX]Desktop/Pm 31148/Pm 31149/Unexpected behaviors for Collections and Folders (#18506) * fixed collections still appearing if all orgs are suspended * fixed 'No folders' not displaying vault items * PR followup: - converted `allOrganizationsDisabled` to computed property - converted observables to signals --- .../vault-filter/vault-filter.component.html | 8 ++-- .../vault-filter/vault-filter.component.ts | 38 ++++++++++--------- .../src/vault/app/vault-v3/vault.component.ts | 2 + 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.html b/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.html index e0ae4687ed8..2110c545d9e 100644 --- a/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.html +++ b/apps/desktop/src/vault/app/vault-v3/vault-filter/vault-filter.component.html @@ -6,11 +6,11 @@ - + @if (showCollectionsFilter()) { - @for (collection of (collections$ | async)?.children ?? []; track collection.node.id) { + @for (collection of collections()?.children ?? []; track collection.node.id) { } @@ -32,7 +32,7 @@ [appA11yTitle]="'folders' | i18n" [disableToggleOnClick]="true" > - @for (folder of (folders$ | async)?.children ?? []; track folder.node.id) { + @for (folder of folders()?.children ?? []; track folder.node.id) { >; - protected collections$: Observable>; - protected folders$: Observable>; - protected cipherTypes$: Observable>; + protected readonly organizations = toSignal(this.vaultFilterService.organizationTree$); + protected readonly collections = toSignal(this.vaultFilterService.collectionTree$); + protected readonly folders = toSignal(this.vaultFilterService.folderTree$); + protected readonly cipherTypes = toSignal(this.vaultFilterService.cipherTypeTree$); protected readonly showCollectionsFilter = computed(() => { - return this.organizations$ != null && !this.activeFilter()?.isMyVaultSelected; + return ( + this.organizations() != null && + !this.activeFilter()?.isMyVaultSelected && + !this.allOrganizationsDisabled() + ); + }); + + protected readonly allOrganizationsDisabled = computed(() => { + if (!this.organizations()) { + return false; + } + const orgs = this.organizations().children.filter((org) => org.node.id !== "MyVault"); + return orgs.length > 0 && orgs.every((org) => !org.node.enabled); }); private async setActivePolicies() { @@ -98,16 +107,9 @@ export class VaultFilterComponent implements OnInit { async ngOnInit(): Promise { this.activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId)); - this.organizations$ = this.vaultFilterService.organizationTree$; - if ( - this.organizations$ != null && - (await firstValueFrom(this.organizations$)).children.length > 0 - ) { + if (this.organizations() != null && this.organizations().children.length > 0) { await this.setActivePolicies(); } - this.cipherTypes$ = this.vaultFilterService.cipherTypeTree$; - this.folders$ = this.vaultFilterService.folderTree$; - this.collections$ = this.vaultFilterService.collectionTree$; this.showArchiveVaultFilter = await firstValueFrom( this.cipherArchiveService.hasArchiveFlagEnabled$, diff --git a/apps/desktop/src/vault/app/vault-v3/vault.component.ts b/apps/desktop/src/vault/app/vault-v3/vault.component.ts index a64830c3b5d..455f9177c4d 100644 --- a/apps/desktop/src/vault/app/vault-v3/vault.component.ts +++ b/apps/desktop/src/vault/app/vault-v3/vault.component.ts @@ -805,6 +805,8 @@ export class VaultComponent implements OnInit, OnDestroy, CopyClickListener { type: CipherViewLikeUtils.getType(cipher), // Normalize undefined organizationId to null for filter compatibility organizationId: cipher.organizationId ?? null, + // Normalize empty string folderId to null for filter compatibility + folderId: cipher.folderId ? cipher.folderId : null, // Explicitly include isDeleted and isArchived since they might be getters isDeleted: CipherViewLikeUtils.isDeleted(cipher), isArchived: CipherViewLikeUtils.isArchived(cipher), From 47b574dbc31a02a1a246ffd4f542e2727dd02746 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Fri, 23 Jan 2026 11:30:31 -0500 Subject: [PATCH 6/9] [PM-31072] If Archive Item is in Trash, remove Unarchive button (#18481) --- .../vault-item-dialog/vault-item-dialog.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html index e4030a7ab18..059347709f0 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.html @@ -87,7 +87,7 @@ @if (showActionButtons) {
@if ((userCanArchive$ | async) && !params.isAdminConsoleAction) { - @if (isCipherArchived) { + @if (isCipherArchived && !cipher?.isDeleted) { } diff --git a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html index fdfe8eb55ff..d1ee9d29ebd 100644 --- a/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html +++ b/apps/web/src/app/tools/send/new-send/new-send-dropdown.component.html @@ -1,5 +1,5 @@ diff --git a/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html index 00cfa701529..268f5b912d1 100644 --- a/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html +++ b/libs/vault/src/components/new-cipher-menu/new-cipher-menu.component.html @@ -8,7 +8,7 @@ id="newItemDropdown" [appA11yTitle]="'new' | i18n" > - + {{ "new" | i18n }} From 6cf434cf114834fea58f4e0171fa75b4b4716f78 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Fri, 23 Jan 2026 11:57:55 -0500 Subject: [PATCH 8/9] [CL-841] landing layout component (#17969) * wip * wip * implement new control flow syntax * new landing helper components * add missing imports to header * create max width container * create landing card component * address claude feedback * fix center aligned text * ensure secondary content is centered for now * only render container when there is content * remove max width container * remove constructor init of variable * remove unnecessary styling * build styling into helper components * ensure content grows * ensure content takes allowed width * apply padding to elements instead of header and check for projected content * use Array.from to filter nodes * fix logo width * use has selector to apply actions styles, simplify heading padding * remove unneeded content projection * remove unneeded comment * use modern control flow for story * update max width classes to signal and remove switch * make logo input readonly * remove variables * remove object type * fix width types usage * add comments about component usage * fix broken variable reference * fix broken max width class usage * only appyly y padding to header actions --- .../anon-layout-wrapper.component.ts | 8 +- .../anon-layout/anon-layout.component.html | 93 ++-------- .../src/anon-layout/anon-layout.component.ts | 37 +--- libs/components/src/index.ts | 1 + libs/components/src/landing-layout/index.ts | 7 + .../landing-card.component.html | 5 + .../landing-layout/landing-card.component.ts | 33 ++++ .../landing-content.component.html | 8 + .../landing-content.component.ts | 63 +++++++ .../landing-footer.component.html | 3 + .../landing-footer.component.ts | 29 ++++ .../landing-header.component.html | 13 ++ .../landing-header.component.ts | 42 +++++ .../landing-hero.component.html | 28 +++ .../landing-layout/landing-hero.component.ts | 40 +++++ .../landing-layout.component.html | 25 +++ .../landing-layout.component.ts | 40 +++++ .../landing-layout/landing-layout.module.ts | 28 +++ .../landing-layout/landing-layout.stories.ts | 162 ++++++++++++++++++ 19 files changed, 551 insertions(+), 114 deletions(-) create mode 100644 libs/components/src/landing-layout/index.ts create mode 100644 libs/components/src/landing-layout/landing-card.component.html create mode 100644 libs/components/src/landing-layout/landing-card.component.ts create mode 100644 libs/components/src/landing-layout/landing-content.component.html create mode 100644 libs/components/src/landing-layout/landing-content.component.ts create mode 100644 libs/components/src/landing-layout/landing-footer.component.html create mode 100644 libs/components/src/landing-layout/landing-footer.component.ts create mode 100644 libs/components/src/landing-layout/landing-header.component.html create mode 100644 libs/components/src/landing-layout/landing-header.component.ts create mode 100644 libs/components/src/landing-layout/landing-hero.component.html create mode 100644 libs/components/src/landing-layout/landing-hero.component.ts create mode 100644 libs/components/src/landing-layout/landing-layout.component.html create mode 100644 libs/components/src/landing-layout/landing-layout.component.ts create mode 100644 libs/components/src/landing-layout/landing-layout.module.ts create mode 100644 libs/components/src/landing-layout/landing-layout.stories.ts diff --git a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts index 553da0c541b..84140a8953a 100644 --- a/libs/components/src/anon-layout/anon-layout-wrapper.component.ts +++ b/libs/components/src/anon-layout/anon-layout-wrapper.component.ts @@ -7,10 +7,10 @@ import { Icon } from "@bitwarden/assets/svg"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Translation } from "../dialog"; +import { LandingContentMaxWidthType } from "../landing-layout"; import { AnonLayoutWrapperDataService } from "./anon-layout-wrapper-data.service"; -import { AnonLayoutComponent, AnonLayoutMaxWidth } from "./anon-layout.component"; - +import { AnonLayoutComponent } from "./anon-layout.component"; export interface AnonLayoutWrapperData { /** * The optional title of the page. @@ -35,7 +35,7 @@ export interface AnonLayoutWrapperData { /** * Optional flag to set the max-width of the page. Defaults to 'md' if not provided. */ - maxWidth?: AnonLayoutMaxWidth; + maxWidth?: LandingContentMaxWidthType; /** * Hide the card that wraps the default content. Defaults to false. */ @@ -59,7 +59,7 @@ export class AnonLayoutWrapperComponent implements OnInit { protected pageSubtitle?: string | null; protected pageIcon: Icon | null = null; protected showReadonlyHostname?: boolean | null; - protected maxWidth?: AnonLayoutMaxWidth | null; + protected maxWidth?: LandingContentMaxWidthType | null; protected hideCardWrapper?: boolean | null; protected hideBackgroundIllustration?: boolean | null; diff --git a/libs/components/src/anon-layout/anon-layout.component.html b/libs/components/src/anon-layout/anon-layout.component.html index 6bd72a25382..932ff10832c 100644 --- a/libs/components/src/anon-layout/anon-layout.component.html +++ b/libs/components/src/anon-layout/anon-layout.component.html @@ -1,76 +1,26 @@ -
-
- @if (!hideLogo()) { - - - - } -
- -
-
+ + + + -
- @let iconInput = icon(); - - - -
- -
- - @if (title()) { - -

- {{ title() }} -

- -

- {{ title() }} -

- } - - @if (subtitle()) { -
{{ subtitle() }}
- } -
- -
+ + @if (hideCardWrapper()) {
} @else { - + - + } - -
+
+ +
+ @if (!hideFooter()) { -
+ @if (showReadonlyHostname()) {
{{ "accessing" | i18n }} {{ hostname }}
} @else { @@ -81,22 +31,9 @@
© {{ year }} Bitwarden Inc.
{{ version }}
} -
+ } - - @if (!hideBackgroundIllustration()) { -
- -
-
- -
- } -
+ diff --git a/libs/components/src/anon-layout/anon-layout.component.ts b/libs/components/src/anon-layout/anon-layout.component.ts index e6572a0c3c1..eded556cd53 100644 --- a/libs/components/src/anon-layout/anon-layout.component.ts +++ b/libs/components/src/anon-layout/anon-layout.component.ts @@ -11,23 +11,17 @@ import { import { RouterModule } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { - BackgroundLeftIllustration, - BackgroundRightIllustration, - BitwardenLogo, - Icon, -} from "@bitwarden/assets/svg"; +import { BitwardenLogo, Icon } from "@bitwarden/assets/svg"; import { ClientType } from "@bitwarden/common/enums"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { BaseCardComponent } from "../card"; import { IconModule } from "../icon"; +import { LandingContentMaxWidthType } from "../landing-layout"; +import { LandingLayoutModule } from "../landing-layout/landing-layout.module"; import { SharedModule } from "../shared"; import { TypographyModule } from "../typography"; -export type AnonLayoutMaxWidth = "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; - // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush // eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection @Component({ @@ -39,7 +33,7 @@ export type AnonLayoutMaxWidth = "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; TypographyModule, SharedModule, RouterModule, - BaseCardComponent, + LandingLayoutModule, ], }) export class AnonLayoutComponent implements OnInit, OnChanges { @@ -49,9 +43,6 @@ export class AnonLayoutComponent implements OnInit, OnChanges { return ["tw-h-full"]; } - readonly leftIllustration = BackgroundLeftIllustration; - readonly rightIllustration = BackgroundRightIllustration; - readonly title = input(); readonly subtitle = input(); readonly icon = model.required(); @@ -66,7 +57,7 @@ export class AnonLayoutComponent implements OnInit, OnChanges { * * @default 'md' */ - readonly maxWidth = model("md"); + readonly maxWidth = model("md"); protected logo = BitwardenLogo; protected year: string; @@ -76,24 +67,6 @@ export class AnonLayoutComponent implements OnInit, OnChanges { protected hideYearAndVersion = false; - get maxWidthClass(): string { - const maxWidth = this.maxWidth(); - switch (maxWidth) { - case "md": - return "tw-max-w-md"; - case "lg": - return "tw-max-w-lg"; - case "xl": - return "tw-max-w-xl"; - case "2xl": - return "tw-max-w-2xl"; - case "3xl": - return "tw-max-w-3xl"; - case "4xl": - return "tw-max-w-4xl"; - } - } - constructor( private environmentService: EnvironmentService, private platformUtilsService: PlatformUtilsService, diff --git a/libs/components/src/index.ts b/libs/components/src/index.ts index 23fb5beb456..9c4dadadd4b 100644 --- a/libs/components/src/index.ts +++ b/libs/components/src/index.ts @@ -25,6 +25,7 @@ export * from "./icon"; export * from "./icon-tile"; export * from "./input"; export * from "./item"; +export * from "./landing-layout"; export * from "./layout"; export * from "./link"; export * from "./menu"; diff --git a/libs/components/src/landing-layout/index.ts b/libs/components/src/landing-layout/index.ts new file mode 100644 index 00000000000..49b3d24631d --- /dev/null +++ b/libs/components/src/landing-layout/index.ts @@ -0,0 +1,7 @@ +export * from "./landing-layout.component"; +export * from "./landing-layout.module"; +export * from "./landing-card.component"; +export * from "./landing-content.component"; +export * from "./landing-footer.component"; +export * from "./landing-header.component"; +export * from "./landing-hero.component"; diff --git a/libs/components/src/landing-layout/landing-card.component.html b/libs/components/src/landing-layout/landing-card.component.html new file mode 100644 index 00000000000..bea783489bf --- /dev/null +++ b/libs/components/src/landing-layout/landing-card.component.html @@ -0,0 +1,5 @@ + + + diff --git a/libs/components/src/landing-layout/landing-card.component.ts b/libs/components/src/landing-layout/landing-card.component.ts new file mode 100644 index 00000000000..cea04f6f784 --- /dev/null +++ b/libs/components/src/landing-layout/landing-card.component.ts @@ -0,0 +1,33 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +import { BaseCardComponent } from "../card"; + +/** + * Card component for landing pages that wraps content in a styled container. + * + * @remarks + * This component provides: + * - Card-based layout with consistent styling + * - Content projection for forms, text, or other content + * - Proper elevation and border styling + * + * Use this component inside `bit-landing-content` to wrap forms, content sections, + * or any content that should appear in a contained, elevated card. + * + * @example + * ```html + * + *
+ * + *
+ *
+ * ``` + */ +@Component({ + selector: "bit-landing-card", + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [BaseCardComponent], + templateUrl: "./landing-card.component.html", +}) +export class LandingCardComponent {} diff --git a/libs/components/src/landing-layout/landing-content.component.html b/libs/components/src/landing-layout/landing-content.component.html new file mode 100644 index 00000000000..a09db26e4e4 --- /dev/null +++ b/libs/components/src/landing-layout/landing-content.component.html @@ -0,0 +1,8 @@ +
+
+ + +
+
diff --git a/libs/components/src/landing-layout/landing-content.component.ts b/libs/components/src/landing-layout/landing-content.component.ts new file mode 100644 index 00000000000..940e4b01f53 --- /dev/null +++ b/libs/components/src/landing-layout/landing-content.component.ts @@ -0,0 +1,63 @@ +import { ChangeDetectionStrategy, Component, computed, input } from "@angular/core"; + +export const LandingContentMaxWidth = ["md", "lg", "xl", "2xl", "3xl", "4xl"] as const; + +export type LandingContentMaxWidthType = (typeof LandingContentMaxWidth)[number]; + +/** + * Main content container for landing pages with configurable max-width constraints. + * + * @remarks + * This component provides: + * - Centered content area with alternative background color + * - Configurable maximum width to control content readability + * - Content projection slots for hero section and main content + * - Responsive padding and layout + * + * Use this component inside `bit-landing-layout` to wrap your main page content. + * Optionally include a `bit-landing-hero` as the first child for consistent hero section styling. + * + * @example + * ```html + * + * + * + * + * + * + * ``` + */ +@Component({ + selector: "bit-landing-content", + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "./landing-content.component.html", + host: { + class: "tw-grow tw-flex tw-flex-col", + }, +}) +export class LandingContentComponent { + /** + * Max width of the landing layout container. + * + * @default "md" + */ + readonly maxWidth = input("md"); + + private readonly maxWidthClassMap: Record = { + md: "tw-max-w-md", + lg: "tw-max-w-lg", + xl: "tw-max-w-xl", + "2xl": "tw-max-w-2xl", + "3xl": "tw-max-w-3xl", + "4xl": "tw-max-w-4xl", + }; + + readonly maxWidthClasses = computed(() => { + const maxWidthClass = this.maxWidthClassMap[this.maxWidth()]; + return `tw-flex tw-flex-col tw-w-full ${maxWidthClass}`; + }); +} diff --git a/libs/components/src/landing-layout/landing-footer.component.html b/libs/components/src/landing-layout/landing-footer.component.html new file mode 100644 index 00000000000..c0230a93171 --- /dev/null +++ b/libs/components/src/landing-layout/landing-footer.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/libs/components/src/landing-layout/landing-footer.component.ts b/libs/components/src/landing-layout/landing-footer.component.ts new file mode 100644 index 00000000000..f18199bd280 --- /dev/null +++ b/libs/components/src/landing-layout/landing-footer.component.ts @@ -0,0 +1,29 @@ +import { ChangeDetectionStrategy, Component } from "@angular/core"; + +/** + * Footer component for landing pages. + * + * @remarks + * This component provides: + * - Content projection for custom footer content (e.g., links, copyright, legal) + * - Consistent footer positioning at the bottom of the page + * - Proper z-index to appear above background illustrations + * + * Use this component inside `bit-landing-layout` as the last child to position it at the bottom. + * + * @example + * ```html + * + *
+ * Privacy + * © 2024 Bitwarden + *
+ *
+ * ``` + */ +@Component({ + selector: "bit-landing-footer", + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "./landing-footer.component.html", +}) +export class LandingFooterComponent {} diff --git a/libs/components/src/landing-layout/landing-header.component.html b/libs/components/src/landing-layout/landing-header.component.html new file mode 100644 index 00000000000..ed6d34ef23b --- /dev/null +++ b/libs/components/src/landing-layout/landing-header.component.html @@ -0,0 +1,13 @@ +
+ @if (!hideLogo()) { + + + + } +
+ +
+
diff --git a/libs/components/src/landing-layout/landing-header.component.ts b/libs/components/src/landing-layout/landing-header.component.ts new file mode 100644 index 00000000000..eb5329e915d --- /dev/null +++ b/libs/components/src/landing-layout/landing-header.component.ts @@ -0,0 +1,42 @@ +import { ChangeDetectionStrategy, Component, input } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { BitwardenLogo } from "@bitwarden/assets/svg"; + +import { IconModule } from "../icon"; +import { SharedModule } from "../shared"; + +/** + * Header component for landing pages with optional Bitwarden logo and header actions slot. + * + * @remarks + * This component provides: + * - Optional Bitwarden logo with link to home page (left-aligned) + * - Default content projection slot for header actions (right-aligned, auto-margin left) + * - Consistent header styling across landing pages + * - Responsive layout that adapts logo size + * + * Use this component inside `bit-landing-layout` as the first child to position it at the top. + * Content projected into this component will automatically align to the right side of the header. + * + * @example + * ```html + * + * + * + * + * ``` + */ +@Component({ + selector: "bit-landing-header", + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "./landing-header.component.html", + imports: [RouterModule, IconModule, SharedModule], +}) +export class LandingHeaderComponent { + readonly hideLogo = input(false); + protected readonly logo = BitwardenLogo; +} diff --git a/libs/components/src/landing-layout/landing-hero.component.html b/libs/components/src/landing-layout/landing-hero.component.html new file mode 100644 index 00000000000..dbce6a7c585 --- /dev/null +++ b/libs/components/src/landing-layout/landing-hero.component.html @@ -0,0 +1,28 @@ +@if (icon() || title() || subtitle()) { +
+ @if (icon()) { + + +
+ +
+ } + + @if (title()) { + +

+ {{ title() }} +

+ +

+ {{ title() }} +

+ } + + @if (subtitle()) { +
{{ subtitle() }}
+ } +
+} diff --git a/libs/components/src/landing-layout/landing-hero.component.ts b/libs/components/src/landing-layout/landing-hero.component.ts new file mode 100644 index 00000000000..b29e9768efd --- /dev/null +++ b/libs/components/src/landing-layout/landing-hero.component.ts @@ -0,0 +1,40 @@ +import { ChangeDetectionStrategy, Component, input } from "@angular/core"; + +import { Icon } from "@bitwarden/assets/svg"; + +import { IconModule } from "../icon"; +import { TypographyModule } from "../typography"; + +/** + * Hero section component for landing pages featuring an optional icon, title, and subtitle. + * + * @remarks + * This component provides: + * - Optional icon display (e.g., feature icons, status icons) + * - Large title text with consistent typography + * - Subtitle text for additional context + * - Centered layout with proper spacing + * + * Use this component as the first child inside `bit-landing-content` to create a prominent + * hero section that introduces the page's purpose. + * + * @example + * ```html + * + * ``` + */ +@Component({ + selector: "bit-landing-hero", + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "./landing-hero.component.html", + imports: [IconModule, TypographyModule], +}) +export class LandingHeroComponent { + readonly icon = input(null); + readonly title = input(); + readonly subtitle = input(); +} diff --git a/libs/components/src/landing-layout/landing-layout.component.html b/libs/components/src/landing-layout/landing-layout.component.html new file mode 100644 index 00000000000..1164f538116 --- /dev/null +++ b/libs/components/src/landing-layout/landing-layout.component.html @@ -0,0 +1,25 @@ +
+ +
+ +
+ @if (!hideBackgroundIllustration()) { +
+ +
+
+ +
+ } + +
diff --git a/libs/components/src/landing-layout/landing-layout.component.ts b/libs/components/src/landing-layout/landing-layout.component.ts new file mode 100644 index 00000000000..520cca945d6 --- /dev/null +++ b/libs/components/src/landing-layout/landing-layout.component.ts @@ -0,0 +1,40 @@ +import { Component, ChangeDetectionStrategy, inject, input } from "@angular/core"; + +import { BackgroundLeftIllustration, BackgroundRightIllustration } from "@bitwarden/assets/svg"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { IconModule } from "../icon"; + +/** + * Root layout component for landing pages providing a full-screen container with optional decorative background illustrations. + * + * @remarks + * This component serves as the outermost wrapper for landing pages and provides: + * - Full-screen layout that adapts to different client types (web, browser, desktop) + * - Optional decorative background illustrations in the bottom corners + * - Content projection slots for header, main content, and footer + * + * @example + * ```html + * + * ... + * ... + * ... + * + * ``` + */ +@Component({ + selector: "bit-landing-layout", + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: "./landing-layout.component.html", + imports: [IconModule], +}) +export class LandingLayoutComponent { + readonly hideBackgroundIllustration = input(false); + + protected readonly leftIllustration = BackgroundLeftIllustration; + protected readonly rightIllustration = BackgroundRightIllustration; + + private readonly platformUtilsService: PlatformUtilsService = inject(PlatformUtilsService); + protected readonly clientType = this.platformUtilsService.getClientType(); +} diff --git a/libs/components/src/landing-layout/landing-layout.module.ts b/libs/components/src/landing-layout/landing-layout.module.ts new file mode 100644 index 00000000000..d225b8b35e1 --- /dev/null +++ b/libs/components/src/landing-layout/landing-layout.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from "@angular/core"; + +import { LandingCardComponent } from "./landing-card.component"; +import { LandingContentComponent } from "./landing-content.component"; +import { LandingFooterComponent } from "./landing-footer.component"; +import { LandingHeaderComponent } from "./landing-header.component"; +import { LandingHeroComponent } from "./landing-hero.component"; +import { LandingLayoutComponent } from "./landing-layout.component"; + +@NgModule({ + imports: [ + LandingLayoutComponent, + LandingHeaderComponent, + LandingHeroComponent, + LandingFooterComponent, + LandingContentComponent, + LandingCardComponent, + ], + exports: [ + LandingLayoutComponent, + LandingHeaderComponent, + LandingHeroComponent, + LandingFooterComponent, + LandingContentComponent, + LandingCardComponent, + ], +}) +export class LandingLayoutModule {} diff --git a/libs/components/src/landing-layout/landing-layout.stories.ts b/libs/components/src/landing-layout/landing-layout.stories.ts new file mode 100644 index 00000000000..7ea9598a64a --- /dev/null +++ b/libs/components/src/landing-layout/landing-layout.stories.ts @@ -0,0 +1,162 @@ +import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; + +import { ClientType } from "@bitwarden/common/enums"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { ButtonModule } from "../button"; + +import { LandingLayoutComponent } from "./landing-layout.component"; + +class MockPlatformUtilsService implements Partial { + getClientType = () => ClientType.Web; +} + +type StoryArgs = LandingLayoutComponent & { + contentLength: "normal" | "long" | "thin"; + includeHeader: boolean; + includeFooter: boolean; +}; + +export default { + title: "Component Library/Landing Layout", + component: LandingLayoutComponent, + decorators: [ + moduleMetadata({ + imports: [ButtonModule], + providers: [ + { + provide: PlatformUtilsService, + useClass: MockPlatformUtilsService, + }, + ], + }), + ], + render: (args) => { + return { + props: args, + template: /*html*/ ` + + @if (includeHeader) { + +
+
+
Header Content
+
+
+
+ } + +
+ @switch (contentLength) { + @case ('thin') { +
+
Thin Content
+
+ } + @case ('long') { +
+
Long Content
+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
+
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
+
+ } + @default { +
+
Normal Content
+
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.
+
+ } + } +
+ + @if (includeFooter) { + +
+
Footer Content
+
+
+ } +
+ `, + }; + }, + + argTypes: { + hideBackgroundIllustration: { control: "boolean" }, + contentLength: { + control: "radio", + options: ["normal", "long", "thin"], + }, + includeHeader: { control: "boolean" }, + includeFooter: { control: "boolean" }, + }, + + args: { + hideBackgroundIllustration: false, + contentLength: "normal", + includeHeader: false, + includeFooter: false, + }, +} satisfies Meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + contentLength: "normal", + }, +}; + +export const WithHeader: Story = { + args: { + includeHeader: true, + }, +}; + +export const WithFooter: Story = { + args: { + includeFooter: true, + }, +}; + +export const WithHeaderAndFooter: Story = { + args: { + includeHeader: true, + includeFooter: true, + }, +}; + +export const LongContent: Story = { + args: { + contentLength: "long", + includeHeader: true, + includeFooter: true, + }, +}; + +export const ThinContent: Story = { + args: { + contentLength: "thin", + includeHeader: true, + includeFooter: true, + }, +}; + +export const NoBackgroundIllustration: Story = { + args: { + hideBackgroundIllustration: true, + includeHeader: true, + includeFooter: true, + }, +}; + +export const MinimalState: Story = { + args: { + contentLength: "thin", + hideBackgroundIllustration: true, + includeHeader: false, + includeFooter: false, + }, +}; From bc8c925cd000a6d4665455ff4c171be2aff5a54a Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:09:59 -0600 Subject: [PATCH 9/9] [PM-27486] Remove feature flag PM25174_DisableType0Decryption (#18413) --- .../browser/src/background/main.background.ts | 1 - .../src/popup/services/init.service.ts | 5 --- .../service-container/service-container.ts | 1 - apps/desktop/src/app/services/init.service.ts | 3 -- apps/web/src/app/core/init.service.ts | 3 -- libs/common/src/enums/feature-flag.enum.ts | 2 - .../crypto/abstractions/encrypt.service.ts | 8 ---- .../encrypt.service.implementation.ts | 39 +++---------------- .../crypto/services/encrypt.service.spec.ts | 37 ++++++++++-------- 9 files changed, 26 insertions(+), 73 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 9d551ec2622..58a7eb99ec6 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1565,7 +1565,6 @@ export default class MainBackground { await this.sdkLoadService.loadAndInit(); // Only the "true" background should run migrations await this.migrationRunner.run(); - this.encryptService.init(this.configService); // This is here instead of in the InitService b/c we don't plan for // side effects to run in the Browser InitService. diff --git a/apps/browser/src/popup/services/init.service.ts b/apps/browser/src/popup/services/init.service.ts index f16d82d0810..24ff637c29b 100644 --- a/apps/browser/src/popup/services/init.service.ts +++ b/apps/browser/src/popup/services/init.service.ts @@ -2,8 +2,6 @@ import { inject, Inject, Injectable, DOCUMENT } from "@angular/core"; import { AbstractThemingService } from "@bitwarden/angular/platform/services/theming/theming.service.abstraction"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; -import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService as LogServiceAbstraction } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -30,8 +28,6 @@ export class InitService { private sdkLoadService: SdkLoadService, private viewCacheService: PopupViewCacheService, private readonly migrationRunner: MigrationRunner, - private configService: ConfigService, - private encryptService: EncryptService, @Inject(DOCUMENT) private document: Document, ) {} @@ -43,7 +39,6 @@ export class InitService { this.twoFactorService.init(); await this.viewCacheService.init(); await this.sizeService.init(); - this.encryptService.init(this.configService); const htmlEl = window.document.documentElement; this.themingService.applyThemeChangesTo(this.document); diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index d98b5f0a861..bc3d3153b13 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -1058,7 +1058,6 @@ export class ServiceContainer { this.containerService.attachToGlobal(global); await this.i18nService.init(); this.twoFactorService.init(); - this.encryptService.init(this.configService); // If a user has a BW_SESSION key stored in their env (not process.env.BW_SESSION), // this should set the user key to unlock the vault on init. diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 17115825bf6..a6fd40cb998 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -8,7 +8,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; @@ -54,7 +53,6 @@ export class InitService { private autotypeService: DesktopAutotypeService, private sdkLoadService: SdkLoadService, private biometricMessageHandlerService: BiometricMessageHandlerService, - private configService: ConfigService, @Inject(DOCUMENT) private document: Document, private readonly migrationRunner: MigrationRunner, ) {} @@ -65,7 +63,6 @@ export class InitService { await this.sshAgentService.init(); this.nativeMessagingService.init(); await this.migrationRunner.waitForCompletion(); // Desktop will run migrations in the main process - this.encryptService.init(this.configService); const accounts = await firstValueFrom(this.accountService.accounts$); const setUserKeyInMemoryPromises = []; diff --git a/apps/web/src/app/core/init.service.ts b/apps/web/src/app/core/init.service.ts index 929f1489a61..9322d149e42 100644 --- a/apps/web/src/app/core/init.service.ts +++ b/apps/web/src/app/core/init.service.ts @@ -8,7 +8,6 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { DefaultVaultTimeoutService } from "@bitwarden/common/key-management/vault-timeout"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { IpcService } from "@bitwarden/common/platform/ipc"; @@ -40,7 +39,6 @@ export class InitService { private ipcService: IpcService, private sdkLoadService: SdkLoadService, private taskService: TaskService, - private configService: ConfigService, private readonly migrationRunner: MigrationRunner, @Inject(DOCUMENT) private document: Document, ) {} @@ -49,7 +47,6 @@ export class InitService { return async () => { await this.sdkLoadService.loadAndInit(); await this.migrationRunner.run(); - this.encryptService.init(this.configService); const activeAccount = await firstValueFrom(this.accountService.activeAccount$); if (activeAccount) { diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 811f4e524ac..a6b0de1e2e5 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -40,7 +40,6 @@ export enum FeatureFlag { PrivateKeyRegeneration = "pm-12241-private-key-regeneration", EnrollAeadOnKeyRotation = "enroll-aead-on-key-rotation", ForceUpdateKDFSettings = "pm-18021-force-update-kdf-settings", - PM25174_DisableType0Decryption = "pm-25174-disable-type-0-decryption", LinuxBiometricsV2 = "pm-26340-linux-biometrics-v2", NoLogoutOnKdfChange = "pm-23995-no-logout-on-kdf-change", DataRecoveryTool = "pm-28813-data-recovery-tool", @@ -150,7 +149,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.EnrollAeadOnKeyRotation]: FALSE, [FeatureFlag.ForceUpdateKDFSettings]: FALSE, - [FeatureFlag.PM25174_DisableType0Decryption]: FALSE, [FeatureFlag.LinuxBiometricsV2]: FALSE, [FeatureFlag.NoLogoutOnKdfChange]: FALSE, [FeatureFlag.DataRecoveryTool]: FALSE, diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index 25e5f949b40..87af3852116 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -1,16 +1,8 @@ -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { EncString } from "../models/enc-string"; export abstract class EncryptService { - /** - * A temporary init method to make the encrypt service listen to feature-flag changes. - * This will be removed once the feature flag has been rolled out. - */ - abstract init(configService: ConfigService): void; - /** * Encrypts a string to an EncString * @param plainValue - The value to encrypt diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index a5da0c82382..b14211b5b72 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -1,9 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service"; import { EncryptionType } from "@bitwarden/common/platform/enums"; @@ -15,28 +13,12 @@ import { PureCrypto } from "@bitwarden/sdk-internal"; import { EncryptService } from "../abstractions/encrypt.service"; export class EncryptServiceImplementation implements EncryptService { - private disableType0Decryption = false; - constructor( protected cryptoFunctionService: CryptoFunctionService, protected logService: LogService, protected logMacFailures: boolean, ) {} - init(configService: ConfigService): void { - configService.serverConfig$.subscribe((newConfig) => { - if (newConfig != null) { - this.setDisableType0Decryption( - newConfig.featureStates[FeatureFlag.PM25174_DisableType0Decryption] === true, - ); - } - }); - } - - setDisableType0Decryption(disable: boolean): void { - this.disableType0Decryption = disable; - } - async encryptString(plainValue: string, key: SymmetricCryptoKey): Promise { if (plainValue == null) { this.logService.warning( @@ -60,7 +42,7 @@ export class EncryptServiceImplementation implements EncryptService { } async decryptString(encString: EncString, key: SymmetricCryptoKey): Promise { - if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) { + if (encString.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } await SdkLoadService.Ready; @@ -68,7 +50,7 @@ export class EncryptServiceImplementation implements EncryptService { } async decryptBytes(encString: EncString, key: SymmetricCryptoKey): Promise { - if (this.disableType0Decryption && encString.encryptionType === EncryptionType.AesCbc256_B64) { + if (encString.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } await SdkLoadService.Ready; @@ -76,7 +58,7 @@ export class EncryptServiceImplementation implements EncryptService { } async decryptFileData(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise { - if (this.disableType0Decryption && encBuffer.encryptionType === EncryptionType.AesCbc256_B64) { + if (encBuffer.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } await SdkLoadService.Ready; @@ -148,10 +130,7 @@ export class EncryptServiceImplementation implements EncryptService { throw new Error("No wrappingKey provided for unwrapping."); } - if ( - this.disableType0Decryption && - wrappedDecapsulationKey.encryptionType === EncryptionType.AesCbc256_B64 - ) { + if (wrappedDecapsulationKey.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } @@ -171,10 +150,7 @@ export class EncryptServiceImplementation implements EncryptService { if (wrappingKey == null) { throw new Error("No wrappingKey provided for unwrapping."); } - if ( - this.disableType0Decryption && - wrappedEncapsulationKey.encryptionType === EncryptionType.AesCbc256_B64 - ) { + if (wrappedEncapsulationKey.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } @@ -194,10 +170,7 @@ export class EncryptServiceImplementation implements EncryptService { if (wrappingKey == null) { throw new Error("No wrappingKey provided for unwrapping."); } - if ( - this.disableType0Decryption && - keyToBeUnwrapped.encryptionType === EncryptionType.AesCbc256_B64 - ) { + if (keyToBeUnwrapped.encryptionType === EncryptionType.AesCbc256_B64) { throw new Error("Decryption of AesCbc256_B64 encrypted data is disabled."); } diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index 466f59da7c9..ac1f4d6ada0 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -163,7 +163,7 @@ describe("EncryptService", () => { describe("decryptString", () => { it("is a proxy to PureCrypto", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString("encrypted_string"); + const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "encrypted_string"); const result = await encryptService.decryptString(encString, key); expect(result).toEqual("decrypted_string"); expect(PureCrypto.symmetric_decrypt_string).toHaveBeenCalledWith( @@ -172,8 +172,7 @@ describe("EncryptService", () => { ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_string"); await expect(encryptService.decryptString(encString, key)).rejects.toThrow( @@ -185,7 +184,7 @@ describe("EncryptService", () => { describe("decryptBytes", () => { it("is a proxy to PureCrypto", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString("encrypted_bytes"); + const encString = new EncString(EncryptionType.AesCbc256_HmacSha256_B64, "encrypted_bytes"); const result = await encryptService.decryptBytes(encString, key); expect(result).toEqual(new Uint8Array(3)); expect(PureCrypto.symmetric_decrypt_bytes).toHaveBeenCalledWith( @@ -194,8 +193,7 @@ describe("EncryptService", () => { ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encString = new EncString(EncryptionType.AesCbc256_B64, "encrypted_bytes"); await expect(encryptService.decryptBytes(encString, key)).rejects.toThrow( @@ -216,8 +214,7 @@ describe("EncryptService", () => { ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encBuffer = EncArrayBuffer.fromParts( EncryptionType.AesCbc256_B64, @@ -234,7 +231,10 @@ describe("EncryptService", () => { describe("unwrapDecapsulationKey", () => { it("is a proxy to PureCrypto", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString("wrapped_decapsulation_key"); + const encString = new EncString( + EncryptionType.AesCbc256_HmacSha256_B64, + "wrapped_decapsulation_key", + ); const result = await encryptService.unwrapDecapsulationKey(encString, key); expect(result).toEqual(new Uint8Array(4)); expect(PureCrypto.unwrap_decapsulation_key).toHaveBeenCalledWith( @@ -242,8 +242,7 @@ describe("EncryptService", () => { key.toEncoded(), ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_decapsulation_key"); await expect(encryptService.unwrapDecapsulationKey(encString, key)).rejects.toThrow( @@ -267,7 +266,10 @@ describe("EncryptService", () => { describe("unwrapEncapsulationKey", () => { it("is a proxy to PureCrypto", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString("wrapped_encapsulation_key"); + const encString = new EncString( + EncryptionType.AesCbc256_HmacSha256_B64, + "wrapped_encapsulation_key", + ); const result = await encryptService.unwrapEncapsulationKey(encString, key); expect(result).toEqual(new Uint8Array(5)); expect(PureCrypto.unwrap_encapsulation_key).toHaveBeenCalledWith( @@ -275,8 +277,7 @@ describe("EncryptService", () => { key.toEncoded(), ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_encapsulation_key"); await expect(encryptService.unwrapEncapsulationKey(encString, key)).rejects.toThrow( @@ -300,7 +301,10 @@ describe("EncryptService", () => { describe("unwrapSymmetricKey", () => { it("is a proxy to PureCrypto", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); - const encString = new EncString("wrapped_symmetric_key"); + const encString = new EncString( + EncryptionType.AesCbc256_HmacSha256_B64, + "wrapped_symmetric_key", + ); const result = await encryptService.unwrapSymmetricKey(encString, key); expect(result).toEqual(new SymmetricCryptoKey(new Uint8Array(64))); expect(PureCrypto.unwrap_symmetric_key).toHaveBeenCalledWith( @@ -308,8 +312,7 @@ describe("EncryptService", () => { key.toEncoded(), ); }); - it("throws if disableType0Decryption is enabled and type is AesCbc256_B64", async () => { - encryptService.setDisableType0Decryption(true); + it("throws if type is AesCbc256_B64", async () => { const key = new SymmetricCryptoKey(makeStaticByteArray(64)); const encString = new EncString(EncryptionType.AesCbc256_B64, "wrapped_symmetric_key"); await expect(encryptService.unwrapSymmetricKey(encString, key)).rejects.toThrow(