1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

Streamlines change pass notification logic. (#16435)

* Streamlines change pass notification logic.

* Includes test cases for all change password behavior.

* Allows multiple cipher views to be passed to notification.

* Removes assumptions about matching passwords.

* Includes current password match for now.

* [WIP] Fixes exact login match ignore for change. Partially updates save/update methods for ciphers.

* Removes password matching.

* Preserves nullable cipherId, set only while cipher action handled

* Updates comment.
This commit is contained in:
Miles Blackwood
2025-10-06 18:22:07 -04:00
committed by GitHub
parent aa3be491d7
commit 24e159250b
8 changed files with 276 additions and 135 deletions

View File

@@ -35,7 +35,7 @@ interface NotificationQueueMessage {
}
type ChangePasswordNotificationData = {
cipherId: CipherView["id"];
cipherIds: CipherView["id"][];
newPassword: string;
};

View File

@@ -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<CipherView>({ 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<CipherView>({
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<chrome.runtime.MessageSender>({ 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<CipherView>({ 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<CipherView>({
id: "cipher-id-1",
login: { username: "test", password: "currentPassword" },
}),
mock<CipherView>({
id: "cipher-id-2",
login: { username: "username", password: "currentPassword" },
}),
mock<CipherView>({
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<CipherView>({
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<CipherView>({
id: "cipher-id-1",
login: { password: "password1" },
}),
mock<CipherView>({
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<CipherView>({ login: { username: "test", password: "password" } }),
mock<CipherView>({ 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<CipherView>({ login: { username: "test", password: "password" } }),
mock<CipherView>({ login: { username: "test2", password: "password" } }),
mock<CipherView>({
id: "cipher-id-1",
login: { username: "test", password: "password" },
}),
mock<CipherView>({
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,

View File

@@ -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<boolean> {
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);

View File

@@ -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:

View File

@@ -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,

View File

@@ -40,6 +40,7 @@ export function CipherItem({
if (notificationType === NotificationTypes.Change || notificationType === NotificationTypes.Add) {
cipherActionButton = html`<div>
${CipherAction({
cipherId: cipher.id,
handleAction,
i18n,
itemName: name,

View File

@@ -0,0 +1,3 @@
import { signal } from "@lit-labs/signals";
export const selectedCipher = signal<string | null>(null);

View File

@@ -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,
});