From 886f85df48afb8e08e8682509e7910639bd9154f Mon Sep 17 00:00:00 2001 From: Vicki League Date: Wed, 19 Mar 2025 21:20:07 -0400 Subject: [PATCH 01/13] [CL-605] Fix whitespace between elements (#13644) --- .../components/approve-ssh-request.html | 4 +- .../members/members.component.html | 11 ++++-- .../webauthn-login-settings.component.html | 38 ++++++++++--------- .../exposed-passwords-report.component.html | 3 +- .../domain-add-edit-dialog.component.html | 38 ++++++++++--------- 5 files changed, 51 insertions(+), 43 deletions(-) diff --git a/apps/desktop/src/platform/components/approve-ssh-request.html b/apps/desktop/src/platform/components/approve-ssh-request.html index 952e3344e9c..b7005872f25 100644 --- a/apps/desktop/src/platform/components/approve-ssh-request.html +++ b/apps/desktop/src/platform/components/approve-ssh-request.html @@ -14,13 +14,13 @@ {{params.cipherName}} {{ "sshkeyApprovalMessageSuffix" | i18n }} {{ params.action | i18n }} -
+ -
+ diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index f2995e31f12..3856f6f4e28 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -187,7 +187,7 @@ class="tw-mr-3" >
-
+
@@ -196,22 +196,25 @@ class="tw-text-xs" variant="secondary" *ngIf="u.status === userStatusType.Invited" - >{{ "invited" | i18n }} + {{ "invited" | i18n }} + {{ "needsConfirmation" | i18n }} + {{ "needsConfirmation" | i18n }} + {{ "revoked" | i18n }} + {{ "revoked" | i18n }} +
{{ u.email }} diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html index 9965302d15a..7b1d859fb69 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/webauthn-login-settings.component.html @@ -1,24 +1,26 @@

{{ "logInWithPasskey" | i18n }} - - - {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }} - - - {{ - "on" | i18n - }} - {{ - "off" | i18n - }} + + + + {{ "off" | i18n }} - {{ "ssoLoginIsRequired" | i18n }} + + + + {{ "on" | i18n }} + + + {{ "off" | i18n }} + + - - {{ "beta" | i18n }} + {{ "beta" | i18n }} + diff --git a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html index 589704a409a..eb8e2c56527 100644 --- a/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html +++ b/apps/web/src/app/tools/reports/pages/exposed-passwords-report.component.html @@ -49,8 +49,9 @@ appStopClick (click)="selectCipher(row)" title="{{ 'editItemWithName' | i18n: row.name }}" - >{{ row.name }} + {{ row.name }} + {{ row.name }} diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html index 7226c957598..7df5953c56a 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-add-edit-dialog/domain-add-edit-dialog.component.html @@ -7,25 +7,27 @@ {{ "newDomain" | i18n }} + {{ ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n }} + + + + {{ data.orgDomain.domainName }} + + + {{ - ((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n - }} - - {{ - data.orgDomain.domainName - }} - - {{ - ((accountDeprovisioningEnabled$ | async) - ? "domainStatusUnderVerification" - : "domainStatusUnverified" - ) | i18n - }} - {{ - ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified") - | i18n - }} + ((accountDeprovisioningEnabled$ | async) + ? "domainStatusUnderVerification" + : "domainStatusUnverified" + ) | i18n + }} + + + {{ + ((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified") + | i18n + }} +
From 92587a1dd83913c323429245fa6ea1c6a6f6dc75 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 20 Mar 2025 02:20:55 +0100 Subject: [PATCH 02/13] [CL-611] Fix whitespaces for about dialog & emergency access (#13855) --- .../popup/settings/about-dialog/about-dialog.component.html | 4 ++-- .../settings/emergency-access/emergency-access.component.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html index b3bf06cfbe7..40dad4cde4b 100644 --- a/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html +++ b/apps/browser/src/tools/popup/settings/about-dialog/about-dialog.component.html @@ -42,12 +42,12 @@

-
+ -
+ diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html index 524ebbc28cf..ab93f0be3bc 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access.component.html @@ -52,7 +52,7 @@ [color]="c.avatarColor" size="small" > - + {{ c.email }} - + {{ c.email }} {{ "invited" | i18n From 57c15a26eb1cf035593df677420b677cd8ac3ecd Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Thu, 20 Mar 2025 11:17:46 +0100 Subject: [PATCH 03/13] [PM-18657] Fix clipboard tests calling console.warn (#13580) * Fix clipboard tests calling console.warn * Change to jest.SpyInstance --- .../offscreen-document.spec.ts | 21 ++++++++++++------- .../browser-clipboard.service.spec.ts | 5 ++--- .../browser-platform-utils.service.spec.ts | 8 +++++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 67fa920d18d..37731f17fbe 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -3,14 +3,19 @@ import { BrowserApi } from "../browser/browser-api"; import BrowserClipboardService from "../services/browser-clipboard.service"; describe("OffscreenDocument", () => { - const browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener"); - const browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy"); - const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); - const consoleErrorSpy = jest.spyOn(console, "error"); + let browserClipboardServiceCopySpy: jest.SpyInstance; + let browserClipboardServiceReadSpy: jest.SpyInstance; + let browserApiMessageListenerSpy: jest.SpyInstance; + let consoleErrorSpy: jest.SpyInstance; - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-require-imports - require("../offscreen-document/offscreen-document"); + beforeEach(async () => { + browserApiMessageListenerSpy = jest.spyOn(BrowserApi, "messageListener"); + browserClipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy"); + browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); + consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(); + + await import("./offscreen-document"); + }); describe("init", () => { it("sets up a `chrome.runtime.onMessage` listener", () => { @@ -47,6 +52,7 @@ describe("OffscreenDocument", () => { it("copies the message text", async () => { const text = "test"; + browserClipboardServiceCopySpy.mockResolvedValueOnce(undefined); sendMockExtensionMessage({ command: "offscreenCopyToClipboard", text }); await flushPromises(); @@ -56,6 +62,7 @@ describe("OffscreenDocument", () => { describe("handleOffscreenReadFromClipboard", () => { it("reads the value from the clipboard service", async () => { + browserClipboardServiceReadSpy.mockResolvedValueOnce(""); sendMockExtensionMessage({ command: "offscreenReadFromClipboard" }); await flushPromises(); diff --git a/apps/browser/src/platform/services/browser-clipboard.service.spec.ts b/apps/browser/src/platform/services/browser-clipboard.service.spec.ts index cf0d7c46004..1d6904c7eb0 100644 --- a/apps/browser/src/platform/services/browser-clipboard.service.spec.ts +++ b/apps/browser/src/platform/services/browser-clipboard.service.spec.ts @@ -2,9 +2,10 @@ import BrowserClipboardService from "./browser-clipboard.service"; describe("BrowserClipboardService", () => { let windowMock: any; - const consoleWarnSpy = jest.spyOn(console, "warn"); + let consoleWarnSpy: any; beforeEach(() => { + consoleWarnSpy = jest.spyOn(console, "warn").mockImplementation(); windowMock = { navigator: { clipboard: { @@ -104,8 +105,6 @@ describe("BrowserClipboardService", () => { }); await BrowserClipboardService.read(windowMock as Window); - - expect(consoleWarnSpy).toHaveBeenCalled(); }); }); }); diff --git a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts index fe049c4f1db..38166d10a08 100644 --- a/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts +++ b/apps/browser/src/platform/services/platform-utils/browser-platform-utils.service.spec.ts @@ -185,7 +185,9 @@ describe("Browser Utils Service", () => { describe("copyToClipboard", () => { const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp"); - const clipboardServiceCopySpy = jest.spyOn(BrowserClipboardService, "copy"); + const clipboardServiceCopySpy = jest + .spyOn(BrowserClipboardService, "copy") + .mockResolvedValue(undefined); let triggerOffscreenCopyToClipboardSpy: jest.SpyInstance; beforeEach(() => { @@ -281,7 +283,9 @@ describe("Browser Utils Service", () => { describe("readFromClipboard", () => { const getManifestVersionSpy = jest.spyOn(BrowserApi, "manifestVersion", "get"); const sendMessageToAppSpy = jest.spyOn(SafariApp, "sendMessageToApp"); - const clipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); + const clipboardServiceReadSpy = jest + .spyOn(BrowserClipboardService, "read") + .mockResolvedValue(""); beforeEach(() => { getManifestVersionSpy.mockReturnValue(2); From 45d5b171b857a61bf94b85aaf1a4afb7f03e3eb9 Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Thu, 20 Mar 2025 09:54:56 -0400 Subject: [PATCH 04/13] PM-19291 Pass relevant folder and vault data to drop down component within notification footer (#13901) * PM-19291 - Pass relevant data into dropdown component - Clean up some files - Pass all data into notificationBarIframeInitData using promise all * fix tests --- .../notification.background.spec.ts | 15 ++-- .../background/notification.background.ts | 69 ++++++++++++------- .../content/components/notification/body.ts | 4 +- .../components/notification/container.ts | 21 ++++-- .../abstractions/notification-bar.ts | 18 +++-- apps/browser/src/autofill/notification/bar.ts | 23 ++++++- .../browser/src/background/main.background.ts | 13 ++-- 7 files changed, 110 insertions(+), 53 deletions(-) diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index e02e3d8d951..d474e303336 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -1,6 +1,7 @@ import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject, firstValueFrom } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; @@ -59,6 +60,7 @@ describe("NotificationBackground", () => { const themeStateService = mock(); const configService = mock(); const accountService = mock(); + const organizationService = mock(); const activeAccountSubject = new BehaviorSubject<{ id: UserId } & AccountInfo>({ id: "testId" as UserId, @@ -73,18 +75,19 @@ describe("NotificationBackground", () => { authService.activeAccountStatus$ = activeAccountStatusMock$; accountService.activeAccount$ = activeAccountSubject; notificationBackground = new NotificationBackground( + accountService, + authService, autofillService, cipherService, - authService, - policyService, - folderService, - userNotificationSettingsService, + configService, domainSettingsService, environmentService, + folderService, logService, + organizationService, + policyService, themeStateService, - configService, - accountService, + userNotificationSettingsService, ); }); diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 11037e7e261..50e0ee0aa75 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { firstValueFrom } from "rxjs"; +import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; @@ -63,46 +64,48 @@ export default class NotificationBackground { ExtensionCommand.AutofillIdentity, ]); private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = { - unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), - bgGetFolderData: () => this.getFolderData(), - bgCloseNotificationBar: ({ message, sender }) => - this.handleCloseNotificationBarMessage(message, sender), + bgAddLogin: ({ message, sender }) => this.addLogin(message, sender), bgAdjustNotificationBar: ({ message, sender }) => this.handleAdjustNotificationBarMessage(message, sender), - bgAddLogin: ({ message, sender }) => this.addLogin(message, sender), bgChangedPassword: ({ message, sender }) => this.changedPassword(message, sender), - bgRemoveTabFromNotificationQueue: ({ sender }) => - this.removeTabFromNotificationQueue(sender.tab), - bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), - bgNeverSave: ({ sender }) => this.saveNever(sender.tab), - collectPageDetailsResponse: ({ message }) => - this.handleCollectPageDetailsResponseMessage(message), - bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), - checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), - bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), + bgCloseNotificationBar: ({ message, sender }) => + this.handleCloseNotificationBarMessage(message, sender), + bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(), + bgGetDecryptedCiphers: () => this.getNotificationCipherData(), bgGetEnableChangedPasswordPrompt: () => this.getEnableChangedPasswordPrompt(), bgGetEnableAddedLoginPrompt: () => this.getEnableAddedLoginPrompt(), bgGetExcludedDomains: () => this.getExcludedDomains(), - bgGetActiveUserServerConfig: () => this.getActiveUserServerConfig(), + bgGetFolderData: () => this.getFolderData(), + bgGetOrgData: () => this.getOrgData(), + bgNeverSave: ({ sender }) => this.saveNever(sender.tab), + bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab), + bgRemoveTabFromNotificationQueue: ({ sender }) => + this.removeTabFromNotificationQueue(sender.tab), + bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab), + bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender), + bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab), + checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab), + collectPageDetailsResponse: ({ message }) => + this.handleCollectPageDetailsResponseMessage(message), getWebVaultUrlForNotification: () => this.getWebVaultUrl(), notificationRefreshFlagValue: () => this.getNotificationFlag(), - bgGetDecryptedCiphers: () => this.getNotificationCipherData(), - bgOpenVault: ({ message, sender }) => this.openVault(message, sender.tab), + unlockCompleted: ({ message, sender }) => this.handleUnlockCompleted(message, sender), }; constructor( + private accountService: AccountService, + private authService: AuthService, private autofillService: AutofillService, private cipherService: CipherService, - private authService: AuthService, - private policyService: PolicyService, - private folderService: FolderService, - private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, + private configService: ConfigService, private domainSettingsService: DomainSettingsService, private environmentService: EnvironmentService, + private folderService: FolderService, private logService: LogService, + private organizationService: OrganizationService, + private policyService: PolicyService, private themeStateService: ThemeStateService, - private configService: ConfigService, - private accountService: AccountService, + private userNotificationSettingsService: UserNotificationSettingsServiceAbstraction, ) {} init() { @@ -744,6 +747,26 @@ export default class NotificationBackground { ); } + /** + * Returns the first value found from the organization service organizations$ observable. + */ + private async getOrgData() { + const activeUserId = await firstValueFrom( + this.accountService.activeAccount$.pipe(getOptionalUserId), + ); + const organizations = await firstValueFrom( + this.organizationService.organizations$(activeUserId), + ); + return organizations.map((org) => { + const { id, name, productTierType } = org; + return { + id, + name, + productTierType, + }; + }); + } + /** * Handles the unlockCompleted extension message. Will close the notification bar * after an attempted autofill action, and retry the autofill action if the message diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index 2433381dfba..66b580bde43 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -16,12 +16,12 @@ const { css } = createEmotion({ }); export function NotificationBody({ - ciphers, + ciphers = [], notificationType, theme = ThemeTypes.Light, handleEditOrUpdateAction, }: { - ciphers: NotificationCipherData[]; + ciphers?: NotificationCipherData[]; customClasses?: string[]; notificationType?: NotificationType; theme: Theme; diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index f98ef795749..8d80dc9fb50 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -9,6 +9,7 @@ import { NotificationType, } from "../../../notification/abstractions/notification-bar"; import { NotificationCipherData } from "../cipher/types"; +import { FolderView, OrgView } from "../common-types"; import { themes, spacing } from "../constants/styles"; import { NotificationBody, componentClassPrefix as notificationBodyClassPrefix } from "./body"; @@ -20,20 +21,24 @@ import { export function NotificationContainer({ handleCloseNotification, + handleEditOrUpdateAction, + handleSaveAction, + ciphers, + folders, i18n, + organizations, theme = ThemeTypes.Light, type, - ciphers, - handleSaveAction, - handleEditOrUpdateAction, }: NotificationBarIframeInitData & { handleCloseNotification: (e: Event) => void; handleSaveAction: (e: Event) => void; handleEditOrUpdateAction: (e: Event) => void; } & { + ciphers?: NotificationCipherData[]; + folders?: FolderView[]; i18n: { [key: string]: string }; + organizations?: OrgView[]; type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` - ciphers: NotificationCipherData[]; }) { const headerMessage = getHeaderMessage(i18n, type); const showBody = true; @@ -42,8 +47,8 @@ export function NotificationContainer({
${NotificationHeader({ handleCloseNotification, - standalone: showBody, message: headerMessage, + standalone: showBody, theme, })} ${showBody @@ -56,9 +61,11 @@ export function NotificationContainer({ : null} ${NotificationFooter({ handleSaveAction, - theme, - notificationType: type, + folders, i18n, + notificationType: type, + organizations, + theme, })}
`; diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index cb14a86dffa..6e7427e3a38 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -1,5 +1,8 @@ import { Theme } from "@bitwarden/common/platform/enums"; +import { NotificationCipherData } from "../../../autofill/content/components/cipher/types"; +import { FolderView, OrgView } from "../../../autofill/content/components/common-types"; + const NotificationTypes = { Add: "add", Change: "change", @@ -9,21 +12,24 @@ const NotificationTypes = { type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]; type NotificationBarIframeInitData = { - type?: string; // @TODO use `NotificationType` - isVaultLocked?: boolean; - theme?: Theme; - removeIndividualVault?: boolean; - importType?: string; applyRedesign?: boolean; + ciphers?: NotificationCipherData[]; + folders?: FolderView[]; + importType?: string; + isVaultLocked?: boolean; launchTimestamp?: number; + organizations?: OrgView[]; + removeIndividualVault?: boolean; + theme?: Theme; + type?: string; // @TODO use `NotificationType` }; type NotificationBarWindowMessage = { + cipherId?: string; command: string; error?: string; initData?: NotificationBarIframeInitData; username?: string; - cipherId?: string; }; type NotificationBarWindowMessageHandlers = { diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 617b1e58c14..d17c008372d 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -5,6 +5,8 @@ import { ConsoleLogService } from "@bitwarden/common/platform/services/console-l import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { AdjustNotificationBarMessageData } from "../background/abstractions/notification.background"; +import { NotificationCipherData } from "../content/components/cipher/types"; +import { OrgView } from "../content/components/common-types"; import { NotificationConfirmationContainer } from "../content/components/notification/confirmation-container"; import { NotificationContainer } from "../content/components/notification/container"; import { buildSvgDomElement } from "../utils"; @@ -115,7 +117,7 @@ function setElementText(template: HTMLTemplateElement, elementId: string, text: } } -function initNotificationBar(message: NotificationBarWindowMessage) { +async function initNotificationBar(message: NotificationBarWindowMessage) { const { initData } = message; if (!initData) { return; @@ -131,7 +133,23 @@ function initNotificationBar(message: NotificationBarWindowMessage) { // Current implementations utilize a require for scss files which creates the need to remove the node. document.head.querySelectorAll('link[rel="stylesheet"]').forEach((node) => node.remove()); - sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, (cipherData) => { + await Promise.all([ + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetOrgData" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetFolderData" }, resolve), + ), + new Promise((resolve) => + sendPlatformMessage({ command: "bgGetDecryptedCiphers" }, resolve), + ), + ]).then(([organizations, folders, ciphers]) => { + notificationBarIframeInitData = { + ...notificationBarIframeInitData, + folders, + ciphers, + organizations, + }; // @TODO use context to avoid prop drilling return render( NotificationContainer({ @@ -142,7 +160,6 @@ function initNotificationBar(message: NotificationBarWindowMessage) { handleSaveAction, handleEditOrUpdateAction, i18n, - ciphers: cipherData, }), document.body, ); diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index f8f86e6a277..74fa6acdf79 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -1173,18 +1173,19 @@ export default class MainBackground { () => this.generatePasswordToClipboard(), ); this.notificationBackground = new NotificationBackground( + this.accountService, + this.authService, this.autofillService, this.cipherService, - this.authService, - this.policyService, - this.folderService, - this.userNotificationSettingsService, + this.configService, this.domainSettingsService, this.environmentService, + this.folderService, this.logService, + this.organizationService, + this.policyService, this.themeStateService, - this.configService, - this.accountService, + this.userNotificationSettingsService, ); this.overlayNotificationsBackground = new OverlayNotificationsBackground( From 23fbb56248cde0a5d6f4d11e707d8baec617b204 Mon Sep 17 00:00:00 2001 From: Justin Baur <19896123+justindbaur@users.noreply.github.com> Date: Thu, 20 Mar 2025 10:56:30 -0400 Subject: [PATCH 05/13] Switch Notifications to only connect on unlocked (#13913) --- .../internal/default-notifications.service.spec.ts | 13 +++++++++---- .../internal/default-notifications.service.ts | 10 +++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts index e24069a9fbe..bf834e8dd93 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.spec.ts @@ -225,9 +225,10 @@ describe("NotificationsService", () => { }); it.each([ - { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked }, - { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked }, - { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked }, + // Temporarily rolling back notifications being connected while locked + // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Unlocked }, + // { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Locked }, + // { initialStatus: AuthenticationStatus.Locked, updatedStatus: AuthenticationStatus.Locked }, { initialStatus: AuthenticationStatus.Unlocked, updatedStatus: AuthenticationStatus.Unlocked }, ])( "does not re-connect when the user transitions from $initialStatus to $updatedStatus", @@ -252,7 +253,11 @@ describe("NotificationsService", () => { }, ); - it.each([AuthenticationStatus.Locked, AuthenticationStatus.Unlocked])( + it.each([ + // Temporarily disabling notifications connecting while in a locked state + // AuthenticationStatus.Locked, + AuthenticationStatus.Unlocked, + ])( "connects when a user transitions from logged out to %s", async (newStatus: AuthenticationStatus) => { emitActiveUser(mockUser1); diff --git a/libs/common/src/platform/notifications/internal/default-notifications.service.ts b/libs/common/src/platform/notifications/internal/default-notifications.service.ts index f0586e37ff7..fc505b018ce 100644 --- a/libs/common/src/platform/notifications/internal/default-notifications.service.ts +++ b/libs/common/src/platform/notifications/internal/default-notifications.service.ts @@ -123,13 +123,13 @@ export class DefaultNotificationsService implements NotificationsServiceAbstract ); } + // This method name is a lie currently as we also have an access token + // when locked, this is eventually where we want to be but it increases load + // on signalR so we are rolling back until we can move the load of browser to + // web push. private hasAccessToken$(userId: UserId) { return this.authService.authStatusFor$(userId).pipe( - map( - (authStatus) => - authStatus === AuthenticationStatus.Locked || - authStatus === AuthenticationStatus.Unlocked, - ), + map((authStatus) => authStatus === AuthenticationStatus.Unlocked), distinctUntilChanged(), ); } From bef0e0f5b7c45e345e408f053ff465df0baeb975 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Thu, 20 Mar 2025 14:58:59 +0000 Subject: [PATCH 06/13] Bumped client version(s) --- apps/browser/package.json | 2 +- apps/browser/src/manifest.json | 2 +- apps/browser/src/manifest.v3.json | 2 +- package-lock.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index e3bccf3f0df..5a8ddd03b41 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -1,6 +1,6 @@ { "name": "@bitwarden/browser", - "version": "2025.3.0", + "version": "2025.3.1", "scripts": { "build": "npm run build:chrome", "build:chrome": "cross-env BROWSER=chrome MANIFEST_VERSION=3 NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index 5bfca440b99..4510c2f342d 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.3.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 1e2ac1812ca..fc897c1b1c3 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -3,7 +3,7 @@ "minimum_chrome_version": "102.0", "name": "__MSG_extName__", "short_name": "__MSG_appName__", - "version": "2025.3.0", + "version": "2025.3.1", "description": "__MSG_extDesc__", "default_locale": "en", "author": "Bitwarden Inc.", diff --git a/package-lock.json b/package-lock.json index eaf5c0f24ed..cb51c157b09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -189,7 +189,7 @@ }, "apps/browser": { "name": "@bitwarden/browser", - "version": "2025.3.0" + "version": "2025.3.1" }, "apps/cli": { "name": "@bitwarden/cli", From e31ffd9b6603939e45b80af4a935c1e8485e51e3 Mon Sep 17 00:00:00 2001 From: Matt Andreko Date: Thu, 20 Mar 2025 12:29:24 -0400 Subject: [PATCH 07/13] Update SARIF upload to use proper branch (#13917) --- .github/workflows/build-web.yml | 2 ++ .github/workflows/scan.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index bd96b388c6a..3cc0df5103c 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -323,6 +323,8 @@ jobs: uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} - name: Log out of Docker run: docker logout diff --git a/.github/workflows/scan.yml b/.github/workflows/scan.yml index c5e189c4666..77b66ba8bf1 100644 --- a/.github/workflows/scan.yml +++ b/.github/workflows/scan.yml @@ -49,6 +49,8 @@ jobs: uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: cx_result.sarif + sha: ${{ contains(github.event_name, 'pull_request') && github.event.pull_request.head.sha || github.sha }} + ref: ${{ contains(github.event_name, 'pull_request') && format('refs/pull/{0}/head', github.event.pull_request.number) || github.ref }} quality: name: Quality scan From c999c19f07eb7014208035bdedd3c31ac5737904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Ch=C4=99ci=C5=84ski?= Date: Thu, 20 Mar 2025 17:38:51 +0100 Subject: [PATCH 08/13] fix(workflow): add conditional checks for Docker image scanning and result upload (#13898) --- .github/workflows/build-web.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build-web.yml b/.github/workflows/build-web.yml index 3cc0df5103c..e91fba2e87a 100644 --- a/.github/workflows/build-web.yml +++ b/.github/workflows/build-web.yml @@ -312,6 +312,7 @@ jobs: cosign sign --yes ${images} - name: Scan Docker image + if: ${{ needs.setup.outputs.has_secrets == 'true' }} id: container-scan uses: anchore/scan-action@869c549e657a088dc0441b08ce4fc0ecdac2bb65 # v5.3.0 with: @@ -320,6 +321,7 @@ jobs: output-format: sarif - name: Upload Grype results to GitHub + if: ${{ needs.setup.outputs.has_secrets == 'true' }} uses: github/codeql-action/upload-sarif@d68b2d4edb4189fd2a5366ac14e72027bd4b37dd # v3.28.2 with: sarif_file: ${{ steps.container-scan.outputs.sarif }} From bd0fedc5ce18fc412b6f621dff2a9199503cd2ed Mon Sep 17 00:00:00 2001 From: Alex Rosenfeld Date: Thu, 20 Mar 2025 13:53:17 -0400 Subject: [PATCH 09/13] [PM-18153] add support for importing some older / wonky card formats from msecure (#13328) * add support for importing some older / wonky card formats from msecure * slightly less fuzzy logic --------- Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> --- .../importers/msecure-csv-importer.spec.ts | 18 ++++++++ .../src/importers/msecure-csv-importer.ts | 45 ++++++++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/libs/importer/src/importers/msecure-csv-importer.spec.ts b/libs/importer/src/importers/msecure-csv-importer.spec.ts index 83e35802fac..3cf7cc713a8 100644 --- a/libs/importer/src/importers/msecure-csv-importer.spec.ts +++ b/libs/importer/src/importers/msecure-csv-importer.spec.ts @@ -8,6 +8,24 @@ describe("MSecureCsvImporter.parse", () => { importer = new MSecureCsvImporter(); }); + it("should correctly parse legacy formatted cards", async () => { + const mockCsvData = + `aWeirdOldStyleCard|1032,Credit Card,,Security code 1234,Card Number|12|5555 4444 3333 2222,Expiration Date|11|04/0029,Name on Card|9|Obi Wan Kenobi,Security Code|9|444,`.trim(); + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("aWeirdOldStyleCard"); + expect(cipher.type).toBe(CipherType.Card); + expect(cipher.card.number).toBe("5555 4444 3333 2222"); + expect(cipher.card.expiration).toBe("04 / 2029"); + expect(cipher.card.code).toBe("444"); + expect(cipher.card.cardholderName).toBe("Obi Wan Kenobi"); + expect(cipher.notes).toBe("Security code 1234"); + expect(cipher.card.brand).toBe(""); + }); + it("should correctly parse credit card entries as Secret Notes", async () => { const mockCsvData = `myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|,`.trim(); diff --git a/libs/importer/src/importers/msecure-csv-importer.ts b/libs/importer/src/importers/msecure-csv-importer.ts index 322764fa8dc..e78c715976f 100644 --- a/libs/importer/src/importers/msecure-csv-importer.ts +++ b/libs/importer/src/importers/msecure-csv-importer.ts @@ -43,23 +43,34 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { ).split("/"); cipher.card.expMonth = month.trim(); cipher.card.expYear = year.trim(); - cipher.card.code = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6])); - cipher.card.cardholderName = this.getValueOrDefault( - this.splitValueRetainingLastPart(value[7]), + const securityCodeRegex = RegExp("^Security Code\\|\\d*\\|"); + const securityCodeEntry = value.find((entry: string) => securityCodeRegex.test(entry)); + cipher.card.code = this.getValueOrDefault( + this.splitValueRetainingLastPart(securityCodeEntry), ); - cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9])); - cipher.notes = - this.getValueOrDefault(value[8].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[8]), "") + - "\n" + - this.getValueOrDefault(value[10].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[10]), "") + - "\n" + - this.getValueOrDefault(value[11].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[11]), ""); + + const cardNameRegex = RegExp("^Name on Card\\|\\d*\\|"); + const nameOnCardEntry = value.find((entry: string) => entry.match(cardNameRegex)); + cipher.card.cardholderName = this.getValueOrDefault( + this.splitValueRetainingLastPart(nameOnCardEntry), + ); + + cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]), ""); + + const noteRegex = RegExp("\\|\\d*\\|"); + const rawNotes = value + .slice(2) + .filter((entry: string) => !this.isNullOrWhitespace(entry) && !noteRegex.test(entry)); + const noteIndexes = [8, 10, 11]; + const indexedNotes = noteIndexes + .filter((idx) => value[idx] && noteRegex.test(value[idx])) + .map((idx) => value[idx]) + .map((val) => { + const key = val.split("|")[0]; + const value = this.getValueOrDefault(this.splitValueRetainingLastPart(val), ""); + return `${key}: ${value}`; + }); + cipher.notes = [...rawNotes, ...indexedNotes].join("\n"); } else if (value.length > 3) { cipher.type = CipherType.SecureNote; cipher.secureNote = new SecureNoteView(); @@ -95,6 +106,6 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { // like "Password|8|myPassword", we want to keep the "myPassword" but also ensure that if // the value contains any "|" it works fine private splitValueRetainingLastPart(value: string) { - return value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop(); + return value && value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop(); } } From cf827981af3abf9d12d88bdad214b5b9f09b6243 Mon Sep 17 00:00:00 2001 From: Jason Ng Date: Thu, 20 Mar 2025 15:16:18 -0400 Subject: [PATCH 10/13] [PM-19240] Do not show task unless Manage or Edit Permission (#13880) * do not show task for edit except pw --- .../view/emergency-view-dialog.component.spec.ts | 3 +++ libs/vault/src/cipher-view/cipher-view.component.ts | 11 +++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts index 0021d938f82..6e96b357e3e 100644 --- a/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts +++ b/apps/web/src/app/auth/settings/emergency-access/view/emergency-view-dialog.component.spec.ts @@ -13,6 +13,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl import { Utils } from "@bitwarden/common/platform/misc/utils"; import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; import { 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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; @@ -65,6 +66,7 @@ describe("EmergencyViewDialogComponent", () => { useValue: ChangeLoginPasswordService, }, { provide: ConfigService, useValue: ConfigService }, + { provide: CipherService, useValue: mock() }, ], }, add: { @@ -79,6 +81,7 @@ describe("EmergencyViewDialogComponent", () => { useValue: mock(), }, { provide: ConfigService, useValue: mock() }, + { provide: CipherService, useValue: mock() }, ], }, }) diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts index 1df96656da5..57c2b4dbae4 100644 --- a/libs/vault/src/cipher-view/cipher-view.component.ts +++ b/libs/vault/src/cipher-view/cipher-view.component.ts @@ -15,7 +15,8 @@ import { isCardExpired } from "@bitwarden/common/autofill/utils"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -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 { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; @@ -87,6 +88,7 @@ export class CipherViewComponent implements OnChanges, OnDestroy { private platformUtilsService: PlatformUtilsService, private changeLoginPasswordService: ChangeLoginPasswordService, private configService: ConfigService, + private cipherService: CipherService, ) {} async ngOnChanges() { @@ -152,7 +154,12 @@ export class CipherViewComponent implements OnChanges, OnDestroy { const userId = await firstValueFrom(this.activeUserId$); - if (this.cipher.edit && this.cipher.viewPassword) { + // Show Tasks for Manage and Edit permissions + // Using cipherService to see if user has access to cipher in a non-AC context to address with Edit Except Password permissions + const allCiphers = await firstValueFrom(this.cipherService.ciphers$(userId)); + const cipherServiceCipher = allCiphers[this.cipher?.id as CipherId]; + + if (cipherServiceCipher?.edit && cipherServiceCipher?.viewPassword) { await this.checkPendingChangePasswordTasks(userId); } From 85c71351fce911af516dd10557bcd62ed34d154f Mon Sep 17 00:00:00 2001 From: Daniel Riera Date: Thu, 20 Mar 2025 15:37:46 -0400 Subject: [PATCH 11/13] PM-19361 Notification bar dropdown folder component displays "No Folder" twice (#13924) * PM-19361 - Remove default folder option - Edit iFrame height * revert testing change --- .../content/components/notification/button-row.ts | 13 ++++--------- .../src/autofill/content/notification-bar.ts | 4 ++-- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/browser/src/autofill/content/components/notification/button-row.ts b/apps/browser/src/autofill/content/components/notification/button-row.ts index 1eb0a4ac5f4..8661f5957e1 100644 --- a/apps/browser/src/autofill/content/components/notification/button-row.ts +++ b/apps/browser/src/autofill/content/components/notification/button-row.ts @@ -60,23 +60,18 @@ export function NotificationButtonRow({ ) : ([] as Option[]); - const noFolderOption: Option = { - default: true, - icon: Folder, - text: "No folder", // @TODO localize - value: "0", - }; const folderOptions: Option[] = folders?.length - ? folders.reduce( + ? folders.reduce( (options, { id, name }: FolderView) => [ ...options, { icon: Folder, text: name, - value: id, + value: id === null ? "0" : id, + default: id === null, }, ], - [noFolderOption], + [], ) : []; diff --git a/apps/browser/src/autofill/content/notification-bar.ts b/apps/browser/src/autofill/content/notification-bar.ts index ae489ea956b..bf3d562a0ef 100644 --- a/apps/browser/src/autofill/content/notification-bar.ts +++ b/apps/browser/src/autofill/content/notification-bar.ts @@ -880,7 +880,7 @@ async function loadNotificationBar() { const baseStyle = useComponentBar ? isNotificationFresh - ? "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(100%); opacity:0;" + ? "height: calc(276px + 50px); width: 450px; right: 0; transform:translateX(100%); opacity:0;" : "height: calc(276px + 25px); width: 450px; right: 0; transform:translateX(0%); opacity:1;" : "height: 42px; width: 100%;"; @@ -910,7 +910,7 @@ async function loadNotificationBar() { function getFrameStyle(useComponentBar: boolean): string { return ( (useComponentBar - ? "height: calc(276px + 25px); width: 450px; right: 0;" + ? "height: calc(276px + 50px); width: 450px; right: 0;" : "height: 42px; width: 100%; left: 0;") + " top: 0; padding: 0; position: fixed;" + " z-index: 2147483647; visibility: visible;" From 79fd1b32639e2e89ce4648185e7675dcfbd246b4 Mon Sep 17 00:00:00 2001 From: Jeffrey Holland <124393578+jholland-livefront@users.noreply.github.com> Date: Thu, 20 Mar 2025 20:54:33 +0100 Subject: [PATCH 12/13] PM-17187 Autofill new card information in the popout (#13688) --- .../add-edit/add-edit-v2.component.ts | 26 +++++++++++++++++ .../cipher-form-config.service.ts | 5 ++++ .../card-details-section.component.spec.ts | 6 ++++ .../card-details-section.component.ts | 28 ++++++++++++++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index b7ff0718b35..f986bdfca31 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -440,6 +440,32 @@ const mapAddEditCipherInfoToInitialValues = ( initialValues.name = cipher.name; } + if (cipher.type === CipherType.Card) { + const card = cipher.card; + + if (card != null) { + if (card.cardholderName != null) { + initialValues.cardholderName = card.cardholderName; + } + + if (card.number != null) { + initialValues.number = card.number; + } + + if (card.expMonth != null) { + initialValues.expMonth = card.expMonth; + } + + if (card.expYear != null) { + initialValues.expYear = card.expYear; + } + + if (card.code != null) { + initialValues.code = card.code; + } + } + } + if (cipher.type === CipherType.Login) { const login = cipher.login; diff --git a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts index 3fc473c4465..8a16050804b 100644 --- a/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts +++ b/libs/vault/src/cipher-form/abstractions/cipher-form-config.service.ts @@ -25,6 +25,11 @@ export type OptionalInitialValues = { username?: string; password?: string; name?: string; + cardholderName?: string; + number?: string; + expMonth?: string; + expYear?: string; + code?: string; }; /** diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts index 39a59192985..32baad189cf 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.spec.ts @@ -65,6 +65,8 @@ describe("CardDetailsSectionComponent", () => { cardView.cardholderName = "Ron Burgundy"; cardView.number = "4242 4242 4242 4242"; cardView.brand = "Visa"; + cardView.expMonth = ""; + cardView.code = ""; expect(patchCipherSpy).toHaveBeenCalled(); const patchFn = patchCipherSpy.mock.lastCall[0]; @@ -79,6 +81,10 @@ describe("CardDetailsSectionComponent", () => { }); const cardView = new CardView(); + cardView.cardholderName = ""; + cardView.number = ""; + cardView.expMonth = ""; + cardView.code = ""; cardView.expYear = "2022"; expect(patchCipherSpy).toHaveBeenCalled(); diff --git a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts index c2b3ebb59aa..cb00c7d24f5 100644 --- a/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts +++ b/libs/vault/src/cipher-form/components/card-details-section/card-details-section.component.ts @@ -97,6 +97,10 @@ export class CardDetailsSectionComponent implements OnInit { EventType = EventType; + get initialValues() { + return this.cipherFormContainer.config.initialValues; + } + constructor( private cipherFormContainer: CipherFormContainer, private formBuilder: FormBuilder, @@ -139,7 +143,9 @@ export class CardDetailsSectionComponent implements OnInit { const prefillCipher = this.cipherFormContainer.getInitialCipherView(); if (prefillCipher) { - this.setInitialValues(prefillCipher); + this.initFromExistingCipher(prefillCipher.card); + } else { + this.initNewCipher(); } if (this.disabled) { @@ -147,6 +153,26 @@ export class CardDetailsSectionComponent implements OnInit { } } + private initFromExistingCipher(existingCard: CardView) { + this.cardDetailsForm.patchValue({ + cardholderName: this.initialValues?.cardholderName ?? existingCard.cardholderName, + number: this.initialValues?.number ?? existingCard.number, + expMonth: this.initialValues?.expMonth ?? existingCard.expMonth, + expYear: this.initialValues?.expYear ?? existingCard.expYear, + code: this.initialValues?.code ?? existingCard.code, + }); + } + + private initNewCipher() { + this.cardDetailsForm.patchValue({ + cardholderName: this.initialValues?.cardholderName || "", + number: this.initialValues?.number || "", + expMonth: this.initialValues?.expMonth || "", + expYear: this.initialValues?.expYear || "", + code: this.initialValues?.code || "", + }); + } + /** Get the section heading based on the card brand */ getSectionHeading(): string { const { brand } = this.cardDetailsForm.value; From 87847dc8067462418acdb29f9b56bbe5444f5b12 Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:23:19 -0500 Subject: [PATCH 13/13] fix: check device id and creationDate for falsey values This commit adds validation to check for falsey values in device 'id' and 'creationDate' fields in the device management component. This prevents potential issues when these string values are empty or otherwise evaluate to false. Resolves PM-18757 --- .../app/auth/settings/security/device-management.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/auth/settings/security/device-management.component.ts b/apps/web/src/app/auth/settings/security/device-management.component.ts index 0f31b8d4639..631ab02db7d 100644 --- a/apps/web/src/app/auth/settings/security/device-management.component.ts +++ b/apps/web/src/app/auth/settings/security/device-management.component.ts @@ -180,7 +180,7 @@ export class DeviceManagementComponent { private updateDeviceTable(devices: Array): void { this.dataSource.data = devices .map((device: DeviceView): DeviceTableData | null => { - if (device.id == undefined) { + if (!device.id) { this.validationService.showError(new Error(this.i18nService.t("deviceIdMissing"))); return null; } @@ -190,7 +190,7 @@ export class DeviceManagementComponent { return null; } - if (device.creationDate == undefined) { + if (!device.creationDate) { this.validationService.showError( new Error(this.i18nService.t("deviceCreationDateMissing")), );