mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 20:50:28 +00:00
Merge remote-tracking branch 'origin' into auth/pm-18720/change-password-component-non-dialog-v2
This commit is contained in:
2
.github/renovate.json5
vendored
2
.github/renovate.json5
vendored
@@ -312,6 +312,7 @@
|
||||
"@angular/platform-browser",
|
||||
"@angular/platform",
|
||||
"@angular/router",
|
||||
"axe-playwright",
|
||||
"@compodoc/compodoc",
|
||||
"@ng-select/ng-select",
|
||||
"@storybook/addon-a11y",
|
||||
@@ -320,6 +321,7 @@
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
"@storybook/addon-links",
|
||||
"@storybook/test-runner",
|
||||
"@storybook/addon-themes",
|
||||
"@storybook/angular",
|
||||
"@storybook/manager-api",
|
||||
|
||||
@@ -26,6 +26,9 @@ const preview: Preview = {
|
||||
wrapperDecorator,
|
||||
],
|
||||
parameters: {
|
||||
a11y: {
|
||||
element: "#storybook-root",
|
||||
},
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
|
||||
47
.storybook/test-runner.ts
Normal file
47
.storybook/test-runner.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { type TestRunnerConfig } from "@storybook/test-runner";
|
||||
import { injectAxe, checkA11y } from "axe-playwright";
|
||||
|
||||
const testRunnerConfig: TestRunnerConfig = {
|
||||
setup() {},
|
||||
|
||||
async preVisit(page, context) {
|
||||
return await injectAxe(page);
|
||||
},
|
||||
|
||||
async postVisit(page, context) {
|
||||
await page.waitForSelector("#storybook-root");
|
||||
// https://github.com/abhinaba-ghosh/axe-playwright#parameters-on-checka11y-axerun
|
||||
await checkA11y(
|
||||
// Playwright page instance.
|
||||
page,
|
||||
|
||||
// context
|
||||
"#storybook-root",
|
||||
|
||||
// axeOptions, see https://www.deque.com/axe/core-documentation/api-documentation/#parameters-axerun
|
||||
{
|
||||
detailedReport: true,
|
||||
detailedReportOptions: {
|
||||
// Includes the full html for invalid nodes
|
||||
html: true,
|
||||
},
|
||||
verbose: false,
|
||||
},
|
||||
|
||||
// skipFailures
|
||||
false,
|
||||
|
||||
// reporter "v2" is terminal reporter, "html" writes results to file
|
||||
"v2",
|
||||
|
||||
// axeHtmlReporterOptions
|
||||
// NOTE: set reporter param (above) to "html" to activate these options
|
||||
{
|
||||
outputDir: "reports/a11y",
|
||||
reportFileName: `${context.id}.html`,
|
||||
},
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default testRunnerConfig;
|
||||
@@ -1088,22 +1088,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"loginSaveConfirmation": {
|
||||
"message": "$ITEMNAME$ saved to Bitwarden.",
|
||||
"placeholders": {
|
||||
"itemName": {
|
||||
"content": "$1"
|
||||
}
|
||||
},
|
||||
"notificationLoginSaveConfirmation": {
|
||||
"message": "saved to Bitwarden.",
|
||||
|
||||
"description": "Shown to user after item is saved."
|
||||
},
|
||||
"loginUpdatedConfirmation": {
|
||||
"message": "$ITEMNAME$ updated in Bitwarden.",
|
||||
"placeholders": {
|
||||
"itemName": {
|
||||
"content": "$1"
|
||||
}
|
||||
},
|
||||
"notificationLoginUpdatedConfirmation": {
|
||||
"message": "updated in Bitwarden.",
|
||||
"description": "Shown to user after item is updated."
|
||||
},
|
||||
"saveAsNewLoginAction": {
|
||||
@@ -5267,6 +5258,9 @@
|
||||
"secureDevicesBody": {
|
||||
"message": "Save unlimited passwords across unlimited devices with Bitwarden mobile, browser, and desktop apps."
|
||||
},
|
||||
"nudgeBadgeAria": {
|
||||
"message": "1 notification"
|
||||
},
|
||||
"emptyVaultNudgeTitle": {
|
||||
"message": "Import existing passwords"
|
||||
},
|
||||
@@ -5330,5 +5324,8 @@
|
||||
"message": "Learn more about SSH agent",
|
||||
"description": "Two part message",
|
||||
"example": "Store your keys and connect with the SSH agent for fast, encrypted authentication. Learn more about SSH agent"
|
||||
},
|
||||
"noPermissionsViewPage": {
|
||||
"message": "You do not have permissions to view this page. Try logging in with a different account."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
|
||||
import { SelfHostedEnvironment } from "@bitwarden/common/platform/services/default-environment.service";
|
||||
import { ThemeStateService } from "@bitwarden/common/platform/theming/theme-state.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherRepromptType } from "@bitwarden/common/vault/enums/cipher-reprompt-type";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
@@ -828,6 +829,7 @@ describe("NotificationBackground", () => {
|
||||
id: "testId",
|
||||
name: "testItemName",
|
||||
login: { username: "testUser" },
|
||||
reprompt: CipherRepromptType.None,
|
||||
});
|
||||
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||
|
||||
@@ -842,6 +844,7 @@ describe("NotificationBackground", () => {
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
false,
|
||||
);
|
||||
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||
@@ -855,6 +858,55 @@ describe("NotificationBackground", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prompts the user for master password entry if the notification message type is for ChangePassword and the cipher reprompt is enabled", async () => {
|
||||
const tab = createChromeTabMock({ id: 1, url: "https://example.com" });
|
||||
const sender = mock<chrome.runtime.MessageSender>({ tab });
|
||||
const message: NotificationBackgroundExtensionMessage = {
|
||||
command: "bgSaveCipher",
|
||||
edit: false,
|
||||
folder: "folder-id",
|
||||
};
|
||||
const queueMessage = mock<AddChangePasswordQueueMessage>({
|
||||
type: NotificationQueueMessageType.ChangePassword,
|
||||
tab,
|
||||
domain: "example.com",
|
||||
newPassword: "newPassword",
|
||||
});
|
||||
notificationBackground["notificationQueue"] = [queueMessage];
|
||||
const cipherView = mock<CipherView>({
|
||||
id: "testId",
|
||||
name: "testItemName",
|
||||
login: { username: "testUser" },
|
||||
reprompt: CipherRepromptType.Password,
|
||||
});
|
||||
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||
|
||||
sendMockExtensionMessage(message, sender);
|
||||
await flushPromises();
|
||||
|
||||
expect(editItemSpy).not.toHaveBeenCalled();
|
||||
expect(autofillService.isPasswordRepromptRequired).toHaveBeenCalled();
|
||||
expect(createWithServerSpy).not.toHaveBeenCalled();
|
||||
expect(updatePasswordSpy).toHaveBeenCalledWith(
|
||||
cipherView,
|
||||
queueMessage.newPassword,
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
false,
|
||||
);
|
||||
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
||||
expect(tabSendMessageDataSpy).not.toHaveBeenCalledWith(
|
||||
sender.tab,
|
||||
"saveCipherAttemptCompleted",
|
||||
{
|
||||
itemName: "testItemName",
|
||||
cipherId: cipherView.id,
|
||||
task: undefined,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("completes password update notification with a security task notice if any are present for the cipher, and dismisses tasks for the updated cipher", async () => {
|
||||
const mockCipherId = "testId";
|
||||
const mockOrgId = "testOrgId";
|
||||
@@ -905,6 +957,7 @@ describe("NotificationBackground", () => {
|
||||
id: mockCipherId,
|
||||
organizationId: mockOrgId,
|
||||
name: "Test Item",
|
||||
reprompt: CipherRepromptType.None,
|
||||
});
|
||||
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||
|
||||
@@ -919,6 +972,7 @@ describe("NotificationBackground", () => {
|
||||
message.edit,
|
||||
sender.tab,
|
||||
mockCipherId,
|
||||
false,
|
||||
);
|
||||
expect(updateWithServerSpy).toHaveBeenCalled();
|
||||
expect(tabSendMessageDataSpy).toHaveBeenCalledWith(
|
||||
@@ -1000,6 +1054,7 @@ describe("NotificationBackground", () => {
|
||||
message.edit,
|
||||
sender.tab,
|
||||
"testId",
|
||||
false,
|
||||
);
|
||||
expect(editItemSpy).toHaveBeenCalled();
|
||||
expect(updateWithServerSpy).not.toHaveBeenCalled();
|
||||
@@ -1170,7 +1225,7 @@ describe("NotificationBackground", () => {
|
||||
newPassword: "newPassword",
|
||||
});
|
||||
notificationBackground["notificationQueue"] = [queueMessage];
|
||||
const cipherView = mock<CipherView>();
|
||||
const cipherView = mock<CipherView>({ reprompt: CipherRepromptType.None });
|
||||
getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView);
|
||||
const errorMessage = "fetch error";
|
||||
updateWithServerSpy.mockImplementation(() => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
ExtensionCommand,
|
||||
ExtensionCommandType,
|
||||
NOTIFICATION_BAR_LIFESPAN_MS,
|
||||
UPDATE_PASSWORD,
|
||||
} from "@bitwarden/common/autofill/constants";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { UserNotificationSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/user-notification-settings.service";
|
||||
@@ -104,6 +105,8 @@ export default class NotificationBackground {
|
||||
this.removeTabFromNotificationQueue(sender.tab),
|
||||
bgReopenUnlockPopout: ({ sender }) => this.openUnlockPopout(sender.tab),
|
||||
bgSaveCipher: ({ message, sender }) => this.handleSaveCipherMessage(message, sender),
|
||||
bgHandleReprompt: ({ message, sender }: any) =>
|
||||
this.handleCipherUpdateRepromptResponse(message),
|
||||
bgUnlockPopoutOpened: ({ message, sender }) => this.unlockVault(message, sender.tab),
|
||||
checkNotificationQueue: ({ sender }) => this.checkNotificationQueue(sender.tab),
|
||||
collectPageDetailsResponse: ({ message }) =>
|
||||
@@ -631,6 +634,17 @@ export default class NotificationBackground {
|
||||
await this.saveOrUpdateCredentials(sender.tab, message.edit, message.folder);
|
||||
}
|
||||
|
||||
async handleCipherUpdateRepromptResponse(message: NotificationBackgroundExtensionMessage) {
|
||||
if (message.success) {
|
||||
await this.saveOrUpdateCredentials(message.tab, false, undefined, true);
|
||||
} else {
|
||||
await BrowserApi.tabSendMessageData(message.tab, "saveCipherAttemptCompleted", {
|
||||
error: "Password reprompt failed",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves or updates credentials based on the message within the
|
||||
* notification queue that is associated with the specified tab.
|
||||
@@ -639,7 +653,12 @@ export default class NotificationBackground {
|
||||
* @param edit - Identifies if the credentials should be edited or simply added
|
||||
* @param folderId - The folder to add the cipher to
|
||||
*/
|
||||
private async saveOrUpdateCredentials(tab: chrome.tabs.Tab, edit: boolean, folderId?: string) {
|
||||
private async saveOrUpdateCredentials(
|
||||
tab: chrome.tabs.Tab,
|
||||
edit: boolean,
|
||||
folderId?: string,
|
||||
skipReprompt: boolean = false,
|
||||
) {
|
||||
for (let i = this.notificationQueue.length - 1; i >= 0; i--) {
|
||||
const queueMessage = this.notificationQueue[i];
|
||||
if (
|
||||
@@ -654,18 +673,26 @@ export default class NotificationBackground {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
|
||||
const activeUserId = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
|
||||
if (queueMessage.type === NotificationQueueMessageType.ChangePassword) {
|
||||
const cipherView = await this.getDecryptedCipherById(queueMessage.cipherId, activeUserId);
|
||||
await this.updatePassword(cipherView, queueMessage.newPassword, edit, tab, activeUserId);
|
||||
|
||||
await this.updatePassword(
|
||||
cipherView,
|
||||
queueMessage.newPassword,
|
||||
edit,
|
||||
tab,
|
||||
activeUserId,
|
||||
skipReprompt,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationQueue.splice(i, 1);
|
||||
|
||||
// If the vault was locked, check if a cipher needs updating instead of creating a new one
|
||||
if (queueMessage.wasVaultLocked) {
|
||||
const allCiphers = await this.cipherService.getAllDecryptedForUrl(
|
||||
@@ -725,6 +752,7 @@ export default class NotificationBackground {
|
||||
edit: boolean,
|
||||
tab: chrome.tabs.Tab,
|
||||
userId: UserId,
|
||||
skipReprompt: boolean = false,
|
||||
) {
|
||||
cipherView.login.password = newPassword;
|
||||
|
||||
@@ -758,6 +786,12 @@ export default class NotificationBackground {
|
||||
}
|
||||
: undefined;
|
||||
|
||||
if (cipherView.reprompt && !skipReprompt) {
|
||||
await this.autofillService.isPasswordRepromptRequired(cipherView, tab, UPDATE_PASSWORD);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.cipherService.updateWithServer(cipher);
|
||||
|
||||
await BrowserApi.tabSendMessageData(tab, "saveCipherAttemptCompleted", {
|
||||
|
||||
@@ -107,9 +107,9 @@ export const mockI18n = {
|
||||
collection: "Collection",
|
||||
folder: "Folder",
|
||||
loginSaveSuccess: "Login saved",
|
||||
loginSaveConfirmation: "$ITEMNAME$ saved to Bitwarden.",
|
||||
notificationLoginSaveConfirmation: "saved to Bitwarden.",
|
||||
loginUpdateSuccess: "Login updated",
|
||||
loginUpdatedConfirmation: "$ITEMNAME$ updated in Bitwarden.",
|
||||
notificationLoginUpdatedConfirmation: "updated in Bitwarden.",
|
||||
loginUpdateTaskSuccess:
|
||||
"Great job! You took the steps to make you and $ORGANIZATION$ more secure.",
|
||||
loginUpdateTaskSuccessAdditional:
|
||||
|
||||
@@ -113,9 +113,14 @@ function getConfirmationMessage(i18n: I18n, type?: NotificationType, error?: str
|
||||
if (error) {
|
||||
return i18n.saveFailureDetails;
|
||||
}
|
||||
|
||||
/* @TODO This partial string return and later concatenation with the cipher name is needed
|
||||
* to handle cipher name overflow cases, but is risky for i18n concerns. Fix concatenation
|
||||
* with cipher name overflow when a tag replacement solution is available.
|
||||
*/
|
||||
return type === NotificationTypes.Add
|
||||
? i18n.loginSaveConfirmation
|
||||
: i18n.loginUpdatedConfirmation;
|
||||
? i18n.notificationLoginSaveConfirmation
|
||||
: i18n.notificationLoginUpdatedConfirmation;
|
||||
}
|
||||
|
||||
function getHeaderMessage(i18n: I18n, type?: NotificationType, error?: string) {
|
||||
|
||||
@@ -28,28 +28,31 @@ export function NotificationConfirmationMessage({
|
||||
<div class=${containerStyles}>
|
||||
${message || buttonText
|
||||
? html`
|
||||
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
|
||||
<span
|
||||
title=${message || buttonText}
|
||||
class=${notificationConfirmationMessageStyles(theme)}
|
||||
>
|
||||
${message || nothing}
|
||||
${buttonText
|
||||
? html`
|
||||
<a
|
||||
title=${buttonText}
|
||||
class=${notificationConfirmationButtonTextStyles(theme)}
|
||||
@click=${handleClick}
|
||||
@keydown=${(e: KeyboardEvent) => handleButtonKeyDown(e, () => handleClick(e))}
|
||||
aria-label=${buttonAria}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
${buttonText}
|
||||
</a>
|
||||
`
|
||||
: nothing}
|
||||
</span>
|
||||
<div class=${singleLineWrapperStyles}>
|
||||
<span class=${itemNameStyles(theme)} title=${itemName}> ${itemName} </span>
|
||||
<span
|
||||
title=${message || buttonText}
|
||||
class=${notificationConfirmationMessageStyles(theme)}
|
||||
>
|
||||
${message || nothing}
|
||||
${buttonText
|
||||
? html`
|
||||
<a
|
||||
title=${buttonText}
|
||||
class=${notificationConfirmationButtonTextStyles(theme)}
|
||||
@click=${handleClick}
|
||||
@keydown=${(e: KeyboardEvent) =>
|
||||
handleButtonKeyDown(e, () => handleClick(e))}
|
||||
aria-label=${buttonAria}
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
${buttonText}
|
||||
</a>
|
||||
`
|
||||
: nothing}
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${messageDetails
|
||||
@@ -61,12 +64,17 @@ export function NotificationConfirmationMessage({
|
||||
|
||||
const containerStyles = css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: ${spacing[1]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const singleLineWrapperStyles = css`
|
||||
display: inline;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
`;
|
||||
|
||||
const baseTextStyles = css`
|
||||
overflow-x: hidden;
|
||||
text-align: left;
|
||||
@@ -81,6 +89,9 @@ const notificationConfirmationMessageStyles = (theme: Theme) => css`
|
||||
|
||||
color: ${themes[theme].text.main};
|
||||
font-weight: 400;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
display: inline;
|
||||
`;
|
||||
|
||||
const itemNameStyles = (theme: Theme) => css`
|
||||
@@ -90,6 +101,10 @@ const itemNameStyles = (theme: Theme) => css`
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
`;
|
||||
|
||||
const notificationConfirmationButtonTextStyles = (theme: Theme) => css`
|
||||
|
||||
@@ -94,7 +94,7 @@ const notificationContainerStyles = (theme: Theme) => css`
|
||||
}
|
||||
|
||||
[class*="${notificationBodyClassPrefix}-"] {
|
||||
margin: ${spacing["3"]} 0 ${spacing["1.5"]} ${spacing["3"]};
|
||||
margin: ${spacing["3"]} 0 0 ${spacing["3"]};
|
||||
padding-right: ${spacing["3"]};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
NotificationTypes,
|
||||
} from "../../../notification/abstractions/notification-bar";
|
||||
import { OrgView, FolderView, I18n, CollectionView } from "../common-types";
|
||||
import { spacing, themes } from "../constants/styles";
|
||||
import { spacing } from "../constants/styles";
|
||||
|
||||
import { NotificationButtonRow } from "./button-row";
|
||||
|
||||
@@ -37,7 +37,7 @@ export function NotificationFooter({
|
||||
const primaryButtonText = i18n.saveAction;
|
||||
|
||||
return html`
|
||||
<div class=${notificationFooterStyles({ theme })}>
|
||||
<div class=${notificationFooterStyles({ isChangeNotification })}>
|
||||
${!isChangeNotification
|
||||
? NotificationButtonRow({
|
||||
collections,
|
||||
@@ -56,13 +56,16 @@ export function NotificationFooter({
|
||||
`;
|
||||
}
|
||||
|
||||
const notificationFooterStyles = ({ theme }: { theme: Theme }) => css`
|
||||
const notificationFooterStyles = ({
|
||||
isChangeNotification,
|
||||
}: {
|
||||
isChangeNotification: boolean;
|
||||
}) => css`
|
||||
display: flex;
|
||||
background-color: ${themes[theme].background.alt};
|
||||
padding: 0 ${spacing[3]} ${spacing[3]} ${spacing[3]};
|
||||
padding: ${spacing[2]} ${spacing[4]} ${isChangeNotification ? spacing[1] : spacing[4]}
|
||||
${spacing[4]};
|
||||
|
||||
:last-child {
|
||||
border-radius: 0 0 ${spacing["4"]} ${spacing["4"]};
|
||||
padding-bottom: ${spacing[4]};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -62,7 +62,7 @@ const buttonRowStyles = css`
|
||||
|
||||
> button {
|
||||
max-width: min-content;
|
||||
flex: 1 1 50%;
|
||||
flex: 1 1 25%;
|
||||
}
|
||||
|
||||
> div {
|
||||
|
||||
@@ -21,6 +21,8 @@ export const RedirectFocusDirection = {
|
||||
Next: "next",
|
||||
} as const;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum InlineMenuFillType {
|
||||
AccountCreationUsername = 5,
|
||||
PasswordGeneration = 6,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
AssertCredentialResult,
|
||||
} from "@bitwarden/common/platform/abstractions/fido2/fido2-client.service.abstraction";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum MessageType {
|
||||
CredentialCreationRequest,
|
||||
CredentialCreationResponse,
|
||||
|
||||
@@ -59,9 +59,7 @@ function getI18n() {
|
||||
collection: chrome.i18n.getMessage("collection"),
|
||||
folder: chrome.i18n.getMessage("folder"),
|
||||
loginSaveSuccess: chrome.i18n.getMessage("loginSaveSuccess"),
|
||||
loginSaveConfirmation: chrome.i18n.getMessage("loginSaveConfirmation"),
|
||||
loginUpdateSuccess: chrome.i18n.getMessage("loginUpdateSuccess"),
|
||||
loginUpdatedConfirmation: chrome.i18n.getMessage("loginUpdatedConfirmation"),
|
||||
loginUpdateTaskSuccess: chrome.i18n.getMessage("loginUpdateTaskSuccess"),
|
||||
loginUpdateTaskSuccessAdditional: chrome.i18n.getMessage("loginUpdateTaskSuccessAdditional"),
|
||||
nextSecurityTaskAction: chrome.i18n.getMessage("nextSecurityTaskAction"),
|
||||
@@ -74,6 +72,10 @@ function getI18n() {
|
||||
notificationUpdate: chrome.i18n.getMessage("notificationChangeSave"),
|
||||
notificationEdit: chrome.i18n.getMessage("edit"),
|
||||
notificationEditTooltip: chrome.i18n.getMessage("notificationEditTooltip"),
|
||||
notificationLoginSaveConfirmation: chrome.i18n.getMessage("notificationLoginSaveConfirmation"),
|
||||
notificationLoginUpdatedConfirmation: chrome.i18n.getMessage(
|
||||
"notificationLoginUpdatedConfirmation",
|
||||
),
|
||||
notificationUnlock: chrome.i18n.getMessage("notificationUnlock"),
|
||||
notificationUnlockDesc: chrome.i18n.getMessage("notificationUnlockDesc"),
|
||||
notificationViewAria: chrome.i18n.getMessage("notificationViewAria"),
|
||||
|
||||
@@ -15,6 +15,7 @@ export type NotificationsExtensionMessage = {
|
||||
typeData?: NotificationTypeData;
|
||||
height?: number;
|
||||
error?: string;
|
||||
closedByUser?: boolean;
|
||||
fadeOutNotification?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -106,13 +106,15 @@ export class OverlayNotificationsContentService
|
||||
* @param message - The message containing the data for closing the notification bar.
|
||||
*/
|
||||
private handleCloseNotificationBarMessage(message: NotificationsExtensionMessage) {
|
||||
const closedByUser =
|
||||
typeof message.data?.closedByUser === "boolean" ? message.data.closedByUser : true;
|
||||
if (message.data?.fadeOutNotification) {
|
||||
setElementStyles(this.notificationBarIframeElement, { opacity: "0" }, true);
|
||||
globalThis.setTimeout(() => this.closeNotificationBar(true), 150);
|
||||
globalThis.setTimeout(() => this.closeNotificationBar(closedByUser), 150);
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeNotificationBar(true);
|
||||
this.closeNotificationBar(closedByUser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,5 +87,9 @@ export abstract class AutofillService {
|
||||
cipherType?: CipherType,
|
||||
) => Promise<string | null>;
|
||||
setAutoFillOnPageLoadOrgPolicy: () => Promise<void>;
|
||||
isPasswordRepromptRequired: (cipher: CipherView, tab: chrome.tabs.Tab) => Promise<boolean>;
|
||||
isPasswordRepromptRequired: (
|
||||
cipher: CipherView,
|
||||
tab: chrome.tabs.Tab,
|
||||
action?: string,
|
||||
) => Promise<boolean>;
|
||||
}
|
||||
|
||||
@@ -593,15 +593,20 @@ export default class AutofillService implements AutofillServiceInterface {
|
||||
*
|
||||
* @param cipher - The cipher to autofill
|
||||
* @param tab - The tab to autofill
|
||||
* @param action - override for default action once reprompt is completed successfully
|
||||
*/
|
||||
async isPasswordRepromptRequired(cipher: CipherView, tab: chrome.tabs.Tab): Promise<boolean> {
|
||||
async isPasswordRepromptRequired(
|
||||
cipher: CipherView,
|
||||
tab: chrome.tabs.Tab,
|
||||
action?: string,
|
||||
): Promise<boolean> {
|
||||
const userHasMasterPasswordAndKeyHash =
|
||||
await this.userVerificationService.hasMasterPasswordAndMasterKeyHash();
|
||||
if (cipher.reprompt === CipherRepromptType.Password && userHasMasterPasswordAndKeyHash) {
|
||||
if (!this.isDebouncingPasswordRepromptPopout()) {
|
||||
await this.openVaultItemPasswordRepromptPopout(tab, {
|
||||
cipherId: cipher.id,
|
||||
action: "autofill",
|
||||
action: action ?? "autofill",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -664,6 +664,10 @@ export class BrowserApi {
|
||||
* Identifies if the browser autofill settings are overridden by the extension.
|
||||
*/
|
||||
static async browserAutofillSettingsOverridden(): Promise<boolean> {
|
||||
if (!(await BrowserApi.permissionsGranted(["privacy"]))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResult<boolean>) =>
|
||||
details.levelOfControl === "controlled_by_this_extension" && !details.value;
|
||||
|
||||
|
||||
@@ -50,17 +50,40 @@ export class PopupSizeService {
|
||||
PopupSizeService.setStyle(width);
|
||||
localStorage.setItem(PopupSizeService.LocalStorageKey, width);
|
||||
});
|
||||
}
|
||||
|
||||
async setHeight() {
|
||||
const isInChromeTab = await BrowserPopupUtils.isInTab();
|
||||
|
||||
/**
|
||||
* To support both browser default zoom and system default zoom, we need to take into account
|
||||
* the full screen height. When system default zoom is >100%, window.innerHeight still outputs
|
||||
* a height equivalent to what it would be at 100%, which can cause the extension window to
|
||||
* render as too tall. So if the screen height is smaller than the max possible extension height,
|
||||
* we should use that to set our extension height. Otherwise, we want to use the window.innerHeight
|
||||
* to support browser zoom.
|
||||
*
|
||||
* This is basically a workaround for what we consider a bug with browsers reporting the wrong
|
||||
* available innerHeight when system zoom is turned on. If that gets fixed, we can remove the code
|
||||
* checking the screen height.
|
||||
*/
|
||||
const MAX_EXT_HEIGHT = 600;
|
||||
const extensionInnerHeight = window.innerHeight;
|
||||
// Use a 100px offset when calculating screen height to account for browser container elements
|
||||
const screenAvailHeight = window.screen.availHeight - 100;
|
||||
const availHeight =
|
||||
screenAvailHeight < MAX_EXT_HEIGHT ? screenAvailHeight : extensionInnerHeight;
|
||||
|
||||
if (!BrowserPopupUtils.inPopup(window) || isInChromeTab) {
|
||||
window.document.body.classList.add("body-full");
|
||||
} else if (window.innerHeight < 400) {
|
||||
window.document.body.classList.add("body-xxs");
|
||||
} else if (window.innerHeight < 500) {
|
||||
window.document.body.classList.add("body-xs");
|
||||
} else if (window.innerHeight < 600) {
|
||||
window.document.body.classList.add("body-sm");
|
||||
window.document.documentElement.classList.add("body-full");
|
||||
} else if (availHeight < 300) {
|
||||
window.document.documentElement.classList.add("body-3xs");
|
||||
} else if (availHeight < 400) {
|
||||
window.document.documentElement.classList.add("body-xxs");
|
||||
} else if (availHeight < 500) {
|
||||
window.document.documentElement.classList.add("body-xs");
|
||||
} else if (availHeight < 600) {
|
||||
window.document.documentElement.classList.add("body-sm");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
FormCacheOptions,
|
||||
SignalCacheOptions,
|
||||
ViewCacheService,
|
||||
} from "@bitwarden/angular/platform/abstractions/view-cache.service";
|
||||
} from "@bitwarden/angular/platform/view-cache";
|
||||
import { MessageSender } from "@bitwarden/common/platform/messaging";
|
||||
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { BiometricsService, BiometricStateService } from "@bitwarden/key-management";
|
||||
|
||||
import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service";
|
||||
import { PopupSizeService } from "../platform/popup/layout/popup-size.service";
|
||||
import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service";
|
||||
import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service";
|
||||
|
||||
@@ -71,6 +72,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private biometricStateService: BiometricStateService,
|
||||
private biometricsService: BiometricsService,
|
||||
private deviceTrustToastService: DeviceTrustToastService,
|
||||
private popupSizeService: PopupSizeService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
}
|
||||
@@ -79,6 +81,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
initPopupClosedListener();
|
||||
|
||||
this.compactModeService.init();
|
||||
await this.popupSizeService.setHeight();
|
||||
|
||||
// Component states must not persist between closing and reopening the popup, otherwise they become dead objects
|
||||
// Clear them aggressively to make sure this doesn't occur
|
||||
|
||||
@@ -8,6 +8,34 @@
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
min-height: 600px;
|
||||
height: 100%;
|
||||
|
||||
&.body-sm {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
&.body-xs {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
&.body-xxs {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
&.body-3xs {
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
&.body-full {
|
||||
min-height: unset;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
& body {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
@@ -20,9 +48,9 @@ body {
|
||||
|
||||
body {
|
||||
width: 380px;
|
||||
height: 600px;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
min-height: inherit;
|
||||
overflow: hidden;
|
||||
color: $text-color;
|
||||
background-color: $background-color;
|
||||
@@ -31,23 +59,6 @@ body {
|
||||
color: themed("textColor");
|
||||
background-color: themed("backgroundColor");
|
||||
}
|
||||
|
||||
&.body-sm {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
&.body-xs {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
&.body-xxs {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
&.body-full {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Router } from "@angular/router";
|
||||
import { merge, of, Subject } from "rxjs";
|
||||
|
||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||
import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service";
|
||||
import { AngularThemingService } from "@bitwarden/angular/platform/services/theming/angular-theming.service";
|
||||
import { SafeProvider, safeProvider } from "@bitwarden/angular/platform/utils/safe-provider";
|
||||
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
|
||||
import {
|
||||
CLIENT_TYPE,
|
||||
DEFAULT_VAULT_TIMEOUT,
|
||||
|
||||
@@ -26,6 +26,8 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SendState {
|
||||
Empty,
|
||||
NoResults,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
*ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1</span
|
||||
>
|
||||
</div>
|
||||
@@ -53,6 +54,7 @@
|
||||
*ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1</span
|
||||
>
|
||||
</div>
|
||||
@@ -83,6 +85,7 @@
|
||||
*ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
>1
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { inject } from "@angular/core";
|
||||
import { CanActivateFn } from "@angular/router";
|
||||
import { switchMap, tap } from "rxjs";
|
||||
import { CanActivateFn, Router } from "@angular/router";
|
||||
import { map, switchMap } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
@@ -13,18 +13,22 @@ export const canAccessAtRiskPasswords: CanActivateFn = () => {
|
||||
const taskService = inject(TaskService);
|
||||
const toastService = inject(ToastService);
|
||||
const i18nService = inject(I18nService);
|
||||
const router = inject(Router);
|
||||
|
||||
return accountService.activeAccount$.pipe(
|
||||
filterOutNullish(),
|
||||
switchMap((user) => taskService.tasksEnabled$(user.id)),
|
||||
tap((tasksEnabled) => {
|
||||
map((tasksEnabled) => {
|
||||
if (!tasksEnabled) {
|
||||
toastService.showToast({
|
||||
variant: "error",
|
||||
title: "",
|
||||
message: i18nService.t("accessDenied"),
|
||||
message: i18nService.t("noPermissionsViewPage"),
|
||||
});
|
||||
|
||||
return router.createUrlTree(["/tabs/vault"]);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
import { DarkImageSourceDirective, VaultCarouselModule } from "@bitwarden/vault";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AtRiskCarouselDialogResult {
|
||||
Dismissed = "dismissed",
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface GeneratorDialogResult {
|
||||
generatedValue?: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum GeneratorDialogAction {
|
||||
Selected = "selected",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -55,6 +55,8 @@ import { VaultPageService } from "./vault-page.service";
|
||||
|
||||
import { AutofillVaultListItemsComponent, VaultListItemsContainerComponent } from ".";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum VaultState {
|
||||
Empty,
|
||||
NoResults,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
COPY_USERNAME_ID,
|
||||
COPY_VERIFICATION_CODE_ID,
|
||||
SHOW_AUTOFILL_BUTTON,
|
||||
UPDATE_PASSWORD,
|
||||
} from "@bitwarden/common/autofill/constants";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
@@ -49,6 +50,7 @@ import {
|
||||
PasswordRepromptService,
|
||||
} from "@bitwarden/vault";
|
||||
|
||||
import { sendExtensionMessage } from "../../../../../autofill/utils/index";
|
||||
import { BrowserApi } from "../../../../../platform/browser/browser-api";
|
||||
import BrowserPopupUtils from "../../../../../platform/popup/browser-popup-utils";
|
||||
import { PopOutComponent } from "../../../../../platform/popup/components/pop-out.component";
|
||||
@@ -72,7 +74,8 @@ type LoadAction =
|
||||
| typeof SHOW_AUTOFILL_BUTTON
|
||||
| typeof COPY_USERNAME_ID
|
||||
| typeof COPY_PASSWORD_ID
|
||||
| typeof COPY_VERIFICATION_CODE_ID;
|
||||
| typeof COPY_VERIFICATION_CODE_ID
|
||||
| typeof UPDATE_PASSWORD;
|
||||
|
||||
@Component({
|
||||
selector: "app-view-v2",
|
||||
@@ -294,7 +297,7 @@ export class ViewV2Component {
|
||||
// Both vaultPopupAutofillService and copyCipherFieldService will perform password re-prompting internally.
|
||||
|
||||
switch (loadAction) {
|
||||
case "show-autofill-button":
|
||||
case SHOW_AUTOFILL_BUTTON:
|
||||
// This action simply shows the cipher view, no need to do anything.
|
||||
if (
|
||||
this.cipher.reprompt !== CipherRepromptType.None &&
|
||||
@@ -303,30 +306,42 @@ export class ViewV2Component {
|
||||
await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`);
|
||||
}
|
||||
return;
|
||||
case "autofill":
|
||||
case AUTOFILL_ID:
|
||||
actionSuccess = await this.vaultPopupAutofillService.doAutofill(this.cipher, false);
|
||||
break;
|
||||
case "copy-username":
|
||||
case COPY_USERNAME_ID:
|
||||
actionSuccess = await this.copyCipherFieldService.copy(
|
||||
this.cipher.login.username,
|
||||
"username",
|
||||
this.cipher,
|
||||
);
|
||||
break;
|
||||
case "copy-password":
|
||||
case COPY_PASSWORD_ID:
|
||||
actionSuccess = await this.copyCipherFieldService.copy(
|
||||
this.cipher.login.password,
|
||||
"password",
|
||||
this.cipher,
|
||||
);
|
||||
break;
|
||||
case "copy-totp":
|
||||
case COPY_VERIFICATION_CODE_ID:
|
||||
actionSuccess = await this.copyCipherFieldService.copy(
|
||||
this.cipher.login.totp,
|
||||
"totp",
|
||||
this.cipher,
|
||||
);
|
||||
break;
|
||||
case UPDATE_PASSWORD: {
|
||||
const repromptSuccess = await this.passwordRepromptService.showPasswordPrompt();
|
||||
|
||||
await sendExtensionMessage("bgHandleReprompt", {
|
||||
tab: await chrome.tabs.get(senderTabId),
|
||||
success: repromptSuccess,
|
||||
});
|
||||
|
||||
await closeViewVaultItemPopout(`${VaultPopoutType.viewVaultItem}_${this.cipher.id}`);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (BrowserPopupUtils.inPopout(window)) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FormBuilder } from "@angular/forms";
|
||||
import { BehaviorSubject, skipWhile } from "rxjs";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
|
||||
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";
|
||||
@@ -20,8 +21,6 @@ import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
|
||||
import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service";
|
||||
|
||||
import {
|
||||
CachedFilterState,
|
||||
MY_VAULT_ID,
|
||||
@@ -123,7 +122,7 @@ describe("VaultPopupListFiltersService", () => {
|
||||
useValue: accountService,
|
||||
},
|
||||
{
|
||||
provide: PopupViewCacheService,
|
||||
provide: ViewCacheService,
|
||||
useValue: viewCacheService,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "rxjs";
|
||||
|
||||
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
|
||||
import { ViewCacheService } from "@bitwarden/angular/platform/view-cache";
|
||||
import { DynamicTreeNode } from "@bitwarden/angular/vault/vault-filter/models/dynamic-tree-node.model";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
@@ -40,8 +41,6 @@ import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
|
||||
import { ServiceUtils } from "@bitwarden/common/vault/service-utils";
|
||||
import { ChipSelectOption } from "@bitwarden/components";
|
||||
|
||||
import { PopupViewCacheService } from "../../../platform/popup/view-cache/popup-view-cache.service";
|
||||
|
||||
const FILTER_VISIBILITY_KEY = new KeyDefinition<boolean>(VAULT_SETTINGS_DISK, "filterVisibility", {
|
||||
deserializer: (obj) => obj,
|
||||
});
|
||||
@@ -178,7 +177,7 @@ export class VaultPopupListFiltersService {
|
||||
private policyService: PolicyService,
|
||||
private stateProvider: StateProvider,
|
||||
private accountService: AccountService,
|
||||
private viewCacheService: PopupViewCacheService,
|
||||
private viewCacheService: ViewCacheService,
|
||||
) {
|
||||
this.filterForm.controls.organization.valueChanges
|
||||
.pipe(takeUntilDestroyed())
|
||||
|
||||
@@ -64,13 +64,14 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@koa/multer": "3.0.2",
|
||||
"@koa/multer": "3.1.0",
|
||||
"@koa/router": "13.1.0",
|
||||
"argon2": "0.41.1",
|
||||
"big-integer": "1.6.52",
|
||||
"browser-hrtime": "1.1.8",
|
||||
"chalk": "4.1.2",
|
||||
"commander": "11.1.0",
|
||||
"core-js": "3.40.0",
|
||||
"form-data": "4.0.1",
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"inquirer": "8.2.6",
|
||||
@@ -81,7 +82,7 @@
|
||||
"koa-json": "2.0.2",
|
||||
"lowdb": "1.0.0",
|
||||
"lunr": "2.3.9",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"multer": "1.4.5-lts.2",
|
||||
"node-fetch": "2.6.12",
|
||||
"node-forge": "1.3.1",
|
||||
"open": "8.4.2",
|
||||
|
||||
@@ -106,6 +106,8 @@ export class LoginCommand {
|
||||
return Response.badRequest("client_secret is required.");
|
||||
}
|
||||
} else if (options.sso != null && this.canInteract) {
|
||||
// If the optional Org SSO Identifier isn't provided, the option value is `true`.
|
||||
const orgSsoIdentifier = options.sso === true ? null : options.sso;
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
length: 64,
|
||||
@@ -119,7 +121,7 @@ export class LoginCommand {
|
||||
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
|
||||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||
try {
|
||||
const ssoParams = await this.openSsoPrompt(codeChallenge, state);
|
||||
const ssoParams = await this.openSsoPrompt(codeChallenge, state, orgSsoIdentifier);
|
||||
ssoCode = ssoParams.ssoCode;
|
||||
orgIdentifier = ssoParams.orgIdentifier;
|
||||
} catch {
|
||||
@@ -664,6 +666,7 @@ export class LoginCommand {
|
||||
private async openSsoPrompt(
|
||||
codeChallenge: string,
|
||||
state: string,
|
||||
orgSsoIdentifier: string,
|
||||
): Promise<{ ssoCode: string; orgIdentifier: string }> {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
|
||||
@@ -712,6 +715,8 @@ export class LoginCommand {
|
||||
this.ssoRedirectUri,
|
||||
state,
|
||||
codeChallenge,
|
||||
null,
|
||||
orgSsoIdentifier,
|
||||
);
|
||||
this.platformUtilsService.launchUri(webAppSsoUrl);
|
||||
});
|
||||
|
||||
@@ -118,7 +118,10 @@ export class Program extends BaseProgram {
|
||||
.description("Log into a user account.")
|
||||
.option("--method <method>", "Two-step login method.")
|
||||
.option("--code <code>", "Two-step login code.")
|
||||
.option("--sso", "Log in with Single-Sign On.")
|
||||
.option(
|
||||
"--sso [identifier]",
|
||||
"Log in with Single-Sign On with optional organization identifier.",
|
||||
)
|
||||
.option("--apikey", "Log in with an Api Key.")
|
||||
.option("--passwordenv <passwordenv>", "Environment variable storing your password")
|
||||
.option(
|
||||
|
||||
@@ -15,6 +15,8 @@ const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds
|
||||
|
||||
export type MessageHandler = (MessageCommon) => void;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum IPCConnectionState {
|
||||
Disconnected = "disconnected",
|
||||
Connecting = "connecting",
|
||||
|
||||
@@ -25,6 +25,8 @@ import { SearchBarService } from "../../layout/search/search-bar.service";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum Action {
|
||||
None = "",
|
||||
Add = "add",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SshAgentPromptType {
|
||||
Always = "always",
|
||||
Never = "never",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum BiometricAction {
|
||||
Authenticate = "authenticate",
|
||||
GetStatus = "status",
|
||||
|
||||
@@ -31,6 +31,8 @@ export interface CredentialGeneratorDialogResult {
|
||||
generatedValue?: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CredentialGeneratorDialogAction {
|
||||
Selected = "selected",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -43,6 +43,8 @@ export interface BulkCollectionsDialogParams {
|
||||
collections: CollectionView[];
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum BulkCollectionsDialogResult {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -132,6 +132,8 @@ import { VaultHeaderComponent } from "./vault-header/vault-header.component";
|
||||
const BroadcasterSubscriptionId = "OrgVaultComponent";
|
||||
const SearchTextDebounceInterval = 200;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum AddAccessStatusType {
|
||||
All = 0,
|
||||
AddAccess = 1,
|
||||
|
||||
@@ -58,6 +58,8 @@ import { AddEditGroupDetail } from "./../core/views/add-edit-group-detail";
|
||||
/**
|
||||
* Indices for the available tabs in the dialog
|
||||
*/
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum GroupAddEditTabType {
|
||||
Info = 0,
|
||||
Members = 1,
|
||||
@@ -82,6 +84,8 @@ export interface GroupAddEditDialogParams {
|
||||
initialTab?: GroupAddEditTabType;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum GroupAddEditDialogResultType {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -64,6 +64,8 @@ import { commaSeparatedEmails } from "./validators/comma-separated-emails.valida
|
||||
import { inputEmailLimitValidator } from "./validators/input-email-limit.validator";
|
||||
import { orgSeatLimitReachedValidator } from "./validators/org-seat-limit-reached.validator";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum MemberDialogTab {
|
||||
Role = 0,
|
||||
Groups = 1,
|
||||
@@ -92,6 +94,8 @@ export interface EditMemberDialogParams extends CommonMemberDialogParams {
|
||||
|
||||
export type MemberDialogParams = EditMemberDialogParams | AddMemberDialogParams;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum MemberDialogResult {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -50,6 +50,8 @@ export type ResetPasswordDialogData = {
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ResetPasswordDialogResult {
|
||||
Ok = "ok",
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class OrganizationUserResetPasswordService
|
||||
if (orgSymKey == null) {
|
||||
throw new Error("No org key found");
|
||||
}
|
||||
const decPrivateKey = await this.encryptService.decryptToBytes(
|
||||
const decPrivateKey = await this.encryptService.unwrapDecapsulationKey(
|
||||
new EncString(response.encryptedPrivateKey),
|
||||
orgSymKey,
|
||||
);
|
||||
|
||||
@@ -41,6 +41,8 @@ export type PolicyEditDialogData = {
|
||||
organizationId: string;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PolicyEditDialogResult {
|
||||
Saved = "saved",
|
||||
UpgradePlan = "upgrade-plan",
|
||||
|
||||
@@ -71,6 +71,8 @@ export interface DeleteOrganizationDialogParams {
|
||||
requestType: "InvalidFamiliesForEnterprise" | "RegularDelete";
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum DeleteOrganizationDialogResult {
|
||||
Deleted = "deleted",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
Permission,
|
||||
} from "./access-selector.models";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PermissionMode {
|
||||
/**
|
||||
* No permission controls or column present. No permission values are emitted.
|
||||
|
||||
@@ -15,6 +15,8 @@ import { GroupView } from "../../../core";
|
||||
/**
|
||||
* Permission options that replace/correspond with manage, readOnly, and hidePassword server fields.
|
||||
*/
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CollectionPermission {
|
||||
View = "view",
|
||||
ViewExceptPass = "viewExceptPass",
|
||||
@@ -23,6 +25,8 @@ export enum CollectionPermission {
|
||||
Manage = "manage",
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AccessItemType {
|
||||
Collection,
|
||||
Group,
|
||||
|
||||
@@ -65,6 +65,8 @@ import {
|
||||
} from "../access-selector/access-selector.models";
|
||||
import { AccessSelectorModule } from "../access-selector/access-selector.module";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CollectionDialogTabType {
|
||||
Info = 0,
|
||||
Access = 1,
|
||||
@@ -76,6 +78,8 @@ export enum CollectionDialogTabType {
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum ButtonType {
|
||||
/** Displayed when the user has reached the maximum number of collections allowed for the organization. */
|
||||
Upgrade = "upgrade",
|
||||
@@ -103,6 +107,8 @@ export interface CollectionDialogResult {
|
||||
collection: CollectionResponse | CollectionView;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CollectionDialogAction {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum WebauthnLoginCredentialPrfStatus {
|
||||
Enabled = 0,
|
||||
Supported = 1,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum EmergencyAccessStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum EmergencyAccessType {
|
||||
View = 0,
|
||||
Takeover = 1,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { DialogConfig, DialogRef, DIALOG_DATA, DialogService } from "@bitwarden/components";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum EmergencyAccessConfirmDialogResult {
|
||||
Confirmed = "confirmed",
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export type EmergencyAccessAddEditDialogData = {
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum EmergencyAccessAddEditDialogResult {
|
||||
Saved = "saved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -24,6 +24,8 @@ import { KdfType, KdfConfigService, KeyService } from "@bitwarden/key-management
|
||||
|
||||
import { EmergencyAccessService } from "../../../emergency-access";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum EmergencyAccessTakeoverResultType {
|
||||
Done = "done",
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { PendingWebauthnLoginCredentialView } from "../../../core/views/pending-
|
||||
import { CreatePasskeyFailedIcon } from "./create-passkey-failed.icon";
|
||||
import { CreatePasskeyIcon } from "./create-passkey.icon";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CreateCredentialDialogResult {
|
||||
Success,
|
||||
}
|
||||
|
||||
@@ -51,8 +51,38 @@
|
||||
<h2 class="tw-mb-3 tw-text-base tw-font-semibold">{{ "paymentType" | i18n }}</h2>
|
||||
<app-payment [showAccountCredit]="false"></app-payment>
|
||||
<app-manage-tax-information
|
||||
(taxInformationChanged)="changedCountry()"
|
||||
(taxInformationChanged)="onTaxInformationChanged()"
|
||||
></app-manage-tax-information>
|
||||
|
||||
@if (trialLength === 0) {
|
||||
@let priceLabel =
|
||||
subscriptionProduct === SubscriptionProduct.PasswordManager
|
||||
? "passwordManagerPlanPrice"
|
||||
: "secretsManagerPlanPrice";
|
||||
|
||||
<div id="price" class="tw-my-4">
|
||||
<div class="tw-text-muted tw-text-base">
|
||||
{{ priceLabel | i18n }}: {{ getPriceFor(formGroup.value.cadence) | currency: "USD $" }}
|
||||
<div>
|
||||
{{ "estimatedTax" | i18n }}:
|
||||
@if (fetchingTaxAmount) {
|
||||
<ng-container *ngTemplateOutlet="loadingSpinner" />
|
||||
} @else {
|
||||
{{ taxAmount | currency: "USD $" }}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<hr class="tw-my-1 tw-grid tw-grid-cols-3 tw-ml-0" />
|
||||
<p class="tw-text-lg">
|
||||
<strong>{{ "total" | i18n }}: </strong>
|
||||
@if (fetchingTaxAmount) {
|
||||
<ng-container *ngTemplateOutlet="loadingSpinner" />
|
||||
} @else {
|
||||
{{ total | currency: "USD $" }}/{{ interval | i18n }}
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="tw-flex tw-space-x-2">
|
||||
<button type="submit" buttonType="primary" bitButton [loading]="form.loading">
|
||||
@@ -62,3 +92,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ng-template #loadingSpinner>
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from "@angular/core";
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild,
|
||||
} from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { from, Subject, switchMap, takeUntil } from "rxjs";
|
||||
|
||||
import { ManageTaxInformationComponent } from "@bitwarden/angular/billing/components";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -12,7 +21,14 @@ import {
|
||||
PaymentInformation,
|
||||
PlanInformation,
|
||||
} from "@bitwarden/common/billing/abstractions/organization-billing.service";
|
||||
import { PaymentMethodType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { TaxServiceAbstraction } from "@bitwarden/common/billing/abstractions/tax.service.abstraction";
|
||||
import {
|
||||
PaymentMethodType,
|
||||
PlanType,
|
||||
ProductTierType,
|
||||
ProductType,
|
||||
} from "@bitwarden/common/billing/enums";
|
||||
import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax";
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
@@ -34,11 +50,15 @@ export interface OrganizationCreatedEvent {
|
||||
planDescription: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum SubscriptionCadence {
|
||||
Annual,
|
||||
Monthly,
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SubscriptionProduct {
|
||||
PasswordManager,
|
||||
SecretsManager,
|
||||
@@ -50,7 +70,7 @@ export enum SubscriptionProduct {
|
||||
imports: [BillingSharedModule],
|
||||
standalone: true,
|
||||
})
|
||||
export class TrialBillingStepComponent implements OnInit {
|
||||
export class TrialBillingStepComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(PaymentComponent) paymentComponent: PaymentComponent;
|
||||
@ViewChild(ManageTaxInformationComponent) taxInfoComponent: ManageTaxInformationComponent;
|
||||
@Input() organizationInfo: OrganizationInfo;
|
||||
@@ -60,6 +80,7 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
@Output() organizationCreated = new EventEmitter<OrganizationCreatedEvent>();
|
||||
|
||||
loading = true;
|
||||
fetchingTaxAmount = false;
|
||||
|
||||
annualCadence = SubscriptionCadence.Annual;
|
||||
monthlyCadence = SubscriptionCadence.Monthly;
|
||||
@@ -73,6 +94,12 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
annualPlan?: PlanResponse;
|
||||
monthlyPlan?: PlanResponse;
|
||||
|
||||
taxAmount = 0;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected readonly SubscriptionProduct = SubscriptionProduct;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private i18nService: I18nService,
|
||||
@@ -80,6 +107,7 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
private messagingService: MessagingService,
|
||||
private organizationBillingService: OrganizationBillingService,
|
||||
private toastService: ToastService,
|
||||
private taxService: TaxServiceAbstraction,
|
||||
) {}
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
@@ -87,9 +115,26 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
this.applicablePlans = plans.data.filter(this.isApplicable);
|
||||
this.annualPlan = this.findPlanFor(SubscriptionCadence.Annual);
|
||||
this.monthlyPlan = this.findPlanFor(SubscriptionCadence.Monthly);
|
||||
|
||||
if (this.trialLength === 0) {
|
||||
this.formGroup.controls.cadence.valueChanges
|
||||
.pipe(
|
||||
switchMap((cadence) => from(this.previewTaxAmount(cadence))),
|
||||
takeUntil(this.destroy$),
|
||||
)
|
||||
.subscribe((taxAmount) => {
|
||||
this.taxAmount = taxAmount;
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async submit(): Promise<void> {
|
||||
if (!this.taxInfoComponent.validate()) {
|
||||
return;
|
||||
@@ -115,7 +160,11 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
this.messagingService.send("organizationCreated", { organizationId });
|
||||
}
|
||||
|
||||
protected changedCountry() {
|
||||
async onTaxInformationChanged() {
|
||||
if (this.trialLength === 0) {
|
||||
this.taxAmount = await this.previewTaxAmount(this.formGroup.value.cadence);
|
||||
}
|
||||
|
||||
this.paymentComponent.showBankAccount =
|
||||
this.taxInfoComponent.getTaxInformation().country === "US";
|
||||
if (
|
||||
@@ -170,6 +219,7 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
const payment: PaymentInformation = {
|
||||
paymentMethod,
|
||||
billing: this.getBillingInformationFromTaxInfoComponent(),
|
||||
skipTrial: this.trialLength === 0,
|
||||
};
|
||||
|
||||
const response = await this.organizationBillingService.purchaseSubscription({
|
||||
@@ -250,4 +300,45 @@ export class TrialBillingStepComponent implements OnInit {
|
||||
const notDisabledOrLegacy = !plan.disabled && !plan.legacyYear;
|
||||
return hasCorrectProductType && notDisabledOrLegacy;
|
||||
}
|
||||
|
||||
private previewTaxAmount = async (cadence: SubscriptionCadence): Promise<number> => {
|
||||
this.fetchingTaxAmount = true;
|
||||
|
||||
if (!this.taxInfoComponent.validate()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const plan = this.findPlanFor(cadence);
|
||||
|
||||
const productType =
|
||||
this.subscriptionProduct === SubscriptionProduct.PasswordManager
|
||||
? ProductType.PasswordManager
|
||||
: ProductType.SecretsManager;
|
||||
|
||||
const taxInformation = this.taxInfoComponent.getTaxInformation();
|
||||
|
||||
const request: PreviewTaxAmountForOrganizationTrialRequest = {
|
||||
planType: plan.type,
|
||||
productType,
|
||||
taxInformation: {
|
||||
...taxInformation,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await this.taxService.previewTaxAmountForOrganizationTrial(request);
|
||||
this.fetchingTaxAmount = false;
|
||||
return response.taxAmount;
|
||||
};
|
||||
|
||||
get price() {
|
||||
return this.getPriceFor(this.formGroup.value.cadence);
|
||||
}
|
||||
|
||||
get total() {
|
||||
return this.price + this.taxAmount;
|
||||
}
|
||||
|
||||
get interval() {
|
||||
return this.formGroup.value.cadence === SubscriptionCadence.Annual ? "year" : "month";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export class FreeBitwardenFamiliesComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.deleteRevokeSponsorship(this.organizationId);
|
||||
await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.organizationId, true);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
|
||||
@@ -74,11 +74,15 @@ type ChangePlanDialogParams = {
|
||||
productTierType: ProductTierType;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ChangePlanDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum PlanCardState {
|
||||
Selected = "selected",
|
||||
NotSelected = "not_selected",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
|
||||
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
|
||||
import { DialogConfig, DIALOG_DATA, DialogRef, DialogService } from "@bitwarden/components";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum DownloadLicenseDialogResult {
|
||||
Cancelled = "cancelled",
|
||||
Downloaded = "downloaded",
|
||||
|
||||
@@ -27,6 +27,8 @@ import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
import { BillingSyncKeyComponent } from "./billing-sync-key.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
enum LicenseOptions {
|
||||
SYNC = 0,
|
||||
UPLOAD = 1,
|
||||
|
||||
@@ -112,13 +112,15 @@ export class SponsoredFamiliesComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.anyOrgsAvailable$ = this.availableSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
|
||||
|
||||
this.activeSponsorshipOrgs$ = this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(map((orgs) => orgs.filter((o) => o.familySponsorshipFriendlyName !== null)));
|
||||
|
||||
.pipe(
|
||||
map((orgs) =>
|
||||
orgs.filter((o) => o.familySponsorshipFriendlyName !== null && !o.isAdminInitiated),
|
||||
),
|
||||
);
|
||||
this.anyActiveSponsorships$ = this.activeSponsorshipOrgs$.pipe(map((orgs) => orgs.length > 0));
|
||||
|
||||
this.loading = false;
|
||||
|
||||
@@ -109,7 +109,7 @@ export class SponsoringOrgRowComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.apiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
|
||||
await this.organizationSponsorshipApiService.deleteRevokeSponsorship(this.sponsoringOrg.id);
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface AddCreditDialogData {
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AddCreditDialogResult {
|
||||
Added = "added",
|
||||
Cancelled = "cancelled",
|
||||
|
||||
@@ -30,6 +30,8 @@ export interface AdjustPaymentDialogParams {
|
||||
providerId?: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AdjustPaymentDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -22,6 +22,8 @@ export interface AdjustStorageDialogParams {
|
||||
organizationId?: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AdjustStorageDialogResultType {
|
||||
Submitted = "submitted",
|
||||
Closed = "closed",
|
||||
|
||||
@@ -25,6 +25,8 @@ type OrganizationOffboardingParams = {
|
||||
|
||||
export type OffboardingSurveyDialogParams = UserOffboardingParams | OrganizationOffboardingParams;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum OffboardingSurveyDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum UpdateLicenseDialogResult {
|
||||
Updated = "updated",
|
||||
Cancelled = "cancelled",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ReportUnsecuredWebsites } from "./icons/report-unsecured-websites.icon"
|
||||
import { ReportWeakPasswords } from "./icons/report-weak-passwords.icon";
|
||||
import { ReportEntry } from "./shared";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ReportType {
|
||||
ExposedPasswords = "exposedPasswords",
|
||||
ReusedPasswords = "reusedPasswords",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ReportVariant {
|
||||
Enabled = "Enabled",
|
||||
RequiresPremium = "RequiresPremium",
|
||||
|
||||
@@ -22,6 +22,7 @@ import { DangerZoneComponent } from "../auth/settings/account/danger-zone.compon
|
||||
import { DeauthorizeSessionsComponent } from "../auth/settings/account/deauthorize-sessions.component";
|
||||
import { DeleteAccountDialogComponent } from "../auth/settings/account/delete-account-dialog.component";
|
||||
import { ProfileComponent } from "../auth/settings/account/profile.component";
|
||||
import { SelectableAvatarComponent } from "../auth/settings/account/selectable-avatar.component";
|
||||
import { EmergencyAccessConfirmComponent } from "../auth/settings/emergency-access/confirm/emergency-access-confirm.component";
|
||||
import { EmergencyAccessAddEditComponent } from "../auth/settings/emergency-access/emergency-access-add-edit.component";
|
||||
import { EmergencyAccessComponent } from "../auth/settings/emergency-access/emergency-access.component";
|
||||
@@ -39,7 +40,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp
|
||||
import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component";
|
||||
import { DynamicAvatarComponent } from "../components/dynamic-avatar.component";
|
||||
import { SelectableAvatarComponent } from "../components/selectable-avatar.component";
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component";
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../dirt/reports/pages/organizations/inactive-two-factor-report.component";
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../dirt/reports/pages/organizations/reused-passwords-report.component";
|
||||
|
||||
@@ -95,6 +95,8 @@ export interface VaultItemDialogParams {
|
||||
restore?: (c: CipherView) => Promise<boolean>;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum VaultItemDialogResult {
|
||||
/**
|
||||
* A cipher was saved (created or updated).
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface WebVaultGeneratorDialogResult {
|
||||
generatedValue?: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum WebVaultGeneratorDialogAction {
|
||||
Selected = "selected",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -35,6 +35,8 @@ import { WebCipherFormGenerationService } from "../services/web-cipher-form-gene
|
||||
/**
|
||||
* The result of the AddEditCipherDialogV2 component.
|
||||
*/
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AddEditCipherDialogResult {
|
||||
Edited = "edited",
|
||||
Added = "added",
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface BulkDeleteDialogParams {
|
||||
unassignedCiphers?: string[];
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum BulkDeleteDialogResult {
|
||||
Deleted = "deleted",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface BulkMoveDialogParams {
|
||||
cipherIds?: string[];
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum BulkMoveDialogResult {
|
||||
Moved = "moved",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -113,6 +113,8 @@ export interface FolderAddEditDialogParams {
|
||||
folderId: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum FolderAddEditDialogResult {
|
||||
Deleted = "deleted",
|
||||
Canceled = "canceled",
|
||||
|
||||
@@ -17,6 +17,8 @@ import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
import { PBKDF2KdfConfig, KdfConfigService, KdfType } from "@bitwarden/key-management";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum VisibleVaultBanner {
|
||||
KDFSettings = "kdf-settings",
|
||||
OutdatedBrowser = "outdated-browser",
|
||||
|
||||
@@ -15,6 +15,8 @@ export type VaultFilterType =
|
||||
| FolderFilter
|
||||
| CollectionFilter;
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum VaultFilterLabel {
|
||||
OrganizationFilter = "organizationFilter",
|
||||
TypeFilter = "typeFilter",
|
||||
|
||||
@@ -54,6 +54,8 @@ export interface ViewCipherDialogParams {
|
||||
disableEdit?: boolean;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ViewCipherDialogResult {
|
||||
Edited = "edited",
|
||||
Deleted = "deleted",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { VaultMessages } from "@bitwarden/common/vault/enums/vault-messages.enum";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum BrowserPromptState {
|
||||
Loading = "loading",
|
||||
Error = "error",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "core-js/proposals/explicit-resource-management";
|
||||
|
||||
import { program } from "commander";
|
||||
|
||||
import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs";
|
||||
|
||||
@@ -149,6 +149,8 @@ export interface PasswordHealthReportApplicationsRequest {
|
||||
url: string;
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum DrawerType {
|
||||
None = 0,
|
||||
AppAtRiskMembers = 1,
|
||||
|
||||
@@ -50,7 +50,7 @@ describe("RiskInsightsReportService", () => {
|
||||
let testCase = testCaseResults[0];
|
||||
expect(testCase).toBeTruthy();
|
||||
expect(testCase.cipherMembers).toHaveLength(2);
|
||||
expect(testCase.trimmedUris).toHaveLength(3);
|
||||
expect(testCase.trimmedUris).toHaveLength(2);
|
||||
expect(testCase.weakPasswordDetail).toBeTruthy();
|
||||
expect(testCase.exposedPasswordDetail).toBeTruthy();
|
||||
expect(testCase.reusedPasswordCount).toEqual(2);
|
||||
@@ -69,7 +69,7 @@ describe("RiskInsightsReportService", () => {
|
||||
it("should generate the raw data + uri report correctly", async () => {
|
||||
const result = await firstValueFrom(service.generateRawDataUriReport$("orgId"));
|
||||
|
||||
expect(result).toHaveLength(9);
|
||||
expect(result).toHaveLength(8);
|
||||
|
||||
// Two ciphers that have google.com as their uri. There should be 2 results
|
||||
const googleResults = result.filter((x) => x.trimmedUri === "google.com");
|
||||
@@ -88,7 +88,7 @@ describe("RiskInsightsReportService", () => {
|
||||
it("should generate applications health report data correctly", async () => {
|
||||
const result = await firstValueFrom(service.generateApplicationsReport$("orgId"));
|
||||
|
||||
expect(result).toHaveLength(6);
|
||||
expect(result).toHaveLength(5);
|
||||
|
||||
// Two ciphers have google.com associated with them. The first cipher
|
||||
// has 2 members and the second has 4. However, the 2 members in the first
|
||||
@@ -132,7 +132,7 @@ describe("RiskInsightsReportService", () => {
|
||||
|
||||
expect(reportSummary.totalMemberCount).toEqual(7);
|
||||
expect(reportSummary.totalAtRiskMemberCount).toEqual(6);
|
||||
expect(reportSummary.totalApplicationCount).toEqual(6);
|
||||
expect(reportSummary.totalAtRiskApplicationCount).toEqual(5);
|
||||
expect(reportSummary.totalApplicationCount).toEqual(5);
|
||||
expect(reportSummary.totalAtRiskApplicationCount).toEqual(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -428,7 +428,7 @@ export class RiskInsightsReportService {
|
||||
const cipherUris: string[] = [];
|
||||
const uris = cipher.login?.uris ?? [];
|
||||
uris.map((u: { uri: string }) => {
|
||||
const uri = Utils.getHostname(u.uri).replace("www.", "");
|
||||
const uri = Utils.getDomain(u.uri);
|
||||
if (!cipherUris.includes(uri)) {
|
||||
cipherUris.push(uri);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ export type AddEditMemberDialogParams = {
|
||||
};
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AddEditMemberDialogResultType {
|
||||
Closed = "closed",
|
||||
Deleted = "deleted",
|
||||
|
||||
@@ -74,7 +74,7 @@ export class WebProviderService {
|
||||
|
||||
const [publicKey, encryptedPrivateKey] = await this.keyService.makeKeyPair(organizationKey);
|
||||
|
||||
const encryptedCollectionName = await this.encryptService.encrypt(
|
||||
const encryptedCollectionName = await this.encryptService.encryptString(
|
||||
this.i18nService.t("defaultCollection"),
|
||||
organizationKey,
|
||||
);
|
||||
|
||||
@@ -18,6 +18,8 @@ export type AddExistingOrganizationDialogParams = {
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum AddExistingOrganizationDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -22,6 +22,8 @@ type CreateClientDialogParams = {
|
||||
plans: PlanResponse[];
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum CreateClientDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -23,6 +23,8 @@ type ManageClientNameDialogParams = {
|
||||
};
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ManageClientNameDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -18,6 +18,8 @@ type ManageClientSubscriptionDialogParams = {
|
||||
provider: Provider;
|
||||
};
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum ManageClientSubscriptionDialogResultType {
|
||||
Closed = "closed",
|
||||
Submitted = "submitted",
|
||||
|
||||
@@ -37,6 +37,8 @@ import { PasswordHealthMembersURIComponent } from "./password-health-members-uri
|
||||
import { PasswordHealthMembersComponent } from "./password-health-members.component";
|
||||
import { PasswordHealthComponent } from "./password-health.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum RiskInsightsTabType {
|
||||
AllApps = 0,
|
||||
CriticalApps = 1,
|
||||
|
||||
@@ -11,6 +11,8 @@ import { DialogRef, DIALOG_DATA, BitValidators, ToastService } from "@bitwarden/
|
||||
import { ProjectView } from "../../models/view/project.view";
|
||||
import { ProjectService } from "../../projects/project.service";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum OperationType {
|
||||
Add,
|
||||
Edit,
|
||||
|
||||
@@ -44,11 +44,15 @@ import { SecretService } from "../secret.service";
|
||||
|
||||
import { SecretDeleteDialogComponent, SecretDeleteOperation } from "./secret-delete.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum OperationType {
|
||||
Add,
|
||||
Edit,
|
||||
}
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
export enum SecretDialogTabType {
|
||||
NameValuePair = 0,
|
||||
People = 1,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user