From d1f944a54aa9d0967356cea9ed6bd09c6b278748 Mon Sep 17 00:00:00 2001 From: Miles Blackwood Date: Tue, 29 Apr 2025 18:31:55 -0400 Subject: [PATCH] Ensure translations and action button are loading. --- apps/browser/src/_locales/en/messages.json | 27 +++++++++++++++++++ .../abstractions/notification.background.ts | 5 ++-- .../background/notification.background.ts | 18 ++++++++----- .../overlay-notifications.background.spec.ts | 12 +++++++++ .../content/components/notification/body.ts | 11 +++++--- .../notification/confirmation/container.ts | 2 ++ .../components/notification/container.ts | 3 +++ .../content/components/notification/footer.ts | 11 ++++++++ .../abstractions/notification-bar.ts | 9 +++++++ apps/browser/src/autofill/notification/bar.ts | 3 +-- .../overlay-notifications-content.service.ts | 1 + .../overlay-notifications-content.service.ts | 11 +++++--- 12 files changed, 95 insertions(+), 18 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4f83b07506b..755bf8734c7 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -2493,6 +2493,10 @@ "change": { "message": "Change" }, + "changePassword": { + "message": "Change password", + "description": "Change password button for browser at risk notification on login." + }, "changeButtonTitle": { "message": "Change password - $ITEMNAME$", "placeholders": { @@ -2502,6 +2506,9 @@ } } }, + "atRiskPassword": { + "message": "At-risk password" + }, "atRiskPasswords": { "message": "At-risk passwords" }, @@ -2536,6 +2543,26 @@ } } }, + "atRiskChangePrompt": { + "message": "Your password for this site is at-risk. $ORGANIZATION$ has requested that you change it.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + }, + "description": "Notification body when a login triggers an at-risk password change request and the change password domain is known." + }, + "atRiskNavigatePrompt": { + "message": "$ORGANIZATION$ wants you to change this password because it is at-risk. Navigate to your account settings to change the password.", + "placeholders": { + "organization": { + "content": "$1", + "example": "Acme Corp" + } + }, + "description": "Notification body when a login triggers an at-risk password change request and no change password domain is provided." + }, "reviewAndChangeAtRiskPassword": { "message": "Review and change one at-risk password" }, diff --git a/apps/browser/src/autofill/background/abstractions/notification.background.ts b/apps/browser/src/autofill/background/abstractions/notification.background.ts index 067c1d16fc8..2a908eb8832 100644 --- a/apps/browser/src/autofill/background/abstractions/notification.background.ts +++ b/apps/browser/src/autofill/background/abstractions/notification.background.ts @@ -1,4 +1,3 @@ -import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; import { NeverDomains } from "@bitwarden/common/models/domain/domain-service"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; import { UserId } from "@bitwarden/common/types/guid"; @@ -38,8 +37,8 @@ interface AddUnlockVaultQueueMessage extends NotificationQueueMessage { interface AtRiskPasswordQueueMessage extends NotificationQueueMessage { type: "at-risk-password"; - organization: Organization; - cipher: CipherView; + organizationName: string; + passwordChangeUri?: string; } type NotificationQueueMessageItem = diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 0186a38e449..b8b7a060a60 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -343,12 +343,17 @@ export default class NotificationBackground { tab: chrome.tabs.Tab, notificationQueueMessage: NotificationQueueMessageItem, ) { - const notificationType = notificationQueueMessage.type; + const { + type: notificationType, + wasVaultLocked: isVaultLocked, + launchTimestamp, + ...params + } = notificationQueueMessage; const typeData: NotificationTypeData = { - isVaultLocked: notificationQueueMessage.wasVaultLocked, + isVaultLocked, theme: await firstValueFrom(this.themeStateService.selectedTheme$), - launchTimestamp: notificationQueueMessage.launchTimestamp, + launchTimestamp, }; switch (notificationType) { @@ -360,6 +365,7 @@ export default class NotificationBackground { await BrowserApi.tabSendMessageData(tab, "openNotificationBar", { type: notificationType, typeData, + params, }); } @@ -387,7 +393,7 @@ export default class NotificationBackground { message: NotificationBackgroundExtensionMessage, sender: chrome.runtime.MessageSender, ) { - const { activeUserId, cipher, securityTask, uri } = message.data; + const { activeUserId, securityTask, uri } = message.data; const domain = Utils.getDomain(uri); const addLoginIsEnabled = await this.getEnableAddedLoginPrompt(); @@ -405,9 +411,9 @@ export default class NotificationBackground { domain, wasVaultLocked, type: NotificationQueueMessageType.AtRiskPassword, - organization: organization, + passwordChangeUri: domain, + organizationName: organization.name, tab: sender.tab, - cipher, launchTimestamp, expires: new Date(launchTimestamp + NOTIFICATION_BAR_LIFESPAN_MS), }; diff --git a/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts b/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts index a51757dabea..27a6f85e745 100644 --- a/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts +++ b/apps/browser/src/autofill/background/overlay-notifications.background.spec.ts @@ -1,9 +1,12 @@ import { mock, MockProxy } from "jest-mock-extended"; +import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { CLEAR_NOTIFICATION_LOGIN_DATA_DURATION } from "@bitwarden/common/autofill/constants"; import { ServerConfig } from "@bitwarden/common/platform/abstractions/config/server-config"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { EnvironmentServerConfigData } from "@bitwarden/common/platform/models/data/server-config.data"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { TaskService } from "@bitwarden/common/vault/tasks"; import { BrowserApi } from "../../platform/browser/browser-api"; import AutofillField from "../models/autofill-field"; @@ -24,6 +27,9 @@ import { OverlayNotificationsBackground } from "./overlay-notifications.backgrou describe("OverlayNotificationsBackground", () => { let logService: MockProxy; let notificationBackground: NotificationBackground; + let taskService: TaskService; + let accountService: AccountService; + let cipherService: CipherService; let getEnableChangedPasswordPromptSpy: jest.SpyInstance; let getEnableAddedLoginPromptSpy: jest.SpyInstance; let overlayNotificationsBackground: OverlayNotificationsBackground; @@ -32,6 +38,9 @@ describe("OverlayNotificationsBackground", () => { jest.useFakeTimers(); logService = mock(); notificationBackground = mock(); + taskService = mock(); + accountService = mock(); + cipherService = mock(); getEnableChangedPasswordPromptSpy = jest .spyOn(notificationBackground, "getEnableChangedPasswordPrompt") .mockResolvedValue(true); @@ -41,6 +50,9 @@ describe("OverlayNotificationsBackground", () => { overlayNotificationsBackground = new OverlayNotificationsBackground( logService, notificationBackground, + taskService, + accountService, + cipherService, ); await overlayNotificationsBackground.init(); }); diff --git a/apps/browser/src/autofill/content/components/notification/body.ts b/apps/browser/src/autofill/content/components/notification/body.ts index ea59da541f7..1f36f7c5e30 100644 --- a/apps/browser/src/autofill/content/components/notification/body.ts +++ b/apps/browser/src/autofill/content/components/notification/body.ts @@ -4,6 +4,7 @@ import { html } from "lit"; import { Theme, ThemeTypes } from "@bitwarden/common/platform/enums"; import { + NotificationMessageParams, NotificationType, NotificationTypes, } from "../../../notification/abstractions/notification-bar"; @@ -20,29 +21,31 @@ const { css } = createEmotion({ export function NotificationBody({ ciphers = [], - passwordChangeUri, i18n, notificationType, theme = ThemeTypes.Light, handleEditOrUpdateAction, + params = {}, }: { ciphers?: NotificationCipherData[]; - passwordChangeUri: string; customClasses?: string[]; i18n: { [key: string]: string }; notificationType?: NotificationType; theme: Theme; handleEditOrUpdateAction: (e: Event) => void; + params?: NotificationMessageParams; }) { // @TODO get client vendor from context const isSafari = false; + const { passwordChangeUri, organizationName } = params; switch (notificationType) { case NotificationTypes.AtRiskPassword: return html`
- ${passwordChangeUri ? i18n.atRiskChangePrompt : i18n.atRiskNavigatePrompt} - ${passwordChangeUri && i18n.changePassword} + ${passwordChangeUri + ? chrome.i18n.getMessage("atRiskChangePrompt", organizationName) + : chrome.i18n.getMessage("atRiskNavigatePrompt", organizationName)}
`; default: diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts index 5cc977cf4cb..26fcc8e8a15 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -134,6 +134,8 @@ function getHeaderMessage( return i18n.loginSaveSuccess; case NotificationTypes.Change: return i18n.loginUpdateSuccess; + case NotificationTypes.AtRiskPassword: + return i18n.changePassword; case NotificationTypes.Unlock: return ""; default: diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index bd4c52a226c..a265ece8bfd 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -31,6 +31,7 @@ export type NotificationContainerProps = NotificationBarIframeInitData & { organizations?: OrgView[]; personalVaultIsAllowed?: boolean; type: NotificationType; // @TODO typing override for generic `NotificationBarIframeInitData.type` + params: object; }; export function NotificationContainer({ @@ -45,6 +46,7 @@ export function NotificationContainer({ personalVaultIsAllowed = true, theme = ThemeTypes.Light, type, + params, }: NotificationContainerProps) { const headerMessage = getHeaderMessage(i18n, type); const showBody = true; @@ -64,6 +66,7 @@ export function NotificationContainer({ notificationType: type, theme, i18n, + params, }) : null} ${NotificationFooter({ diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index baa1a2ecffc..f5bfb70ac06 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -7,6 +7,7 @@ import { NotificationType, NotificationTypes, } from "../../../notification/abstractions/notification-bar"; +import { ActionButton } from "../buttons/action-button"; import { OrgView, FolderView, CollectionView } from "../common-types"; import { spacing, themes } from "../constants/styles"; @@ -36,6 +37,16 @@ export function NotificationFooter({ const isChangeNotification = notificationType === NotificationTypes.Change; const primaryButtonText = i18n.saveAction; + if (notificationType === NotificationTypes.AtRiskPassword) { + return html`
+ ${ActionButton({ + handleClick: () => {}, + buttonText: i18n.changePassword, + theme, + })} +
`; + } + return html`
${!isChangeNotification diff --git a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts index 713566cc449..abfb141c21d 100644 --- a/apps/browser/src/autofill/notification/abstractions/notification-bar.ts +++ b/apps/browser/src/autofill/notification/abstractions/notification-bar.ts @@ -33,6 +33,7 @@ type NotificationBarIframeInitData = { theme?: Theme; type?: NotificationType; // @TODO use `NotificationType` passwordChangeUri?: string; + params?: NotificationMessageParams; }; type NotificationBarWindowMessage = { @@ -52,7 +53,15 @@ type NotificationBarWindowMessageHandlers = { saveCipherAttemptCompleted: ({ message }: { message: NotificationBarWindowMessage }) => void; }; +type NotificationMessageParamsAtRiskPasswordType = { + passwordChangeUri?: string; + organizationName: string; +}; + +type NotificationMessageParams = NotificationMessageParamsAtRiskPasswordType | any; + export { + NotificationMessageParams, NotificationTaskInfo, NotificationTypes, NotificationType, diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index b5ce5f9e61c..c7bb3a3f7ff 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -56,8 +56,6 @@ function getI18n() { return { appName: chrome.i18n.getMessage("appName"), atRiskPassword: chrome.i18n.getMessage("atRiskPassword"), - atRiskChangePrompt: chrome.i18n.getMessage("atRiskChangePrompt"), - atRiskNavigatePrompt: chrome.i18n.getMessage("atRiskNavigatePrompt"), changePassword: chrome.i18n.getMessage("changePassword"), close: chrome.i18n.getMessage("close"), collection: chrome.i18n.getMessage("collection"), @@ -185,6 +183,7 @@ async function initNotificationBar(message: NotificationBarWindowMessage) { handleSaveAction, handleEditOrUpdateAction, i18n, + params: initData.params, }), document.body, ); diff --git a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts index 82c03cacadf..f9d65a00679 100644 --- a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts @@ -16,6 +16,7 @@ export type NotificationsExtensionMessage = { height?: number; error?: string; fadeOutNotification?: boolean; + params: object; }; }; diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 519521feaa9..dd3f9cf97df 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -2,7 +2,10 @@ // @ts-strict-ignore import { EVENTS } from "@bitwarden/common/autofill/constants"; -import { NotificationBarIframeInitData } from "../../../notification/abstractions/notification-bar"; +import { + NotificationBarIframeInitData, + NotificationType, +} from "../../../notification/abstractions/notification-bar"; import { sendExtensionMessage, setElementStyles } from "../../../utils"; import { NotificationsExtensionMessage, @@ -78,17 +81,19 @@ export class OverlayNotificationsContentService return; } - const { type, typeData } = message.data; + const { type, typeData, params } = message.data; + if (this.currentNotificationBarType && type !== this.currentNotificationBarType) { this.closeNotificationBar(); } const initData = { - type, + type: type as NotificationType, isVaultLocked: typeData.isVaultLocked, theme: typeData.theme, removeIndividualVault: typeData.removeIndividualVault, importType: typeData.importType, launchTimestamp: typeData.launchTimestamp, + params, }; if (globalThis.document.readyState === "loading") {