1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 14:04:03 +00:00

Ensure translations and action button are loading.

This commit is contained in:
Miles Blackwood
2025-04-29 18:31:55 -04:00
parent c8743eaa84
commit d1f944a54a
12 changed files with 95 additions and 18 deletions

View File

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

View File

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

View File

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

View File

@@ -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<LogService>;
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<LogService>();
notificationBackground = mock<NotificationBackground>();
taskService = mock<TaskService>();
accountService = mock<AccountService>();
cipherService = mock<CipherService>();
getEnableChangedPasswordPromptSpy = jest
.spyOn(notificationBackground, "getEnableChangedPasswordPrompt")
.mockResolvedValue(true);
@@ -41,6 +50,9 @@ describe("OverlayNotificationsBackground", () => {
overlayNotificationsBackground = new OverlayNotificationsBackground(
logService,
notificationBackground,
taskService,
accountService,
cipherService,
);
await overlayNotificationsBackground.init();
});

View File

@@ -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`
<div class=${notificationBodyStyles({ isSafari, theme })}>
${passwordChangeUri ? i18n.atRiskChangePrompt : i18n.atRiskNavigatePrompt}
${passwordChangeUri && i18n.changePassword}
${passwordChangeUri
? chrome.i18n.getMessage("atRiskChangePrompt", organizationName)
: chrome.i18n.getMessage("atRiskNavigatePrompt", organizationName)}
</div>
`;
default:

View File

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

View File

@@ -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({

View File

@@ -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`<div class=${notificationFooterStyles({ theme })}>
${ActionButton({
handleClick: () => {},
buttonText: i18n.changePassword,
theme,
})}
</div>`;
}
return html`
<div class=${notificationFooterStyles({ theme })}>
${!isChangeNotification

View File

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

View File

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

View File

@@ -16,6 +16,7 @@ export type NotificationsExtensionMessage = {
height?: number;
error?: string;
fadeOutNotification?: boolean;
params: object;
};
};

View File

@@ -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") {