From 6ad35e08713c052ee8fc3c28870875bce8c2efa3 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:16:34 -0600 Subject: [PATCH 1/3] Vault Refactor: Clean up some strict types (#12357) * update cipher-view to account for strict type checking * update view-identity-sections to account for strict type checking * update read-only-cipher-card to account for strict type checking * remove unused card import * remove unused card import * update additional-options to account for strict type checking --- .../additional-options.component.ts | 4 +-- .../src/cipher-view/cipher-view.component.ts | 28 +++++++++++++------ .../login-credentials-view.component.ts | 2 -- .../read-only-cipher-card.component.ts | 10 +++---- .../view-identity-sections.component.ts | 28 +++++++++++++------ 5 files changed, 46 insertions(+), 26 deletions(-) diff --git a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts index d590fbf3d50..9f8f034d59b 100644 --- a/libs/vault/src/cipher-view/additional-options/additional-options.component.ts +++ b/libs/vault/src/cipher-view/additional-options/additional-options.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; @@ -31,5 +29,5 @@ import { ], }) export class AdditionalOptionsComponent { - @Input() notes: string; + @Input() notes: string = ""; } diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index d525b88d924..57af96258fa 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; import { firstValueFrom, Observable, Subject, takeUntil } from "rxjs"; @@ -48,19 +46,19 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide ], }) export class CipherViewComponent implements OnChanges, OnDestroy { - @Input({ required: true }) cipher: CipherView; + @Input({ required: true }) cipher: CipherView | null = null; /** * Optional list of collections the cipher is assigned to. If none are provided, they will be fetched using the * `CipherService` and the `collectionIds` property of the cipher. */ - @Input() collections: CollectionView[]; + @Input() collections?: CollectionView[]; /** Should be set to true when the component is used within the Admin Console */ @Input() isAdminConsole?: boolean = false; - organization$: Observable; - folder$: Observable; + organization$: Observable | undefined; + folder$: Observable | undefined; private destroyed$: Subject = new Subject(); cardIsExpired: boolean = false; @@ -86,24 +84,38 @@ export class CipherViewComponent implements OnChanges, OnDestroy { } get hasCard() { + if (!this.cipher) { + return false; + } + const { cardholderName, code, expMonth, expYear, number } = this.cipher.card; return cardholderName || code || expMonth || expYear || number; } get hasLogin() { + if (!this.cipher) { + return false; + } + const { username, password, totp } = this.cipher.login; return username || password || totp; } get hasAutofill() { - return this.cipher.login?.uris.length > 0; + const uris = this.cipher?.login?.uris.length ?? 0; + + return uris > 0; } get hasSshKey() { - return this.cipher.sshKey?.privateKey; + return !!this.cipher?.sshKey?.privateKey; } async loadCipherData() { + if (!this.cipher) { + return; + } + // Load collections if not provided and the cipher has collectionIds if ( this.cipher.collectionIds && diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index a4faa663bf4..0c42c2ddda7 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -11,7 +11,6 @@ import { EventType } from "@bitwarden/common/enums"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { - CardComponent, FormFieldModule, SectionComponent, SectionHeaderComponent, @@ -37,7 +36,6 @@ type TotpCodeValues = { imports: [ CommonModule, JslibModule, - CardComponent, SectionComponent, SectionHeaderComponent, TypographyModule, diff --git a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts index 73fb570f564..9005ea9674c 100644 --- a/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts +++ b/libs/vault/src/cipher-view/read-only-cipher-card/read-only-cipher-card.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { AfterViewInit, Component, ContentChildren, QueryList } from "@angular/core"; import { CardComponent, BitFormFieldComponent } from "@bitwarden/components"; @@ -14,14 +12,16 @@ import { CardComponent, BitFormFieldComponent } from "@bitwarden/components"; * A thin wrapper around the `bit-card` component that disables the bottom border for the last form field. */ export class ReadOnlyCipherCardComponent implements AfterViewInit { - @ContentChildren(BitFormFieldComponent) formFields: QueryList; + @ContentChildren(BitFormFieldComponent) formFields?: QueryList; ngAfterViewInit(): void { // Disable the bottom border for the last form field - if (this.formFields.last) { + if (this.formFields?.last) { // Delay model update until next change detection cycle setTimeout(() => { - this.formFields.last.disableReadOnlyBorder = true; + if (this.formFields) { + this.formFields.last.disableReadOnlyBorder = true; + } }); } } diff --git a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts index bb9b8d0a9a0..fdd5bb66a55 100644 --- a/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts +++ b/libs/vault/src/cipher-view/view-identity-sections/view-identity-sections.component.ts @@ -1,12 +1,9 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { NgIf } from "@angular/common"; import { Component, Input, OnInit } from "@angular/core"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { - CardComponent, FormFieldModule, IconButtonModule, SectionComponent, @@ -23,7 +20,6 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- imports: [ NgIf, JslibModule, - CardComponent, SectionComponent, SectionHeaderComponent, TypographyModule, @@ -33,11 +29,11 @@ import { ReadOnlyCipherCardComponent } from "../read-only-cipher-card/read-only- ], }) export class ViewIdentitySectionsComponent implements OnInit { - @Input() cipher: CipherView; + @Input({ required: true }) cipher: CipherView | null = null; - showPersonalDetails: boolean; - showIdentificationDetails: boolean; - showContactDetails: boolean; + showPersonalDetails: boolean = false; + showIdentificationDetails: boolean = false; + showContactDetails: boolean = false; ngOnInit(): void { this.showPersonalDetails = this.hasPersonalDetails(); @@ -47,6 +43,10 @@ export class ViewIdentitySectionsComponent implements OnInit { /** Returns all populated address fields */ get addressFields(): string { + if (!this.cipher) { + return ""; + } + const { address1, address2, address3, fullAddressPart2, country } = this.cipher.identity; return [address1, address2, address3, fullAddressPart2, country].filter(Boolean).join("\n"); } @@ -58,18 +58,30 @@ export class ViewIdentitySectionsComponent implements OnInit { /** Returns true when any of the "personal detail" attributes are populated */ private hasPersonalDetails(): boolean { + if (!this.cipher) { + return false; + } + const { username, company, fullName } = this.cipher.identity; return Boolean(fullName || username || company); } /** Returns true when any of the "identification detail" attributes are populated */ private hasIdentificationDetails(): boolean { + if (!this.cipher) { + return false; + } + const { ssn, passportNumber, licenseNumber } = this.cipher.identity; return Boolean(ssn || passportNumber || licenseNumber); } /** Returns true when any of the "contact detail" attributes are populated */ private hasContactDetails(): boolean { + if (!this.cipher) { + return false; + } + const { email, phone } = this.cipher.identity; return Boolean(email || phone || this.addressFields); From cc06e1eff3dd615f2bac27b57e20332e051ca0e5 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Fri, 20 Dec 2024 11:33:43 -0500 Subject: [PATCH 2/3] enhancement: UI for multiple totp elements (#12404) * enhancement: UI for multiple totp elements * add tests * update snapshots * update obsolete snapshots * Update apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts Co-authored-by: Jonathan Prusik --------- Co-authored-by: Jonathan Prusik --- .../autofill-inline-menu-list.spec.ts.snap | 248 ++++++++++++++++++ .../list/autofill-inline-menu-list.spec.ts | 41 +++ .../pages/list/autofill-inline-menu-list.ts | 32 ++- 3 files changed, 313 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap index 785cadb5510..3b8458ec2ab 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/__snapshots__/autofill-inline-menu-list.spec.ts.snap @@ -2813,6 +2813,254 @@ exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers f `; +exports[`AutofillInlineMenuList initAutofillInlineMenuList the list of ciphers for an authenticated user renders correctly when there are multiple TOTP elements with username displayed 1`] = ` +
+
    +
  • +
    + + +
    +
  • +
  • +
    + + +
    +
  • +
+
+`; + exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
{ expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); }); + it("renders correctly when there are multiple TOTP elements with username displayed", async () => { + const totpCipher1 = createAutofillOverlayCipherDataMock(1, { + type: CipherType.Login, + login: { + totp: "123456", + totpField: true, + username: "user1", + }, + }); + + const totpCipher2 = createAutofillOverlayCipherDataMock(2, { + type: CipherType.Login, + login: { + totp: "654321", + totpField: true, + username: "user2", + }, + }); + + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + inlineMenuFillType: CipherType.Login, + ciphers: [totpCipher1, totpCipher2], + }), + ); + + await flushPromises(); + const checkSubtitleElement = (username: string) => { + const subtitleElement = autofillInlineMenuList["inlineMenuListContainer"].querySelector( + `span.cipher-subtitle[title="${username}"]`, + ); + expect(subtitleElement).not.toBeNull(); + expect(subtitleElement.textContent).toBe(username); + }; + + checkSubtitleElement("user1"); + checkSubtitleElement("user2"); + + expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); + }); + it("creates the view for a totp field", () => { postWindowMessage( createInitAutofillInlineMenuListMessageMock({ diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index 6cf390d0a29..b7837505d41 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -1163,7 +1163,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { } if (cipher.login?.totpField && cipher.login?.totp) { - return this.buildTotpElement(cipher.login?.totp); + return this.buildTotpElement(cipher.login?.totp, cipher.login?.username); } const subTitleText = this.getSubTitleText(cipher); const cipherSubtitleElement = this.buildCipherSubtitleElement(subTitleText); @@ -1174,13 +1174,24 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return cipherDetailsElement; } + /** + * Checks if there is more than one TOTP element being displayed. + * + * @returns {boolean} - Returns true if more than one TOTP element is displayed, otherwise false. + */ + private multipleTotpElements(): boolean { + return ( + this.ciphers.filter((cipher) => cipher.login?.totpField && cipher.login?.totp).length > 1 + ); + } + /** * Builds a TOTP element for a given TOTP code. * * @param totp - The TOTP code to display. */ - private buildTotpElement(totpCode: string): HTMLDivElement | null { + private buildTotpElement(totpCode: string, username?: string): HTMLDivElement | null { if (!totpCode) { return null; } @@ -1196,12 +1207,17 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { containerElement.appendChild(totpHeading); - const subtitleElement = document.createElement("span"); - subtitleElement.classList.add("cipher-subtitle"); - subtitleElement.textContent = formattedTotpCode; - subtitleElement.setAttribute("aria-label", this.getTranslation("totpCodeAria")); - subtitleElement.setAttribute("data-testid", "totp-code"); - containerElement.appendChild(subtitleElement); + if (this.multipleTotpElements() && username) { + const usernameSubtitle = this.buildCipherSubtitleElement(username); + containerElement.appendChild(usernameSubtitle); + } + + const totpCodeSpan = document.createElement("span"); + totpCodeSpan.classList.add("cipher-subtitle"); + totpCodeSpan.textContent = formattedTotpCode; + totpCodeSpan.setAttribute("aria-label", this.getTranslation("totpCodeAria")); + totpCodeSpan.setAttribute("data-testid", "totp-code"); + containerElement.appendChild(totpCodeSpan); return containerElement; } From 2e6031eee90631274864f9b9ba65e935132ed32f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 20 Dec 2024 17:36:04 +0100 Subject: [PATCH 3/3] Increase heap size for builds (#12483) * Increase heap size for builds * Add cross-env to cli * Add cross-env to desktop * Update apps/web/package.json Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- apps/browser/package.json | 10 +++++----- apps/cli/package.json | 4 ++-- apps/desktop/package.json | 2 +- apps/web/package.json | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 2b3edd96b6e..7c839ecc3d9 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -14,11 +14,11 @@ "build:watch:firefox": "npm run build:firefox -- --watch", "build:watch:opera": "npm run build:opera -- --watch", "build:watch:safari": "npm run build:safari -- --watch", - "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:chrome", - "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:edge", - "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:firefox", - "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:opera", - "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:safari", + "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:chrome", + "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:edge", + "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:firefox", + "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:opera", + "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:safari", "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.ps1 dist-chrome.zip", "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.ps1 dist-edge.zip", "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.ps1 dist-firefox.zip", diff --git a/apps/cli/package.json b/apps/cli/package.json index b7d54e78e1d..b25f327c42a 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -18,14 +18,14 @@ "license": "SEE LICENSE IN LICENSE.txt", "scripts": { "clean": "rimraf dist", - "build:oss": "webpack", + "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:oss:debug": "npm run build:oss && node --inspect ./build/bw.js", "build:oss:watch": "webpack --watch", "build:oss:prod": "cross-env NODE_ENV=production webpack", "build:oss:prod:watch": "cross-env NODE_ENV=production webpack --watch", "debug": "node --inspect ./build/bw.js", "publish:npm": "npm run build:oss:prod && npm publish --access public", - "build:bit": "webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", + "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:debug": "npm run build:bit && node --inspect ./build/bw.js", "build:bit:watch": "webpack --watch -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:prod": "cross-env NODE_ENV=production npm run build:bit", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 10d5ded3448..7c6d1f37692 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -19,7 +19,7 @@ "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", "build-native": "cd desktop_native && node build.js", - "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", + "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", diff --git a/apps/web/package.json b/apps/web/package.json index dee8e7722c9..f2697e1b68d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,8 +2,8 @@ "name": "@bitwarden/web-vault", "version": "2024.12.1", "scripts": { - "build:oss": "webpack", - "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", "build:oss:watch": "webpack serve", "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", "build:bit:dev": "cross-env ENV=development npm run build:bit",