diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index 52720b1f9f5..912d9657124 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -35,7 +35,7 @@ interface NotificationQueueMessage { } type ChangePasswordNotificationData = { - cipherId: CipherView["id"]; + cipherIds: CipherView["id"][]; newPassword: string; }; diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 032baf2e32b..507789ae7a4 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -289,7 +289,6 @@ describe("NotificationBackground", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; let getEnableAddedLoginPromptSpy: jest.SpyInstance; - let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let pushAddLoginToQueueSpy: jest.SpyInstance; let pushChangePasswordToQueueSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; @@ -306,10 +305,7 @@ describe("NotificationBackground", () => { notificationBackground as any, "getEnableAddedLoginPrompt", ); - getEnableChangedPasswordPromptSpy = jest.spyOn( - notificationBackground as any, - "getEnableChangedPasswordPrompt", - ); + pushAddLoginToQueueSpy = jest.spyOn(notificationBackground as any, "pushAddLoginToQueue"); pushChangePasswordToQueueSpy = jest.spyOn( notificationBackground as any, @@ -368,24 +364,6 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("skips attempting to change the password for an existing login if the user has disabled changing the password notification", async () => { - const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getEnableAddedLoginPromptSpy.mockReturnValueOnce(true); - getEnableChangedPasswordPromptSpy.mockReturnValueOnce(false); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock({ login: { username: "test", password: "oldPassword" } }), - ]); - - await notificationBackground.triggerAddLoginNotification(data, tab); - - expect(getEnableAddedLoginPromptSpy).toHaveBeenCalled(); - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(getEnableChangedPasswordPromptSpy).toHaveBeenCalled(); - expect(pushAddLoginToQueueSpy).not.toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); - }); - it("skips attempting to change the password for an existing login if the password has not changed", async () => { const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); @@ -445,37 +423,12 @@ describe("NotificationBackground", () => { sender.tab, ); }); - - it("adds a change password message to the queue if the user has changed an existing cipher's password", async () => { - const data: ModifyLoginCipherFormData = { - ...mockModifyLoginCipherFormData, - username: "tEsT", - }; - - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getEnableAddedLoginPromptSpy.mockResolvedValueOnce(true); - getEnableChangedPasswordPromptSpy.mockResolvedValueOnce(true); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock({ - id: "cipher-id", - login: { username: "test", password: "oldPassword" }, - }), - ]); - - await notificationBackground.triggerAddLoginNotification(data, tab); - - expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", - "example.com", - data.password, - sender.tab, - ); - }); }); describe("bgTriggerChangedPasswordNotification message handler", () => { let tab: chrome.tabs.Tab; let sender: chrome.runtime.MessageSender; + let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let pushChangePasswordToQueueSpy: jest.SpyInstance; let getAllDecryptedForUrlSpy: jest.SpyInstance; const mockModifyLoginCipherFormData: ModifyLoginCipherFormData = { @@ -488,6 +441,11 @@ describe("NotificationBackground", () => { beforeEach(() => { tab = createChromeTabMock(); sender = mock({ tab }); + getEnableChangedPasswordPromptSpy = jest.spyOn( + notificationBackground as any, + "getEnableChangedPasswordPrompt", + ); + pushChangePasswordToQueueSpy = jest.spyOn( notificationBackground as any, "pushChangePasswordToQueue", @@ -495,6 +453,40 @@ describe("NotificationBackground", () => { getAllDecryptedForUrlSpy = jest.spyOn(cipherService, "getAllDecryptedForUrl"); }); + afterEach(() => { + getEnableChangedPasswordPromptSpy.mockRestore(); + pushChangePasswordToQueueSpy.mockRestore(); + getAllDecryptedForUrlSpy.mockRestore(); + }); + + it("skips attempting to change the password for an existing login if the user has disabled changing the password notification", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getEnableChangedPasswordPromptSpy.mockReturnValueOnce(false); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock({ login: { username: "test", password: "oldPassword" } }), + ]); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + }); + + it("skips attempting to add the change password message to the queue if the user is logged out", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + }; + + activeAccountStatusMock$.next(AuthenticationStatus.LoggedOut); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + }); + it("skips attempting to add the change password message to the queue if the passed url is not valid", async () => { const data: ModifyLoginCipherFormData = mockModifyLoginCipherFormData; @@ -503,7 +495,92 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("adds a change password message to the queue if the user does not have an unlocked account", async () => { + it("only only includes ciphers in notification data matching a username if username was present in the modify form data", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + username: "userName", + }; + + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock({ + id: "cipher-id-1", + login: { username: "test", password: "currentPassword" }, + }), + mock({ + id: "cipher-id-2", + login: { username: "username", password: "currentPassword" }, + }), + mock({ + id: "cipher-id-3", + login: { username: "uSeRnAmE", password: "currentPassword" }, + }), + ]); + + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-2", "cipher-id-3"], + "example.com", + data?.newPassword, + sender.tab, + ); + }); + + it("adds a change password message to the queue with current password, if there is a current password, but no new password", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + password: "newPasswordUpdatedElsewhere", + newPassword: null, + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock({ + id: "cipher-id-1", + login: { password: "currentPassword" }, + }), + ]); + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1"], + "example.com", + data?.password, + sender.tab, + ); + }); + + it("adds a change password message to the queue with new password, if new password is provided", async () => { + const data: ModifyLoginCipherFormData = { + ...mockModifyLoginCipherFormData, + uri: "https://example.com", + password: "password2", + newPassword: "password3", + }; + activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); + getAllDecryptedForUrlSpy.mockResolvedValueOnce([ + mock({ + id: "cipher-id-1", + login: { password: "password1" }, + }), + mock({ + id: "cipher-id-4", + login: { password: "password4" }, + }), + ]); + await notificationBackground.triggerChangedPasswordNotification(data, tab); + + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1", "cipher-id-4"], + "example.com", + data?.newPassword, + sender.tab, + ); + }); + + it("adds a change password message to the queue if the user has a locked account", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", @@ -522,10 +599,12 @@ describe("NotificationBackground", () => { ); }); - it("skips adding a change password message to the queue if the multiple ciphers exist for the passed URL and the current password is not found within the list of ciphers", async () => { + it("doesn't add a password if there is no current or new password", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", + password: null, + newPassword: null, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getAllDecryptedForUrlSpy.mockResolvedValueOnce([ @@ -537,23 +616,6 @@ describe("NotificationBackground", () => { expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); }); - it("skips adding a change password message if more than one existing cipher is found with a matching password ", async () => { - const data: ModifyLoginCipherFormData = { - ...mockModifyLoginCipherFormData, - uri: "https://example.com", - }; - activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); - getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock({ login: { username: "test", password: "password" } }), - mock({ login: { username: "test2", password: "password" } }), - ]); - - await notificationBackground.triggerChangedPasswordNotification(data, tab); - - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); - }); - it("adds a change password message to the queue if a single cipher matches the passed current password", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, @@ -570,28 +632,39 @@ describe("NotificationBackground", () => { await notificationBackground.triggerChangedPasswordNotification(data, tab); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", + ["cipher-id"], "example.com", data?.newPassword, sender.tab, ); }); - it("skips adding a change password message if no current password is passed in the message and more than one cipher is found for a url", async () => { + it("adds a change password message with all matching ciphers if no current password is passed and more than one cipher is found for a url", async () => { const data: ModifyLoginCipherFormData = { ...mockModifyLoginCipherFormData, uri: "https://example.com", + password: null, }; activeAccountStatusMock$.next(AuthenticationStatus.Unlocked); getAllDecryptedForUrlSpy.mockResolvedValueOnce([ - mock({ login: { username: "test", password: "password" } }), - mock({ login: { username: "test2", password: "password" } }), + mock({ + id: "cipher-id-1", + login: { username: "test", password: "password" }, + }), + mock({ + id: "cipher-id-2", + login: { username: "test2", password: "password" }, + }), ]); await notificationBackground.triggerChangedPasswordNotification(data, tab); - expect(getAllDecryptedForUrlSpy).toHaveBeenCalled(); - expect(pushChangePasswordToQueueSpy).not.toHaveBeenCalled(); + expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( + ["cipher-id-1", "cipher-id-2"], + "example.com", + data?.newPassword, + sender.tab, + ); }); it("adds a change password message to the queue if no current password is passed with the message, but a single cipher is matched for the uri", async () => { @@ -611,7 +684,7 @@ describe("NotificationBackground", () => { await notificationBackground.triggerChangedPasswordNotification(data, tab); expect(pushChangePasswordToQueueSpy).toHaveBeenCalledWith( - "cipher-id", + ["cipher-id"], "example.com", data?.newPassword, sender.tab, diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index d44bf2f1507..5bfb12f1932 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -213,14 +213,26 @@ export default class NotificationBackground { let cipherView: CipherView; if (cipherQueueMessage.type === NotificationType.ChangePassword) { const { - data: { cipherId }, + data: { cipherIds }, } = cipherQueueMessage; - cipherView = await this.getDecryptedCipherById(cipherId, activeUserId); + const cipherViews = await this.cipherService.getAllDecrypted(activeUserId); + return cipherViews + .filter((cipher) => cipherIds.includes(cipher.id)) + .map((cipherView) => { + const organizationType = getOrganizationType(cipherView.organizationId); + return this.convertToNotificationCipherData( + cipherView, + iconsServerUrl, + showFavicons, + organizationType, + ); + }); } else { cipherView = this.convertAddLoginQueueMessageToCipherView(cipherQueueMessage); } const organizationType = getOrganizationType(cipherView.organizationId); + return [ this.convertToNotificationCipherData( cipherView, @@ -555,16 +567,6 @@ export default class NotificationBackground { return true; } - const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt(); - - if ( - changePasswordIsEnabled && - usernameMatches.length === 1 && - usernameMatches[0].login.password !== login.password - ) { - await this.pushChangePasswordToQueue(usernameMatches[0].id, loginDomain, login.password, tab); - return true; - } return false; } @@ -603,45 +605,92 @@ export default class NotificationBackground { data: ModifyLoginCipherFormData, tab: chrome.tabs.Tab, ): Promise { - const changeData = { - url: data.uri, - currentPassword: data.password, - newPassword: data.newPassword, - }; - - const loginDomain = Utils.getDomain(changeData.url); - if (loginDomain == null) { + const changePasswordIsEnabled = await this.getEnableChangedPasswordPrompt(); + if (!changePasswordIsEnabled) { return false; } - - if ((await this.getAuthStatus()) < AuthenticationStatus.Unlocked) { - await this.pushChangePasswordToQueue(null, loginDomain, changeData.newPassword, tab, true); - return true; + const authStatus = await this.getAuthStatus(); + if (authStatus === AuthenticationStatus.LoggedOut) { + return false; } - - let id: string = null; const activeUserId = await firstValueFrom( this.accountService.activeAccount$.pipe(getOptionalUserId), ); - if (activeUserId == null) { + if (activeUserId === null) { + return false; + } + const loginDomain = Utils.getDomain(data.uri); + if (loginDomain === null) { return false; } - const ciphers = await this.cipherService.getAllDecryptedForUrl(changeData.url, activeUserId); - if (changeData.currentPassword != null) { - const passwordMatches = ciphers.filter( - (c) => c.login.password === changeData.currentPassword, - ); - if (passwordMatches.length === 1) { - id = passwordMatches[0].id; - } - } else if (ciphers.length === 1) { - id = ciphers[0].id; - } - if (id != null) { - await this.pushChangePasswordToQueue(id, loginDomain, changeData.newPassword, tab); + const username: string | null = data.username || null; + const currentPassword = data.password || null; + const newPassword = data.newPassword || null; + + if (authStatus === AuthenticationStatus.Locked && newPassword !== null) { + await this.pushChangePasswordToQueue(null, loginDomain, newPassword, tab, true); return true; } + + let ciphers: CipherView[] = await this.cipherService.getAllDecryptedForUrl( + data.uri, + activeUserId, + ); + + const normalizedUsername: string = username ? username.toLowerCase() : ""; + + const shouldMatchUsername = typeof username === "string" && username.length > 0; + + if (shouldMatchUsername) { + // Presence of a username should filter ciphers further. + ciphers = ciphers.filter( + (cipher) => + cipher.login.username !== null && + cipher.login.username.toLowerCase() === normalizedUsername, + ); + } + + if (ciphers.length === 1) { + const [cipher] = ciphers; + if ( + username !== null && + newPassword === null && + cipher.login.username === normalizedUsername && + cipher.login.password === currentPassword + ) { + // Assumed to be a login + return false; + } + } + + if (currentPassword && !newPassword) { + // Only use current password for change if no new password present. + if (ciphers.length > 0) { + await this.pushChangePasswordToQueue( + ciphers.map((cipher) => cipher.id), + loginDomain, + currentPassword, + tab, + ); + return true; + } + } + + if (newPassword) { + // Otherwise include all known ciphers. + if (ciphers.length > 0) { + await this.pushChangePasswordToQueue( + ciphers.map((cipher) => cipher.id), + loginDomain, + newPassword, + tab, + ); + + return true; + } + } + return false; } @@ -666,7 +715,7 @@ export default class NotificationBackground { } private async pushChangePasswordToQueue( - cipherId: string, + cipherIds: CipherView["id"][], loginDomain: string, newPassword: string, tab: chrome.tabs.Tab, @@ -677,7 +726,7 @@ export default class NotificationBackground { const launchTimestamp = new Date().getTime(); const message: AddChangePasswordNotificationQueueMessage = { type: NotificationType.ChangePassword, - data: { cipherId: cipherId, newPassword: newPassword }, + data: { cipherIds: cipherIds, newPassword: newPassword }, domain: loginDomain, tab: tab, launchTimestamp, @@ -716,12 +765,12 @@ export default class NotificationBackground { return; } - await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder); + await this.saveOrUpdateCredentials(sender.tab, message.cipherId, message.edit, message.folder); } async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) { if (message.success) { - await this.saveOrUpdateCredentials(message.tab, false, undefined, true); + await this.saveOrUpdateCredentials(message.tab, message.cipherId, false, undefined, true); } else { await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", { error: "Password reprompt failed", @@ -740,6 +789,7 @@ export default class NotificationBackground { */ private async saveOrUpdateCredentials( tab: chrome.tabs.Tab, + cipherId: CipherView["id"], edit: boolean, folderId?: string, skipReprompt: boolean = false, @@ -764,7 +814,7 @@ export default class NotificationBackground { if (queueMessage.type === NotificationType.ChangePassword) { const { - data: { cipherId, newPassword }, + data: { newPassword }, } = queueMessage; const cipherView = await this.getDecryptedCipherById(cipherId, activeUserId); diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.ts b/apps/browser/src/autofill/background/overlay-notifications.background.ts index 4657dfb6d1f..e08fe540710 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.ts @@ -455,12 +455,12 @@ export class OverlayNotificationsBackground implements OverlayNotificationsBackg notificationType: NotificationType, ): boolean => { switch (notificationType) { - case NotificationTypes.Change: - return modifyLoginData?.newPassword && !modifyLoginData.username; case NotificationTypes.Add: return ( modifyLoginData?.username && !!(modifyLoginData.password || modifyLoginData.newPassword) ); + case NotificationTypes.Change: + return !!(modifyLoginData.password || modifyLoginData.newPassword); case NotificationTypes.AtRiskPassword: return !modifyLoginData.newPassword; case NotificationTypes.Unlock: diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts index 34ad5e1c9a9..7a392849996 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-action.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-action.ts @@ -4,8 +4,10 @@ import { BadgeButton } from "../../../content/components/buttons/badge-button"; import { EditButton } from "../../../content/components/buttons/edit-button"; import { NotificationTypes } from "../../../notification/abstractions/notification-bar"; import { I18n } from "../common-types"; +import { selectedCipher as selectedCipherSignal } from "../signals/selected-cipher"; export type CipherActionProps = { + cipherId: string; handleAction?: (e: Event) => void; i18n: I18n; itemName: string; @@ -15,6 +17,7 @@ export type CipherActionProps = { }; export function CipherAction({ + cipherId, handleAction = () => { /* no-op */ }, @@ -24,9 +27,17 @@ export function CipherAction({ theme, username, }: CipherActionProps) { + const selectCipherHandleAction = (e: Event) => { + selectedCipherSignal.set(cipherId); + try { + handleAction(e); + } finally { + selectedCipherSignal.set(null); + } + }; return notificationType === NotificationTypes.Change ? BadgeButton({ - buttonAction: handleAction, + buttonAction: selectCipherHandleAction, buttonText: i18n.notificationUpdate, itemName, theme, diff --git a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts index ab3b57f535c..3bfc44636b3 100644 --- a/apps/browser/src/autofill/content/components/cipher/cipher-item.ts +++ b/apps/browser/src/autofill/content/components/cipher/cipher-item.ts @@ -40,6 +40,7 @@ export function CipherItem({ if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) { cipherActionButton = html`
${CipherAction({ + cipherId: cipher.id, handleAction, i18n, itemName: name, diff --git a/apps/browser/src/autofill/content/components/signals/selected-cipher.ts b/apps/browser/src/autofill/content/components/signals/selected-cipher.ts new file mode 100644 index 00000000000..360457233f5 --- /dev/null +++ b/apps/browser/src/autofill/content/components/signals/selected-cipher.ts @@ -0,0 +1,3 @@ +import { signal } from "@lit-labs/signals"; + +export const selectedCipher = signal(null); diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 9ae6fcedc8f..fcf91ca2e91 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -1,6 +1,7 @@ import { render } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import type { FolderView } from "@bitwarden/common/vault/models/view/folder.view"; import { NotificationCipherData } from "../content/components/cipher/types"; @@ -8,6 +9,7 @@ import { CollectionView, I18n, OrgView } from "../content/components/common-type import { AtRiskNotification } from "../content/components/notification/at-risk-password/container"; import { NotificationConfirmationContainer } from "../content/components/notification/confirmation/container"; import { NotificationContainer } from "../content/components/notification/container"; +import { selectedCipher as selectedCipherSignal } from "../content/components/signals/selected-cipher"; import { selectedFolder as selectedFolderSignal } from "../content/components/signals/selected-folder"; import { selectedVault as selectedVaultSignal } from "../content/components/signals/selected-vault"; @@ -180,9 +182,9 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { const i18n = getI18n(); const resolvedTheme = getResolvedTheme(theme ?? ThemeTypes.Light); - const resolvedType = resolveNotificationType(notificationBarIframeInitData); - const headerMessage = getNotificationHeaderMessage(i18n, resolvedType); - const notificationTestId = getNotificationTestId(resolvedType); + const notificationType = resolveNotificationType(notificationBarIframeInitData); + const headerMessage = getNotificationHeaderMessage(i18n, notificationType); + const notificationTestId = getNotificationTestId(notificationType); appendHeaderMessageToTitle(headerMessage); document.body.innerHTML = ""; @@ -191,7 +193,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { const notificationConfig = { ...notificationBarIframeInitData, headerMessage, - type: resolvedType, + type: notificationType, notificationTestId, theme: resolvedTheme, personalVaultIsAllowed: !personalVaultDisallowed, @@ -201,7 +203,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { }; const handleSaveAction = () => { - sendSaveCipherMessage(true); + // cipher ID is null while vault is locked. + sendSaveCipherMessage(null, true); render( NotificationContainer({ @@ -262,7 +265,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { NotificationContainer({ ...notificationBarIframeInitData, headerMessage, - type: resolvedType, + type: notificationType, theme: resolvedTheme, notificationTestId, personalVaultIsAllowed: !personalVaultDisallowed, @@ -276,9 +279,8 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { }); function handleEditOrUpdateAction(e: Event) { - const notificationType = initData?.type; e.preventDefault(); - notificationType === "add" ? sendSaveCipherMessage(true) : sendSaveCipherMessage(false); + sendSaveCipherMessage(selectedCipherSignal.get(), notificationType === NotificationTypes.Add); } } @@ -291,6 +293,7 @@ function handleCloseNotification(e: Event) { } function handleSaveAction(e: Event) { + const selectedCipher = selectedCipherSignal.get(); const selectedVault = selectedVaultSignal.get(); const selectedFolder = selectedFolderSignal.get(); @@ -304,16 +307,16 @@ function handleSaveAction(e: Event) { } e.preventDefault(); - - sendSaveCipherMessage(removeIndividualVault(), selectedFolder); + sendSaveCipherMessage(selectedCipher, removeIndividualVault(), selectedFolder); if (removeIndividualVault()) { return; } } -function sendSaveCipherMessage(edit: boolean, folder?: string) { +function sendSaveCipherMessage(cipherId: CipherView["id"] | null, edit: boolean, folder?: string) { sendPlatformMessage({ command: "bgSaveCipher", + cipherId, folder, edit, });