diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c1045739506..e5cd47077fb 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -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", diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f76b349ccd4..50f04ebaeb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,7 @@ jobs: run: npm test -- --coverage --maxWorkers=3 - name: Report test results - uses: dorny/test-reporter@31a54ee7ebcacc03a09ea97a7e5465a47b84aea5 # v1.9.1 + uses: dorny/test-reporter@6e6a65b7a0bd2c9197df7d0ae36ac5cee784230c # v2.0.0 if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !cancelled() }} with: name: Test Results diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 6bd28cfe809..a948fce0428 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -26,6 +26,9 @@ const preview: Preview = { wrapperDecorator, ], parameters: { + a11y: { + element: "#storybook-root", + }, controls: { matchers: { color: /(background|color)$/i, diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 00000000000..208adf96214 --- /dev/null +++ b/.storybook/test-runner.ts @@ -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; diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 1c3214a6ef7..dcdfe7df4d6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -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": { @@ -4551,6 +4542,12 @@ "downloadFromBitwardenNow": { "message": "Download from bitwarden.com now" }, + "getItOnGooglePlay": { + "message": "Get it on Google Play" + }, + "downloadOnTheAppStore": { + "message": "Download on the App Store" + }, "permanentlyDeleteAttachmentConfirmation": { "message": "Are you sure you want to permanently delete this attachment?" }, @@ -5261,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" }, @@ -5324,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." } } diff --git a/apps/browser/src/autofill/background/notification.background.spec.ts b/apps/browser/src/autofill/background/notification.background.spec.ts index 63ae1193737..009efd7ff36 100644 --- a/apps/browser/src/autofill/background/notification.background.spec.ts +++ b/apps/browser/src/autofill/background/notification.background.spec.ts @@ -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({ tab }); + const message: NotificationBackgroundExtensionMessage = { + command: "bgSaveCipher", + edit: false, + folder: "folder-id", + }; + const queueMessage = mock({ + type: NotificationQueueMessageType.ChangePassword, + tab, + domain: "example.com", + newPassword: "newPassword", + }); + notificationBackground["notificationQueue"] = [queueMessage]; + const cipherView = mock({ + 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(); + const cipherView = mock({ reprompt: CipherRepromptType.None }); getDecryptedCipherByIdSpy.mockResolvedValueOnce(cipherView); const errorMessage = "fetch error"; updateWithServerSpy.mockImplementation(() => { diff --git a/apps/browser/src/autofill/background/notification.background.ts b/apps/browser/src/autofill/background/notification.background.ts index 5dd15274677..52920ec67a8 100644 --- a/apps/browser/src/autofill/background/notification.background.ts +++ b/apps/browser/src/autofill/background/notification.background.ts @@ -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", { diff --git a/apps/browser/src/autofill/content/components/constants/styles.ts b/apps/browser/src/autofill/content/components/constants/styles.ts index f7c9ffd4d92..08c8671ce14 100644 --- a/apps/browser/src/autofill/content/components/constants/styles.ts +++ b/apps/browser/src/autofill/content/components/constants/styles.ts @@ -144,17 +144,17 @@ export const border = { export const typography = { body1: ` line-height: 24px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 16px; `, body2: ` line-height: 20px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 14px; `, helperMedium: ` line-height: 16px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 12px; `, }; diff --git a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts index b8e35813c6c..d134db3ca6f 100644 --- a/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts +++ b/apps/browser/src/autofill/content/components/lit-stories/mock-data.ts @@ -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: diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts index 7ad6b4542ec..d4d66c7a7be 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/container.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/container.ts @@ -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) { diff --git a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts index 521fdf45291..2bf8caecfff 100644 --- a/apps/browser/src/autofill/content/components/notification/confirmation/message.ts +++ b/apps/browser/src/autofill/content/components/notification/confirmation/message.ts @@ -28,28 +28,31 @@ export function NotificationConfirmationMessage({
${message || buttonText ? html` - ${itemName} - - ${message || nothing} - ${buttonText - ? html` - handleButtonKeyDown(e, () => handleClick(e))} - aria-label=${buttonAria} - tabindex="0" - role="button" - > - ${buttonText} - - ` - : nothing} - +
+ ${itemName} + + ${message || nothing} + ${buttonText + ? html` + + handleButtonKeyDown(e, () => handleClick(e))} + aria-label=${buttonAria} + tabindex="0" + role="button" + > + ${buttonText} + + ` + : nothing} + +
` : nothing} ${messageDetails @@ -61,18 +64,23 @@ 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; text-overflow: ellipsis; line-height: 24px; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 16px; `; @@ -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` diff --git a/apps/browser/src/autofill/content/components/notification/container.ts b/apps/browser/src/autofill/content/components/notification/container.ts index c7c7ae16cde..1a78854f5dc 100644 --- a/apps/browser/src/autofill/content/components/notification/container.ts +++ b/apps/browser/src/autofill/content/components/notification/container.ts @@ -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"]}; } `; diff --git a/apps/browser/src/autofill/content/components/notification/footer.ts b/apps/browser/src/autofill/content/components/notification/footer.ts index 73fe9084595..03d0d475e85 100644 --- a/apps/browser/src/autofill/content/components/notification/footer.ts +++ b/apps/browser/src/autofill/content/components/notification/footer.ts @@ -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` -
+
${!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]}; } `; diff --git a/apps/browser/src/autofill/content/components/notification/header-message.ts b/apps/browser/src/autofill/content/components/notification/header-message.ts index ccfa58b8970..47fe8cd2828 100644 --- a/apps/browser/src/autofill/content/components/notification/header-message.ts +++ b/apps/browser/src/autofill/content/components/notification/header-message.ts @@ -19,7 +19,7 @@ const notificationHeaderMessageStyles = (theme: Theme) => css` line-height: 28px; white-space: nowrap; color: ${themes[theme].text.main}; - font-family: "DM Sans", sans-serif; + font-family: Roboto, sans-serif; font-size: 18px; font-weight: 600; `; diff --git a/apps/browser/src/autofill/content/components/rows/button-row.ts b/apps/browser/src/autofill/content/components/rows/button-row.ts index 3527d050b81..7e81d846cb7 100644 --- a/apps/browser/src/autofill/content/components/rows/button-row.ts +++ b/apps/browser/src/autofill/content/components/rows/button-row.ts @@ -62,7 +62,7 @@ const buttonRowStyles = css` > button { max-width: min-content; - flex: 1 1 50%; + flex: 1 1 25%; } > div { diff --git a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts index 9cc457f3c1a..d0b970671a8 100644 --- a/apps/browser/src/autofill/enums/autofill-overlay.enum.ts +++ b/apps/browser/src/autofill/enums/autofill-overlay.enum.ts @@ -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, diff --git a/apps/browser/src/autofill/fido2/content/messaging/message.ts b/apps/browser/src/autofill/fido2/content/messaging/message.ts index 5815be9eb60..640af22ab9a 100644 --- a/apps/browser/src/autofill/fido2/content/messaging/message.ts +++ b/apps/browser/src/autofill/fido2/content/messaging/message.ts @@ -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, diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index 4b9ba58d586..52cbdcd47d3 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -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"), - loginUpdateConfirmation: 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"), diff --git a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts index 82c03cacadf..42d7666e8a7 100644 --- a/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/abstractions/overlay-notifications-content.service.ts @@ -15,6 +15,7 @@ export type NotificationsExtensionMessage = { typeData?: NotificationTypeData; height?: number; error?: string; + closedByUser?: boolean; fadeOutNotification?: boolean; }; }; diff --git a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts index 519521feaa9..c21aaa37dd4 100644 --- a/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts +++ b/apps/browser/src/autofill/overlay/notifications/content/overlay-notifications-content.service.ts @@ -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); } /** diff --git a/apps/browser/src/autofill/services/abstractions/autofill.service.ts b/apps/browser/src/autofill/services/abstractions/autofill.service.ts index 5b1b4b3b8bb..daafd871789 100644 --- a/apps/browser/src/autofill/services/abstractions/autofill.service.ts +++ b/apps/browser/src/autofill/services/abstractions/autofill.service.ts @@ -87,5 +87,9 @@ export abstract class AutofillService { cipherType?: CipherType, ) => Promise; setAutoFillOnPageLoadOrgPolicy: () => Promise; - isPasswordRepromptRequired: (cipher: CipherView, tab: chrome.tabs.Tab) => Promise; + isPasswordRepromptRequired: ( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ) => Promise; } diff --git a/apps/browser/src/autofill/services/autofill.service.ts b/apps/browser/src/autofill/services/autofill.service.ts index da46ceb0864..525010bacc1 100644 --- a/apps/browser/src/autofill/services/autofill.service.ts +++ b/apps/browser/src/autofill/services/autofill.service.ts @@ -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 { + async isPasswordRepromptRequired( + cipher: CipherView, + tab: chrome.tabs.Tab, + action?: string, + ): Promise { 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", }); } diff --git a/apps/browser/src/autofill/shared/styles/variables.scss b/apps/browser/src/autofill/shared/styles/variables.scss index ae6a060798a..1e804ed8fd2 100644 --- a/apps/browser/src/autofill/shared/styles/variables.scss +++ b/apps/browser/src/autofill/shared/styles/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-source-code-pro: "Source Code Pro", monospace; $font-size-base: 14px; diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index d295fddda52..85a9cd27c57 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -720,6 +720,7 @@ export default class MainBackground { this.logService, (logoutReason: LogoutReason, userId?: UserId) => this.logout(logoutReason, userId), this.vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, ); this.fileUploadService = new FileUploadService(this.logService, this.apiService); diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index d92826765db..7172b98d727 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -357,7 +357,7 @@ export class NativeMessagingBackground { await this.secureCommunication(); } - return await this.encryptService.encrypt( + return await this.encryptService.encryptString( JSON.stringify(message), this.secureChannel!.sharedSecret!, ); @@ -401,10 +401,9 @@ export class NativeMessagingBackground { return; } message = JSON.parse( - await this.encryptService.decryptToUtf8( + await this.encryptService.decryptString( rawMessage as EncString, this.secureChannel.sharedSecret, - "ipc-desktop-ipc-channel-key", ), ); } else { diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index b27e8ca7c96..4ef72fa0077 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -664,6 +664,10 @@ export class BrowserApi { * Identifies if the browser autofill settings are overridden by the extension. */ static async browserAutofillSettingsOverridden(): Promise { + if (!(await BrowserApi.permissionsGranted(["privacy"]))) { + return false; + } + const checkOverrideStatus = (details: chrome.types.ChromeSettingGetResult) => details.levelOfControl === "controlled_by_this_extension" && !details.value; diff --git a/apps/browser/src/platform/popup/layout/popup-size.service.ts b/apps/browser/src/platform/popup/layout/popup-size.service.ts index 3ae9a633cab..69d3102d24e 100644 --- a/apps/browser/src/platform/popup/layout/popup-size.service.ts +++ b/apps/browser/src/platform/popup/layout/popup-size.service.ts @@ -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"); } } diff --git a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts index ff63b52ab3f..2a946982990 100644 --- a/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts +++ b/apps/browser/src/platform/popup/view-cache/popup-view-cache.service.ts @@ -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"; diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 49579f889b3..a480e1d6ba3 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -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 diff --git a/apps/browser/src/popup/images/loading.svg b/apps/browser/src/popup/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/browser/src/popup/images/loading.svg +++ b/apps/browser/src/popup/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/browser/src/popup/scss/base.scss b/apps/browser/src/popup/scss/base.scss index 59893b5050d..80ada61f868 100644 --- a/apps/browser/src/popup/scss/base.scss +++ b/apps/browser/src/popup/scss/base.scss @@ -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, diff --git a/apps/browser/src/popup/scss/variables.scss b/apps/browser/src/popup/scss/variables.scss index b78f06f2f3f..aea69e26436 100644 --- a/apps/browser/src/popup/scss/variables.scss +++ b/apps/browser/src/popup/scss/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 16px; $font-size-large: 18px; diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 00b8ae81cf9..6ede88dfc13 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -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, diff --git a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts index 49804abda5d..def425a51a5 100644 --- a/apps/browser/src/tools/popup/send-v2/send-v2.component.ts +++ b/apps/browser/src/tools/popup/send-v2/send-v2.component.ts @@ -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, diff --git a/apps/browser/src/tools/popup/settings/settings-v2.component.html b/apps/browser/src/tools/popup/settings/settings-v2.component.html index 22e2d9a28d0..8d31ccf8371 100644 --- a/apps/browser/src/tools/popup/settings/settings-v2.component.html +++ b/apps/browser/src/tools/popup/settings/settings-v2.component.html @@ -23,6 +23,7 @@ *ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1
@@ -53,6 +54,7 @@ *ngIf="!(showVaultBadge$ | async)?.hasBadgeDismissed" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1
@@ -83,6 +85,7 @@ *ngIf="(downloadBitwardenNudgeStatus$ | async)?.hasBadgeDismissed === false" bitBadge variant="notification" + [attr.aria-label]="'nudgeBadgeAria' | i18n" >1
diff --git a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts index 6bcdddfde81..fc302dd6c36 100644 --- a/apps/browser/src/vault/guards/at-risk-passwords.guard.ts +++ b/apps/browser/src/vault/guards/at-risk-passwords.guard.ts @@ -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; }), ); }; diff --git a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts index fcca125c2b6..0133bccd25c 100644 --- a/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/at-risk-carousel-dialog/at-risk-carousel-dialog.component.ts @@ -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", } diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts index 137f2a9dac3..4daffa6a9b8 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-generator-dialog/vault-generator-dialog.component.ts @@ -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", diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts index 64805a02394..4a8625f982c 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-v2.component.ts @@ -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, diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts index 56db47619b0..a834314560b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.ts @@ -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)) { diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts index 9498d953808..621ec795157 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.spec.ts @@ -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, }, ], diff --git a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts index c726678c973..db4cfeefe9f 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-list-filters.service.ts @@ -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(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()) diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.html b/apps/browser/src/vault/popup/settings/download-bitwarden.component.html index ad063691e76..20896a3c782 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.html +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.html @@ -2,7 +2,6 @@ -

@@ -20,16 +19,30 @@ /> @@ -41,6 +54,7 @@ {{ "downloadFromBitwardenNow" | i18n }} diff --git a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts index b51619b86d1..9f04bb58c34 100644 --- a/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts +++ b/apps/browser/src/vault/popup/settings/download-bitwarden.component.ts @@ -6,7 +6,7 @@ import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { CardComponent, TypographyModule } from "@bitwarden/components"; +import { CardComponent, LinkModule, TypographyModule } from "@bitwarden/components"; import { VaultNudgesService, VaultNudgeType } from "@bitwarden/vault"; import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component"; @@ -27,6 +27,7 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co CardComponent, TypographyModule, CurrentAccountComponent, + LinkModule, ], }) export class DownloadBitwardenComponent implements OnInit { diff --git a/apps/cli/package.json b/apps/cli/package.json index 79d4786a23c..b01c96b23d1 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -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", diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 3ad71c62e66..cd5c8ef9bcd 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -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); }); diff --git a/apps/cli/src/platform/services/node-api.service.ts b/apps/cli/src/platform/services/node-api.service.ts index 8c7629fb3d9..d695272364b 100644 --- a/apps/cli/src/platform/services/node-api.service.ts +++ b/apps/cli/src/platform/services/node-api.service.ts @@ -39,6 +39,7 @@ export class NodeApiService extends ApiService { logService, logoutCallback, vaultTimeoutSettingsService, + { createRequest: (url, request) => new Request(url, request) }, customUserAgent, ); } diff --git a/apps/cli/src/program.ts b/apps/cli/src/program.ts index dca4effcdc9..d85f1b366e6 100644 --- a/apps/cli/src/program.ts +++ b/apps/cli/src/program.ts @@ -118,7 +118,10 @@ export class Program extends BaseProgram { .description("Log into a user account.") .option("--method ", "Two-step login method.") .option("--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 ", "Environment variable storing your password") .option( diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index cfd29303510..1b442fbbb8a 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -120,9 +120,9 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" -version = "3.4.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4" +checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" dependencies = [ "clipboard-win", "log", @@ -130,6 +130,7 @@ dependencies = [ "objc2-app-kit", "objc2-foundation", "parking_lot", + "percent-encoding", "wl-clipboard-rs", "x11rb", ] @@ -465,15 +466,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2", -] - [[package]] name = "blocking" version = "1.6.1" @@ -565,12 +557,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - [[package]] name = "cfg_aliases" version = "0.2.1" @@ -867,17 +853,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive-new" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "desktop_core" version = "0.0.0" @@ -1007,6 +982,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1409,7 +1394,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bdbbd5bc8c5749697ccaa352fa45aff8730cf21c68029c0eef1ffed7c3d6ba2" dependencies = [ "cfg-if", - "nix 0.29.0", + "nix", "widestring", "windows 0.57.0", ] @@ -1839,18 +1824,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases 0.1.1", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -1859,7 +1832,7 @@ checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", - "cfg_aliases 0.2.1", + "cfg_aliases", "libc", "memoffset", ] @@ -1990,47 +1963,24 @@ dependencies = [ "libc", ] -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - [[package]] name = "objc2" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" dependencies = [ - "objc-sys", "objc2-encode", ] [[package]] name = "objc2-app-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags", - "block2", "objc2", + "objc2-core-graphics", "objc2-foundation", ] @@ -2041,18 +1991,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags", + "dispatch2", + "objc2", ] [[package]] -name = "objc2-core-image" -version = "0.2.2" +name = "objc2-core-graphics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "block2", + "bitflags", + "dispatch2", "objc2", - "objc2-foundation", - "objc2-metal", + "objc2-core-foundation", + "objc2-io-surface", ] [[package]] @@ -2063,14 +2016,13 @@ checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags", - "block2", - "libc", "objc2", + "objc2-core-foundation", ] [[package]] @@ -2084,28 +2036,14 @@ dependencies = [ ] [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "objc2-io-surface" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ "bitflags", - "block2", "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", + "objc2-core-foundation", ] [[package]] @@ -3487,9 +3425,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "0781cf46869b37e36928f7b432273c0995aa8aed9552c556fb18754420541efc" dependencies = [ "bitflags", "wayland-backend", @@ -3499,9 +3437,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "248a02e6f595aad796561fa82d25601bd2c8c3b145b1c7453fc8f94c1a58f8b2" dependencies = [ "bitflags", "wayland-backend", @@ -3982,15 +3920,14 @@ dependencies = [ [[package]] name = "wl-clipboard-rs" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb" +checksum = "2a083daad7e8a4b8805ad73947ccadabe62afe37ce0e9787a56ff373d34762c7" dependencies = [ - "derive-new", "libc", "log", - "nix 0.28.0", "os_pipe", + "rustix", "tempfile", "thiserror 1.0.69", "tree_magic_mini", @@ -4085,7 +4022,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.29.0", + "nix", "ordered-stream", "rand 0.8.5", "serde", @@ -4115,7 +4052,7 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.29.0", + "nix", "ordered-stream", "serde", "serde_repr", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index f613744f42a..fafaf02eca3 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -11,7 +11,7 @@ publish = false [workspace.dependencies] aes = "=0.8.4" anyhow = "=1.0.94" -arboard = { version = "=3.4.1", default-features = false } +arboard = { version = "=3.5.0", default-features = false } argon2 = "=0.5.3" base64 = "=0.22.1" bindgen = "0.71.1" diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index d506e109e94..8b39fd9805e 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -17,7 +17,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.14.1", + "@types/node": "22.15.3", "typescript": "5.4.2" } }, @@ -101,9 +101,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index f67ab259d3b..ea6b1b3e7a8 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -22,7 +22,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.14.1", + "@types/node": "22.15.3", "typescript": "5.4.2" }, "_moduleAliases": { diff --git a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts index b02ff1a4225..d8616e9757a 100644 --- a/apps/desktop/native-messaging-test-runner/src/ipc.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/ipc.service.ts @@ -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", diff --git a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts index c01d581afe8..c2356f93e28 100644 --- a/apps/desktop/native-messaging-test-runner/src/native-message.service.ts +++ b/apps/desktop/native-messaging-test-runner/src/native-message.service.ts @@ -220,7 +220,7 @@ export default class NativeMessageService { const sharedKey = await this.getSharedKeyForKey(key); - return this.encryptService.encrypt(commandDataString, sharedKey); + return this.encryptService.encryptString(commandDataString, sharedKey); } private async decryptResponsePayload( @@ -228,11 +228,7 @@ export default class NativeMessageService { key: string, ): Promise { const sharedKey = await this.getSharedKeyForKey(key); - const decrypted = await this.encryptService.decryptToUtf8( - payload, - sharedKey, - "native-messaging-session", - ); + const decrypted = await this.encryptService.decryptString(payload, sharedKey); return JSON.parse(decrypted); } diff --git a/apps/desktop/src/app/components/avatar.component.ts b/apps/desktop/src/app/components/avatar.component.ts index 87db2e4b6af..d2660763667 100644 --- a/apps/desktop/src/app/components/avatar.component.ts +++ b/apps/desktop/src/app/components/avatar.component.ts @@ -113,7 +113,7 @@ export class AvatarComponent implements OnChanges, OnInit { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - '"DM Sans","Helvetica Neue",Helvetica,Arial,' + + 'Roboto,"Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); textTag.textContent = character; diff --git a/apps/desktop/src/app/tools/send/send.component.ts b/apps/desktop/src/app/tools/send/send.component.ts index cc3007ae133..6c2c3ed53c6 100644 --- a/apps/desktop/src/app/tools/send/send.component.ts +++ b/apps/desktop/src/app/tools/send/send.component.ts @@ -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", diff --git a/apps/desktop/src/autofill/models/ssh-agent-setting.ts b/apps/desktop/src/autofill/models/ssh-agent-setting.ts index f332cc93ee1..1775cf35588 100644 --- a/apps/desktop/src/autofill/models/ssh-agent-setting.ts +++ b/apps/desktop/src/autofill/models/ssh-agent-setting.ts @@ -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", diff --git a/apps/desktop/src/images/loading.svg b/apps/desktop/src/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/desktop/src/images/loading.svg +++ b/apps/desktop/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/desktop/src/scss/variables.scss b/apps/desktop/src/scss/variables.scss index b8978e284e5..b094be63f8c 100644 --- a/apps/desktop/src/scss/variables.scss +++ b/apps/desktop/src/scss/variables.scss @@ -1,6 +1,6 @@ $dark-icon-themes: "theme_dark"; -$font-family-sans-serif: "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-family-sans-serif: Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif; $font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace; $font-size-base: 14px; $font-size-large: 18px; diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts index 9ddc3da8ed4..af18828b59d 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -347,7 +347,7 @@ describe("BiometricMessageHandlerService", () => { trusted: false, }), ); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: "biometricUnlock", messageId: 0, @@ -382,7 +382,7 @@ describe("BiometricMessageHandlerService", () => { ngZone.run.mockReturnValue({ closed: of(true), }); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, @@ -433,7 +433,7 @@ describe("BiometricMessageHandlerService", () => { ngZone.run.mockReturnValue({ closed: of(false), }); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, @@ -480,7 +480,7 @@ describe("BiometricMessageHandlerService", () => { trusted: true, }), ); - encryptService.decryptToUtf8.mockResolvedValue( + encryptService.decryptString.mockResolvedValue( JSON.stringify({ command: BiometricsCommands.UnlockWithBiometricsForUser, messageId: 0, diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 3851f40505b..42d7b8aae5f 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -175,7 +175,7 @@ export class BiometricMessageHandlerService { } const message: LegacyMessage = JSON.parse( - await this.encryptService.decryptToUtf8( + await this.encryptService.decryptString( rawMessage as EncString, SymmetricCryptoKey.fromString(sessionSecret), ), @@ -365,7 +365,7 @@ export class BiometricMessageHandlerService { throw new Error("Session secret is missing"); } - const encrypted = await this.encryptService.encrypt( + const encrypted = await this.encryptService.encryptString( JSON.stringify(message), SymmetricCryptoKey.fromString(sessionSecret), ); diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index e82444be993..6fb91231be1 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -168,7 +168,7 @@ export class DuckDuckGoMessageHandlerService { payload: DecryptedCommandData, key: SymmetricCryptoKey, ): Promise { - return await this.encryptService.encrypt(JSON.stringify(payload), key); + return await this.encryptService.encryptString(JSON.stringify(payload), key); } private async decryptPayload(message: EncryptedMessage): Promise { @@ -188,10 +188,9 @@ export class DuckDuckGoMessageHandlerService { } try { - let decryptedResult = await this.encryptService.decryptToUtf8( + let decryptedResult = await this.encryptService.decryptString( message.encryptedCommand as EncString, this.duckduckgoSharedSecret, - "ddg-shared-key", ); decryptedResult = this.trimNullCharsFromMessage(decryptedResult); diff --git a/apps/desktop/src/types/biometric-message.ts b/apps/desktop/src/types/biometric-message.ts index f7a7ef0c507..7616b265005 100644 --- a/apps/desktop/src/types/biometric-message.ts +++ b/apps/desktop/src/types/biometric-message.ts @@ -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", diff --git a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts index 2858d7330e5..204615443ba 100644 --- a/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts +++ b/apps/desktop/src/vault/app/vault/credential-generator-dialog.component.ts @@ -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", diff --git a/apps/desktop/src/vault/app/vault/vault-v2.component.ts b/apps/desktop/src/vault/app/vault/vault-v2.component.ts index 4da2db101ed..66e77580d1c 100644 --- a/apps/desktop/src/vault/app/vault/vault-v2.component.ts +++ b/apps/desktop/src/vault/app/vault/vault-v2.component.ts @@ -514,6 +514,9 @@ export class VaultV2Component implements OnInit, OnDestroy { this.cipherId = cipher.id; this.cipher = cipher; await this.buildFormConfig("edit"); + if (!cipher.edit && this.config) { + this.config.mode = "partial-edit"; + } this.action = "edit"; await this.go().catch(() => {}); } diff --git a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts index dd19c66f21e..147340e6a00 100644 --- a/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/bulk-collections-dialog/bulk-collections-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts index 687aef9b671..96c00faceb2 100644 --- a/apps/web/src/app/admin-console/organizations/collections/vault.component.ts +++ b/apps/web/src/app/admin-console/organizations/collections/vault.component.ts @@ -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, diff --git a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts index 2a5af32ecc2..f29b4b642cb 100644 --- a/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/group-add-edit.component.ts @@ -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", diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts index c90a2a657e7..8349b44c735 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts index f08cb0b7d7c..4e78d4dc91f 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/components/reset-password.component.ts @@ -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", } diff --git a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts index 78d2d8fd165..ecf4d26eb52 100644 --- a/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts +++ b/apps/web/src/app/admin-console/organizations/members/services/organization-user-reset-password/organization-user-reset-password.service.ts @@ -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, ); diff --git a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts index 49f4d15a100..4d722840e23 100644 --- a/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts +++ b/apps/web/src/app/admin-console/organizations/policies/policy-edit.component.ts @@ -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", diff --git a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts index c23dcf2c8f2..e942eecbd37 100644 --- a/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/settings/components/delete-organization-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts index edd0bfcaada..1db1fc8a06e 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.component.ts @@ -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. diff --git a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts index 8702c0f7a6c..884483d32b0 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/access-selector/access-selector.models.ts @@ -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, diff --git a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts index 37d0ebbd195..07bff3aba64 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/collection-dialog/collection-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts index 7dc8217fde5..3073917e57b 100644 --- a/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts +++ b/apps/web/src/app/auth/core/enums/webauthn-login-credential-prf-status.enum.ts @@ -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, diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts index 94400f34e6e..16aa2546101 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-status-type.ts @@ -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, diff --git a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts index 61a366c433e..ecb0c5a3d07 100644 --- a/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts +++ b/apps/web/src/app/auth/emergency-access/enums/emergency-access-type.ts @@ -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, diff --git a/apps/web/src/app/components/selectable-avatar.component.ts b/apps/web/src/app/auth/settings/account/selectable-avatar.component.ts similarity index 100% rename from apps/web/src/app/components/selectable-avatar.component.ts rename to apps/web/src/app/auth/settings/account/selectable-avatar.component.ts diff --git a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts index f3fd19a4e8b..95afc167374 100644 --- a/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/confirm/emergency-access-confirm.component.ts @@ -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", } diff --git a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts index cf52969c244..1a6510ef011 100644 --- a/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/emergency-access-add-edit.component.ts @@ -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", diff --git a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts index c80f82ae126..edb85dc0f1a 100644 --- a/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts +++ b/apps/web/src/app/auth/settings/emergency-access/takeover/emergency-access-takeover.component.ts @@ -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", } diff --git a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts index 591fe3816dc..8e7e25896ab 100644 --- a/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/webauthn-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -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, } diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html index d947ea96dfb..64a9781b7cf 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.html @@ -51,8 +51,38 @@

{{ "paymentType" | i18n }}

+ + @if (trialLength === 0) { + @let priceLabel = + subscriptionProduct === SubscriptionProduct.PasswordManager + ? "passwordManagerPlanPrice" + : "secretsManagerPlanPrice"; + +
+
+ {{ priceLabel | i18n }}: {{ getPriceFor(formGroup.value.cadence) | currency: "USD $" }} +
+ {{ "estimatedTax" | i18n }}: + @if (fetchingTaxAmount) { + + } @else { + {{ taxAmount | currency: "USD $" }} + } +
+
+
+

+ {{ "total" | i18n }}: + @if (fetchingTaxAmount) { + + } @else { + {{ total | currency: "USD $" }}/{{ interval | i18n }} + } +

+
+ }
+ + + + {{ "loading" | i18n }} + diff --git a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts index c6248a06a89..63c42139648 100644 --- a/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts +++ b/apps/web/src/app/billing/accounts/trial-initiation/trial-billing-step.component.ts @@ -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(); 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(); + + 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 { @@ -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 { 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 => { + 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"; + } } diff --git a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts index eb6bfdf368b..b482007e30b 100644 --- a/apps/web/src/app/billing/members/free-bitwarden-families.component.ts +++ b/apps/web/src/app/billing/members/free-bitwarden-families.component.ts @@ -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", diff --git a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts index 8cff90edd5b..49c5bb775b1 100644 --- a/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts +++ b/apps/web/src/app/billing/organizations/change-plan-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/billing/organizations/download-license.component.ts b/apps/web/src/app/billing/organizations/download-license.component.ts index fecb58a7a9d..66778aec50f 100644 --- a/apps/web/src/app/billing/organizations/download-license.component.ts +++ b/apps/web/src/app/billing/organizations/download-license.component.ts @@ -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", diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts index e6854a5216b..2189bfa830f 100644 --- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts +++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts @@ -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, diff --git a/apps/web/src/app/billing/services/stripe.service.ts b/apps/web/src/app/billing/services/stripe.service.ts index 5c35923c1f4..360187ecd1e 100644 --- a/apps/web/src/app/billing/services/stripe.service.ts +++ b/apps/web/src/app/billing/services/stripe.service.ts @@ -148,7 +148,7 @@ export class StripeService { base: { color: null, fontFamily: - '"DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, ' + + 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, ' + '"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"', fontSize: "16px", fontSmoothing: "antialiased", diff --git a/apps/web/src/app/billing/settings/sponsored-families.component.ts b/apps/web/src/app/billing/settings/sponsored-families.component.ts index cee08bae8cd..7e493168ce2 100644 --- a/apps/web/src/app/billing/settings/sponsored-families.component.ts +++ b/apps/web/src/app/billing/settings/sponsored-families.component.ts @@ -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; diff --git a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts index 33e6334c577..39a7531082a 100644 --- a/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts +++ b/apps/web/src/app/billing/settings/sponsoring-org-row.component.ts @@ -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, diff --git a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts index 45dab542ce8..ec6e251418b 100644 --- a/apps/web/src/app/billing/shared/add-credit-dialog.component.ts +++ b/apps/web/src/app/billing/shared/add-credit-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts index e7b7cc78250..9d32becd1bb 100644 --- a/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-payment-dialog/adjust-payment-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts index 3acba414df4..6cd17218f02 100644 --- a/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts +++ b/apps/web/src/app/billing/shared/adjust-storage-dialog/adjust-storage-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/billing/shared/offboarding-survey.component.ts b/apps/web/src/app/billing/shared/offboarding-survey.component.ts index cecbc302f40..62213c1fe94 100644 --- a/apps/web/src/app/billing/shared/offboarding-survey.component.ts +++ b/apps/web/src/app/billing/shared/offboarding-survey.component.ts @@ -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", diff --git a/apps/web/src/app/billing/shared/update-license-types.ts b/apps/web/src/app/billing/shared/update-license-types.ts index 8f939ac62a9..8ba13541ba8 100644 --- a/apps/web/src/app/billing/shared/update-license-types.ts +++ b/apps/web/src/app/billing/shared/update-license-types.ts @@ -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", diff --git a/apps/web/src/app/dirt/reports/reports.ts b/apps/web/src/app/dirt/reports/reports.ts index 500ae23e5cf..c47928af1e9 100644 --- a/apps/web/src/app/dirt/reports/reports.ts +++ b/apps/web/src/app/dirt/reports/reports.ts @@ -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", diff --git a/apps/web/src/app/dirt/reports/shared/models/report-variant.ts b/apps/web/src/app/dirt/reports/shared/models/report-variant.ts index 3beba65f7d9..48b213f4cf6 100644 --- a/apps/web/src/app/dirt/reports/shared/models/report-variant.ts +++ b/apps/web/src/app/dirt/reports/shared/models/report-variant.ts @@ -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", diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 30d1dd1af75..ab6c6cc5d72 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -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"; diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 460b8d58d63..10c35f861b9 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -95,6 +95,8 @@ export interface VaultItemDialogParams { restore?: (c: CipherView) => Promise; } +// 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). diff --git a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts index 2d0aa0231f1..e20efa9dbb8 100644 --- a/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts +++ b/apps/web/src/app/vault/components/web-generator-dialog/web-generator-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts index 5dcbf0d4e78..621e0ec88c5 100644 --- a/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts +++ b/apps/web/src/app/vault/individual-vault/add-edit-v2.component.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts index f8084b03e33..1650b0f371f 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-delete-dialog/bulk-delete-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts index 8f0827e4b51..d287c430d49 100644 --- a/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts +++ b/apps/web/src/app/vault/individual-vault/bulk-action-dialogs/bulk-move-dialog/bulk-move-dialog.component.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts index ad6cbfad43d..6a3c5663d93 100644 --- a/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts +++ b/apps/web/src/app/vault/individual-vault/folder-add-edit.component.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts index 1fa5ae1ad8b..ca16541f88f 100644 --- a/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts +++ b/apps/web/src/app/vault/individual-vault/vault-banners/services/vault-banners.service.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts index 0f949e17146..7566dbdc507 100644 --- a/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts +++ b/apps/web/src/app/vault/individual-vault/vault-filter/shared/models/vault-filter-section.type.ts @@ -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", diff --git a/apps/web/src/app/vault/individual-vault/view.component.ts b/apps/web/src/app/vault/individual-vault/view.component.ts index e7b06cbb8d6..f52a4da3ffb 100644 --- a/apps/web/src/app/vault/individual-vault/view.component.ts +++ b/apps/web/src/app/vault/individual-vault/view.component.ts @@ -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", diff --git a/apps/web/src/app/vault/services/browser-extension-prompt.service.ts b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts index 5617d4aef75..f928404a2a9 100644 --- a/apps/web/src/app/vault/services/browser-extension-prompt.service.ts +++ b/apps/web/src/app/vault/services/browser-extension-prompt.service.ts @@ -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", diff --git a/apps/web/src/connectors/duo-redirect.spec.ts b/apps/web/src/connectors/duo-redirect.spec.ts index c0498861ba0..85953a5dd00 100644 --- a/apps/web/src/connectors/duo-redirect.spec.ts +++ b/apps/web/src/connectors/duo-redirect.spec.ts @@ -10,13 +10,13 @@ describe("duo-redirect", () => { }); it("should redirect to a valid Duo URL", () => { - const validUrl = "https://api-123.duosecurity.com/auth"; + const validUrl = "https://api-123.duosecurity.com/oauth/v1/authorize"; redirectToDuoFrameless(validUrl); expect(window.location.href).toBe(validUrl); }); it("should redirect to a valid Duo Federal URL", () => { - const validUrl = "https://api-123.duofederal.com/auth"; + const validUrl = "https://api-123.duofederal.com/oauth/v1/authorize"; redirectToDuoFrameless(validUrl); expect(window.location.href).toBe(validUrl); }); @@ -27,15 +27,55 @@ describe("duo-redirect", () => { }); it("should throw an error for an malicious URL with valid redirect embedded", () => { - const invalidUrl = "https://malicious-site.com\\@api-123.duosecurity.com/auth"; + const invalidUrl = "https://malicious-site.com\\@api-123.duosecurity.com/oauth/v1/authorize"; expect(() => redirectToDuoFrameless(invalidUrl)).toThrow("Invalid redirect URL"); }); + it("should throw an error for a URL with a malicious subdomain", () => { + const maliciousSubdomainUrl = + "https://api-a86d5bde.duosecurity.com.evil.com/oauth/v1/authorize"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow("Invalid redirect URL"); + }); + + it("should throw an error for a URL using HTTP protocol", () => { + const maliciousSubdomainUrl = "http://api-a86d5bde.duosecurity.com/oauth/v1/authorize"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow( + "Invalid redirect URL: invalid protocol", + ); + }); + + it("should throw an error for a URL with javascript code", () => { + const maliciousSubdomainUrl = "javascript://https://api-a86d5bde.duosecurity.com%0Aalert(1)"; + expect(() => redirectToDuoFrameless(maliciousSubdomainUrl)).toThrow( + "Invalid redirect URL: invalid protocol", + ); + }); + it("should throw an error for a non-HTTPS URL", () => { const nonHttpsUrl = "http://api-123.duosecurity.com/auth"; expect(() => redirectToDuoFrameless(nonHttpsUrl)).toThrow("Invalid redirect URL"); }); + it("should throw an error for a URL with invalid port specified", () => { + const urlWithPort = "https://api-123.duyosecurity.com:8080/auth"; + expect(() => redirectToDuoFrameless(urlWithPort)).toThrow( + "Invalid redirect URL: port not allowed", + ); + }); + + it("should redirect to a valid Duo Federal URL with valid port", () => { + const validUrl = "https://api-123.duofederal.com:443/oauth/v1/authorize"; + redirectToDuoFrameless(validUrl); + expect(window.location.href).toBe(validUrl); + }); + + it("should throw an error for a URL with an invalid pathname", () => { + const urlWithPort = "https://api-123.duyosecurity.com/../evil/path/here/"; + expect(() => redirectToDuoFrameless(urlWithPort)).toThrow( + "Invalid redirect URL: invalid pathname", + ); + }); + it("should throw an error for a URL with an invalid hostname", () => { const invalidHostnameUrl = "https://api-123.invalid.com"; expect(() => redirectToDuoFrameless(invalidHostnameUrl)).toThrow("Invalid redirect URL"); diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index d1841247962..5389b31f6af 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -57,29 +57,46 @@ window.addEventListener("load", async () => { * @param redirectUrl the duo auth url */ export function redirectToDuoFrameless(redirectUrl: string) { - // Regex to match a valid duo redirect URL + // Validation for Duo redirect URL to prevent open redirect or XSS vulnerabilities + // Only used for Duo 2FA redirects in the extension /** * This regex checks for the following: - * The string must start with "https://api-" - * Followed by a subdomain that can contain letters, numbers + * The hostname must start with a subdomain that begins with "api-" followed by a + * string that can contain letters or numbers of indeterminate length * Followed by either "duosecurity.com" or "duofederal.com" * This ensures that the redirect does not contain any malicious content * and is a valid Duo URL. * */ - const duoRedirectUrlRegex = /^https:\/\/api-[a-zA-Z0-9]+\.(duosecurity|duofederal)\.com/; - // Check if the redirect URL matches the regex - if (!duoRedirectUrlRegex.test(redirectUrl)) { - throw new Error("Invalid redirect URL"); - } - // At this point we know the URL to be valid, but we need to check for embedded credentials + const duoRedirectUrlRegex = /^api-[a-zA-Z0-9]+\.(duosecurity|duofederal)\.com$/; const validateUrl = new URL(redirectUrl); - // URLs should not contain + // Check that no embedded credentials are present if (validateUrl.username || validateUrl.password) { throw new Error("Invalid redirect URL: embedded credentials not allowed"); } - window.location.href = decodeURIComponent(redirectUrl); + // Check that the protocol is HTTPS + if (validateUrl.protocol !== "https:") { + throw new Error("Invalid redirect URL: invalid protocol"); + } + + // Check that the port is not specified + if (validateUrl.port && validateUrl.port !== "443") { + throw new Error("Invalid redirect URL: port not allowed"); + } + + if (validateUrl.pathname !== "/oauth/v1/authorize") { + throw new Error("Invalid redirect URL: invalid pathname"); + } + + // Check if the redirect hostname matches the regex + // Only check the hostname part of the URL to avoid over-zealous Regex expressions from matching + // and causing an Open Redirect vulnerability. Always use hostname instead of host, because host includes port if specified. + if (!duoRedirectUrlRegex.test(validateUrl.hostname)) { + throw new Error("Invalid redirect URL"); + } + + window.location.href = redirectUrl; } /** diff --git a/apps/web/src/images/loading-white.svg b/apps/web/src/images/loading-white.svg index 2bebff7daba..ef5970da42e 100644 --- a/apps/web/src/images/loading-white.svg +++ b/apps/web/src/images/loading-white.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/images/loading.svg b/apps/web/src/images/loading.svg index 3f2033303db..5f4102a5921 100644 --- a/apps/web/src/images/loading.svg +++ b/apps/web/src/images/loading.svg @@ -1,5 +1,5 @@  - Loading... diff --git a/apps/web/src/scss/variables.scss b/apps/web/src/scss/variables.scss index 4b023e12746..66773999c54 100644 --- a/apps/web/src/scss/variables.scss +++ b/apps/web/src/scss/variables.scss @@ -21,7 +21,7 @@ $body-bg: $white; $body-color: #333333; $font-family-sans-serif: - "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $h1-font-size: 1.7rem; diff --git a/bitwarden_license/bit-cli/src/bw.ts b/bitwarden_license/bit-cli/src/bw.ts index ffbc186d9e0..2e4e945b9c8 100644 --- a/bitwarden_license/bit-cli/src/bw.ts +++ b/bitwarden_license/bit-cli/src/bw.ts @@ -1,3 +1,5 @@ +import "core-js/proposals/explicit-resource-management"; + import { program } from "commander"; import { registerOssPrograms } from "@bitwarden/cli/register-oss-programs"; diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts index 723d737d5bd..b8333828693 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/models/password-health.ts @@ -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, diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts index 705eb1231a9..f9177bf1bf7 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.spec.ts @@ -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); }); }); diff --git a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts index 027760f678c..8a6eb5000cd 100644 --- a/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/dirt/reports/risk-insights/services/risk-insights-report.service.ts @@ -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); } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts index 0213903c4d9..67f2382cf91 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/manage/dialogs/add-edit-member-dialog.component.ts @@ -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", diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index 844c6b779a9..418b7020ff9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -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, ); diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts index 0d1602946c7..4bb2c36ef15 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/add-existing-organization-dialog.component.ts @@ -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", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index d3245916ad7..d71e18cd539 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -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", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts index bbcd5f7ed63..45abeab1f4a 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-name-dialog.component.ts @@ -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", diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts index b79c887e77e..ced48bfdbea 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/manage-client-subscription-dialog.component.ts @@ -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", diff --git a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts index ab74869bfc9..5aca124a46a 100644 --- a/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/dirt/access-intelligence/risk-insights.component.ts @@ -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, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts index 74824de00e0..c96887cc9ac 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/projects/dialog/project-dialog.component.ts @@ -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, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts index 88b3e5e2172..09a78e02c44 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/secrets/dialog/secret-dialog.component.ts @@ -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, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts index e1cd7a77b28..241c736fb7a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/service-accounts/dialog/service-account-dialog.component.ts @@ -10,6 +10,8 @@ import { DialogRef, DIALOG_DATA, BitValidators, ToastService } from "@bitwarden/ import { ServiceAccountView } from "../../models/view/service-account.view"; import { ServiceAccountService } from "../service-account.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, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts index 6d060ac255d..6b92fd7458a 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-item.enum.ts @@ -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 ApItemEnum { User, Group, diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts index eb442b0af5d..a57f9636178 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/access-policies/access-policy-selector/models/enums/ap-permission.enum.ts @@ -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 ApPermissionEnum { CanRead = "canRead", CanReadWrite = "canReadWrite", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts index a43207ed75e..935ee1c8518 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/shared/dialogs/bulk-confirmation-dialog.component.ts @@ -15,6 +15,8 @@ export interface BulkConfirmationStatus { description: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum BulkConfirmationResult { Continue, Cancel, diff --git a/eslint.config.mjs b/eslint.config.mjs index 9d93d1118c0..7928224dc00 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -73,6 +73,7 @@ export default tseslint.config( "@angular-eslint/use-lifecycle-interface": "error", "@angular-eslint/use-pipe-transform-interface": 0, "@bitwarden/platform/required-using": "error", + "@bitwarden/platform/no-enums": "error", "@typescript-eslint/explicit-member-accessibility": ["error", { accessibility: "no-public" }], "@typescript-eslint/no-explicit-any": "off", // TODO: This should be re-enabled "@typescript-eslint/no-floating-promises": "error", diff --git a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts index 890353d9039..293090ce315 100644 --- a/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection-admin.service.ts @@ -116,7 +116,7 @@ export class DefaultCollectionAdminService implements CollectionAdminService { const promises = collections.map(async (c) => { const view = new CollectionAdminView(); view.id = c.id; - view.name = await this.encryptService.decryptToUtf8(new EncString(c.name), orgKey); + view.name = await this.encryptService.decryptString(new EncString(c.name), orgKey); view.externalId = c.externalId; view.organizationId = c.organizationId; @@ -146,7 +146,7 @@ export class DefaultCollectionAdminService implements CollectionAdminService { } const collection = new CollectionRequest(); collection.externalId = model.externalId; - collection.name = (await this.encryptService.encrypt(model.name, key)).encryptedString; + collection.name = (await this.encryptService.encryptString(model.name, key)).encryptedString; collection.groups = model.groups.map( (group) => new SelectionReadOnlyRequest(group.id, group.readOnly, group.hidePasswords, group.manage), diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts index 7fe81ade4d2..c5f57f38dd3 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.spec.ts @@ -120,9 +120,12 @@ const mockStateProvider = () => { const mockCryptoService = () => { const keyService = mock(); const encryptService = mock(); - encryptService.decryptToUtf8 + encryptService.decryptString .calledWith(expect.any(EncString), expect.anything()) .mockResolvedValue("DECRYPTED_STRING"); + encryptService.decryptToUtf8 + .calledWith(expect.any(EncString), expect.anything(), expect.anything()) + .mockResolvedValue("DECRYPTED_STRING"); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); diff --git a/libs/admin-console/src/common/collections/services/default-collection.service.ts b/libs/admin-console/src/common/collections/services/default-collection.service.ts index 1ae58d3eef3..a1dd0419e2c 100644 --- a/libs/admin-console/src/common/collections/services/default-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-collection.service.ts @@ -113,7 +113,7 @@ export class DefaultCollectionService implements CollectionService { collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; collection.externalId = model.externalId; - collection.name = await this.encryptService.encrypt(model.name, key); + collection.name = await this.encryptService.encryptString(model.name, key); return collection; } diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts index 9700fcb695a..d4bc026b5bd 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.spec.ts @@ -46,6 +46,11 @@ describe("DefaultvNextCollectionService", () => { keyService.orgKeys$.mockReturnValue(cryptoKeys); // Set up mock decryption + encryptService.decryptString + .calledWith(expect.any(EncString), expect.any(SymmetricCryptoKey)) + .mockImplementation((encString, key) => + Promise.resolve(encString.data.replace("ENC_", "DEC_")), + ); encryptService.decryptToUtf8 .calledWith(expect.any(EncString), expect.any(SymmetricCryptoKey), expect.any(String)) .mockImplementation((encString, key) => @@ -103,6 +108,7 @@ describe("DefaultvNextCollectionService", () => { ]); // Assert that the correct org keys were used for each encrypted string + // This should be replaced with decryptString when the platform PR (https://github.com/bitwarden/clients/pull/14544) is merged expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( expect.objectContaining(new EncString(collection1.name)), orgKey1, diff --git a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts index 0ef8ae99ab3..4dcda795afe 100644 --- a/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts +++ b/libs/admin-console/src/common/collections/services/default-vnext-collection.service.ts @@ -113,7 +113,7 @@ export class DefaultvNextCollectionService implements vNextCollectionService { collection.organizationId = model.organizationId; collection.readOnly = model.readOnly; collection.externalId = model.externalId; - collection.name = await this.encryptService.encrypt(model.name, key); + collection.name = await this.encryptService.encryptString(model.name, key); return collection; } diff --git a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts index 7c068c51d86..871895c2ede 100644 --- a/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts +++ b/libs/angular/src/billing/components/add-account-credit-dialog/add-account-credit-dialog.component.ts @@ -22,6 +22,8 @@ export type AddAccountCreditDialogParams = { providerId?: string; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AddAccountCreditDialogResultType { Closed = "closed", Submitted = "submitted", diff --git a/libs/angular/src/platform/view-cache/index.ts b/libs/angular/src/platform/view-cache/index.ts new file mode 100644 index 00000000000..79deef6aa5b --- /dev/null +++ b/libs/angular/src/platform/view-cache/index.ts @@ -0,0 +1 @@ +export { ViewCacheService, FormCacheOptions, SignalCacheOptions } from "./view-cache.service"; diff --git a/libs/angular/src/platform/view-cache/internal.ts b/libs/angular/src/platform/view-cache/internal.ts new file mode 100644 index 00000000000..6e0992eecbf --- /dev/null +++ b/libs/angular/src/platform/view-cache/internal.ts @@ -0,0 +1 @@ +export { NoopViewCacheService } from "./noop-view-cache.service"; diff --git a/libs/angular/src/platform/services/noop-view-cache.service.ts b/libs/angular/src/platform/view-cache/noop-view-cache.service.ts similarity index 87% rename from libs/angular/src/platform/services/noop-view-cache.service.ts rename to libs/angular/src/platform/view-cache/noop-view-cache.service.ts index 9953e80b3b0..f83a0fc0b04 100644 --- a/libs/angular/src/platform/services/noop-view-cache.service.ts +++ b/libs/angular/src/platform/view-cache/noop-view-cache.service.ts @@ -1,11 +1,7 @@ import { Injectable, signal, WritableSignal } from "@angular/core"; import type { FormGroup } from "@angular/forms"; -import { - FormCacheOptions, - SignalCacheOptions, - ViewCacheService, -} from "../abstractions/view-cache.service"; +import { FormCacheOptions, SignalCacheOptions, ViewCacheService } from "./view-cache.service"; /** * The functionality of the {@link ViewCacheService} is only needed in the browser extension popup, diff --git a/libs/angular/src/platform/view-cache/view-cache.md b/libs/angular/src/platform/view-cache/view-cache.md new file mode 100644 index 00000000000..c1f80da5800 --- /dev/null +++ b/libs/angular/src/platform/view-cache/view-cache.md @@ -0,0 +1,130 @@ +# Extension Persistence + +By default, when the browser extension popup closes, the user's current view and any data entered +without saving is lost. This introduces friction in several workflows within our client, such as: + +- Performing actions that require email OTP entry, since the user must navigate from the popup to + get to their email inbox +- Entering information to create a new vault item from a browser tab +- And many more + +Previously, we have recommended that users "pop out" the extension into its own window to persist +the extension context, but this introduces additional user actions and may leave the extension open +(and unlocked) for longer than a user intends. + +In order to provide a better user experience, we have introduced two levels of persistence to the +Bitwarden extension client: + +- We persist the route history, allowing us to re-open the last route when the popup re-opens, and +- We offer a service for teams to use to persist component-specific form data or state to survive a + popup close/re-open cycle + +## Persistence lifetime + +Since we are persisting data, it is important that the lifetime of that data be well-understood and +well-constrained. The cache of route history and form data is cleared when any of the following +events occur: + +- The account is locked +- The account is logged out +- Account switching is used to switch the active account +- The extension popup has been closed for 2 minutes + +In addition, cached form data is cleared when a browser extension navigation event occurs (e.g. +switching between tabs in the extension). + +## Types of persistence + +### Route history persistence + +Route history is persisted on the extension automatically, with no specific implementation required +on any component. + +The persistence layer ensures that the popup will open at the same route as was active when it +closed, provided that none of the lifetime expiration events have occurred. + +:::tip Excluding a route + +If a particular route should be excluded from the history and not persisted, add +`doNotSaveUrl: true` to the `data` property on the route. + +::: + +### View data persistence + +Route persistence ensures that the user will land back on the route that they were on when the popup +closed, but it does not persist any state or form data that the user may have modified. In order to +persist that data, the component is responsible for registering that data with the +[`ViewCacheService`](./view-cache.service.ts). +This is done prescriptively to ensure that only necessary data is cached and that it is done with +intention by the component. + +The `ViewCacheService` provides an interface for caching both individual state and `FormGroup`s. + +#### Caching individual data elements + +For individual pieces of state, use the `signal()` method on the `ViewCacheService` to create a +writeable [signal](https://angular.dev/guide/signals) wrapper around the desired state. + +```typescript +const mySignal = this.viewCacheService.signal({ + key: "my-state-key" + initialValue: null +}); +``` + +If a cached value exists, the returned signal will contain the cached data. + +Setting the value should be done through the signal's `set()` method: + +```typescript +const mySignal = this.viewCacheService.signal({ + key: "my-state-key" + initialValue: null +}); +mySignal.set("value") +``` + +:::note Equality comparison + +By default, signals use `Object.is` to determine equality, and `set()` will only trigger updates if +the updated value is not equal to the current signal state. See documentation +[here](https://angular.dev/guide/signals#signal-equality-functions). + +::: + +Putting this together, the most common implementation pattern would be: + +1. **Register the signal** using `ViewCacheService.signal()` on initialization of the component or + service responsible for the state being persisted. +2. **Restore state from the signal:** If cached data exists, the signal will contain that data. The + component or service should use this data to re-create the state from prior to the popup closing. +3. **Set new state** in the cache when it changes. Ensure that any updates to the data are persisted + to the cache with `set()`, so that the cache reflects the latest state. + +#### Caching form data + +For persisting form data, the `ViewCacheService` supplies a `formGroup()` method, which manages the +persistence of any entered form data to the cache and the initialization of the form from the cached +data. You can supply the `FormGroup` in the `control` parameter of the method, and the +`ViewCacheService` will: + +- Initialize the form the a cached value, if it exists +- Save form value to cache when it changes +- Mark the form dirty if the restored value is not `undefined`. + +```typescript +this.loginDetailsForm = this.viewCacheService.formGroup({ + key: "my-form", + control: this.formBuilder.group({ + username: [""], + email: [""], + }), +}); +``` + +## What about other clients? + +The `ViewCacheService` is designed to be injected into shared, client-agnostic components. A +`NoopViewCacheService` is provided and injected for non-extension clients, preserving a single +interface for your components. diff --git a/libs/angular/src/platform/abstractions/view-cache.service.ts b/libs/angular/src/platform/view-cache/view-cache.service.ts similarity index 98% rename from libs/angular/src/platform/abstractions/view-cache.service.ts rename to libs/angular/src/platform/view-cache/view-cache.service.ts index c5ae6c77d1f..498a29aa242 100644 --- a/libs/angular/src/platform/abstractions/view-cache.service.ts +++ b/libs/angular/src/platform/view-cache/view-cache.service.ts @@ -42,6 +42,8 @@ export type FormCacheOptions = BaseCacheOptions< /** * Cache for temporary component state * + * [Read more](./view-cache.md) + * * #### Implementations * - browser extension popup: used to persist UI between popup open and close * - all other clients: noop diff --git a/libs/angular/src/scss/webfonts.css b/libs/angular/src/scss/webfonts.css index 04b072e1bf1..cd35271bf80 100644 --- a/libs/angular/src/scss/webfonts.css +++ b/libs/angular/src/scss/webfonts.css @@ -1,8 +1,8 @@ @font-face { - font-family: "DM Sans"; + font-family: Roboto; src: - url("webfonts/dm-sans.woff2") format("woff2 supports variations"), - url("webfonts/dm-sans.woff2") format("woff2-variations"); + url("webfonts/roboto.woff2") format("woff2 supports variations"), + url("webfonts/roboto.woff2") format("woff2-variations"); font-display: swap; font-weight: 100 900; } diff --git a/libs/angular/src/scss/webfonts/dm-sans.woff2 b/libs/angular/src/scss/webfonts/dm-sans.woff2 deleted file mode 100644 index 2375279dbf5..00000000000 Binary files a/libs/angular/src/scss/webfonts/dm-sans.woff2 and /dev/null differ diff --git a/libs/angular/src/scss/webfonts/roboto.woff2 b/libs/angular/src/scss/webfonts/roboto.woff2 new file mode 100644 index 00000000000..af8d2343d9c Binary files /dev/null and b/libs/angular/src/scss/webfonts/roboto.woff2 differ diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index a63d862b0d8..d82ff021962 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -13,6 +13,7 @@ import { import { Theme } from "@bitwarden/common/platform/enums"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { Message } from "@bitwarden/common/platform/messaging"; +import { HttpOperations } from "@bitwarden/common/services/api.service"; import { SafeInjectionToken } from "@bitwarden/ui-common"; // Re-export the SafeInjectionToken from ui-common export { SafeInjectionToken } from "@bitwarden/ui-common"; @@ -61,3 +62,5 @@ export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken( "ENV_ADDITIONAL_REGIONS", ); + +export const HTTP_OPERATIONS = new SafeInjectionToken("HTTP_OPERATIONS"); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index d82df8574ff..3ffca776034 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -325,18 +325,20 @@ import { import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction"; import { DeviceTrustToastService } from "../auth/services/device-trust-toast.service.implementation"; import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service"; -import { ViewCacheService } from "../platform/abstractions/view-cache.service"; import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service"; import { LoggingErrorHandler } from "../platform/services/logging-error-handler"; -import { NoopViewCacheService } from "../platform/services/noop-view-cache.service"; import { AngularThemingService } from "../platform/services/theming/angular-theming.service"; import { AbstractThemingService } from "../platform/services/theming/theming.service.abstraction"; import { safeProvider, SafeProvider } from "../platform/utils/safe-provider"; +import { ViewCacheService } from "../platform/view-cache"; +// eslint-disable-next-line no-restricted-imports -- Needed for DI +import { NoopViewCacheService } from "../platform/view-cache/internal"; import { CLIENT_TYPE, DEFAULT_VAULT_TIMEOUT, ENV_ADDITIONAL_REGIONS, + HTTP_OPERATIONS, INTRAPROCESS_MESSAGING_SUBJECT, LOCALES_DIRECTORY, LOCKED_CALLBACK, @@ -700,6 +702,10 @@ const safeProviders: SafeProvider[] = [ }, deps: [ToastService, I18nServiceAbstraction], }), + safeProvider({ + provide: HTTP_OPERATIONS, + useValue: { createRequest: (url, request) => new Request(url, request) }, + }), safeProvider({ provide: ApiServiceAbstraction, useClass: ApiService, @@ -712,6 +718,7 @@ const safeProviders: SafeProvider[] = [ LogService, LOGOUT_CALLBACK, VaultTimeoutSettingsService, + HTTP_OPERATIONS, ], }), safeProvider({ @@ -1072,7 +1079,7 @@ const safeProviders: SafeProvider[] = [ safeProvider({ provide: OrganizationSponsorshipApiServiceAbstraction, useClass: OrganizationSponsorshipApiService, - deps: [ApiServiceAbstraction], + deps: [ApiServiceAbstraction, PlatformUtilsServiceAbstraction], }), safeProvider({ provide: OrganizationBillingApiServiceAbstraction, diff --git a/libs/angular/src/tools/send/add-edit.component.ts b/libs/angular/src/tools/send/add-edit.component.ts index 7e6180e5849..0289664c365 100644 --- a/libs/angular/src/tools/send/add-edit.component.ts +++ b/libs/angular/src/tools/send/add-edit.component.ts @@ -36,6 +36,8 @@ import { SendService } from "@bitwarden/common/tools/send/services/send.service. import { DialogService, ToastService } from "@bitwarden/components"; // Value = hours +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum DatePreset { OneHour = 1, OneDay = 24, diff --git a/libs/auth/src/angular/input-password/input-password.component.ts b/libs/auth/src/angular/input-password/input-password.component.ts index bffb8a56b5e..dff4eafc3c2 100644 --- a/libs/auth/src/angular/input-password/input-password.component.ts +++ b/libs/auth/src/angular/input-password/input-password.component.ts @@ -36,6 +36,8 @@ import { PasswordInputResult } from "./password-input-result"; /** * Determines which form input elements will be displayed in the UI. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum InputPasswordFlow { /** * - Input: New password diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 945e6bbaaf5..3ea9416b7e2 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -41,6 +41,8 @@ import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper import { LoginDecryptionOptionsService } from "./login-decryption-options.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum State { NewUser, ExistingUserUntrustedDevice, diff --git a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts index ab5b0c09b32..d74deb443f5 100644 --- a/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts +++ b/libs/auth/src/angular/login-via-auth-request/login-via-auth-request.component.ts @@ -23,12 +23,10 @@ import { AuthRequest } from "@bitwarden/common/auth/models/request/auth.request" import { AuthRequestResponse } from "@bitwarden/common/auth/models/response/auth-request.response"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; import { ClientType, HttpStatusCode } from "@bitwarden/common/enums"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -42,6 +40,8 @@ import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legac import { AuthRequestApiService } from "../../common/abstractions/auth-request-api.service"; import { LoginViaAuthRequestCacheService } from "../../common/services/auth-request/default-login-via-auth-request-cache.service"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum Flow { StandardAuthRequest, // when user clicks "Login with device" from /login or "Approve from your other device" from /login-initiated AdminAuthRequest, // when user clicks "Request admin approval" from /login-initiated @@ -101,7 +101,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { private validationService: ValidationService, private loginSuccessHandlerService: LoginSuccessHandlerService, private loginViaAuthRequestCacheService: LoginViaAuthRequestCacheService, - private configService: ConfigService, ) { this.clientType = this.platformUtilsService.getClientType(); @@ -132,7 +131,6 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { // Get the authStatus early because we use it in both flows this.authStatus = await firstValueFrom(this.authService.activeAccountStatus$); - await this.loginViaAuthRequestCacheService.init(); const userHasAuthenticatedViaSSO = this.authStatus === AuthenticationStatus.Locked; @@ -410,24 +408,22 @@ export class LoginViaAuthRequestComponent implements OnInit, OnDestroy { const authRequestResponse: AuthRequestResponse = await this.authRequestApiService.postAuthRequest(authRequest); - if (await this.configService.getFeatureFlag(FeatureFlag.PM9112_DeviceApprovalPersistence)) { - if (!this.authRequestKeyPair.privateKey) { - this.logService.error("No private key when trying to cache the login view."); - return; - } - - if (!this.accessCode) { - this.logService.error("No access code when trying to cache the login view."); - return; - } - - this.loginViaAuthRequestCacheService.cacheLoginView( - authRequestResponse.id, - this.authRequestKeyPair.privateKey, - this.accessCode, - ); + if (!this.authRequestKeyPair.privateKey) { + this.logService.error("No private key when trying to cache the login view."); + return; } + if (!this.accessCode) { + this.logService.error("No access code when trying to cache the login view."); + return; + } + + this.loginViaAuthRequestCacheService.cacheLoginView( + authRequestResponse.id, + this.authRequestKeyPair.privateKey, + this.accessCode, + ); + if (authRequestResponse.id) { await this.anonymousHubService.createHubConnection(authRequestResponse.id); } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index cd226cddcec..d5180f56785 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -46,6 +46,8 @@ import { LoginComponentService, PasswordPolicies } from "./login-component.servi const BroadcasterSubscriptionId = "LoginComponent"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginUiState { EMAIL_ENTRY = "EmailEntry", MASTER_PASSWORD_ENTRY = "MasterPasswordEntry", diff --git a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts index e365ff09aa2..44d1d720a8d 100644 --- a/libs/auth/src/angular/registration/registration-start/registration-start.component.ts +++ b/libs/auth/src/angular/registration/registration-start/registration-start.component.ts @@ -26,6 +26,8 @@ import { RegistrationUserAddIcon } from "../../icons"; import { RegistrationCheckEmailIcon } from "../../icons/registration-check-email.icon"; import { RegistrationEnvSelectorComponent } from "../registration-env-selector/registration-env-selector.component"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum RegistrationStartState { USER_DATA_ENTRY = "UserDataEntry", CHECK_EMAIL = "CheckEmail", diff --git a/libs/auth/src/angular/sso/sso.component.ts b/libs/auth/src/angular/sso/sso.component.ts index a91a8ed20e9..968a05bf850 100644 --- a/libs/auth/src/angular/sso/sso.component.ts +++ b/libs/auth/src/angular/sso/sso.component.ts @@ -155,7 +155,14 @@ export class SsoComponent implements OnInit { return; } - // Detect if we have landed here but only have an SSO identifier in the URL. + // Detect if we are on the first portion of the SSO flow + // and have been sent here from another client with the info in query params. + // If so, we want to initialize the SSO flow with those values. + if (this.hasParametersFromOtherClientRedirect(qParams)) { + this.initializeFromRedirectFromOtherClient(qParams); + } + + // Detect if we have landed here with an SSO identifier in the URL. // This is used by integrations that want to "short-circuit" the login to send users // directly to their IdP to simulate IdP-initiated SSO, so we submit automatically. if (qParams.identifier != null) { @@ -165,13 +172,6 @@ export class SsoComponent implements OnInit { return; } - // Detect if we are on the first portion of the SSO flow - // and have been sent here from another client with the info in query params. - // If so, we want to initialize the SSO flow with those values. - if (this.hasParametersFromOtherClientRedirect(qParams)) { - this.initializeFromRedirectFromOtherClient(qParams); - } - // Try to determine the identifier using claimed domain or local state // persisted from the user's last login attempt. await this.initializeIdentifierFromEmailOrStorage(); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts index f3b904a4ea6..d2d86710b72 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-component-email-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts index 1613c0e4af8..36d99ee56ac 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts index e32b6cd1385..d274b8003d7 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email-component-cache.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; import { Jsonify } from "type-fest"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts index 0993954fde1..5b5d486556b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from "@angular/core/testing"; import { mock, MockProxy } from "jest-mock-extended"; import { BehaviorSubject } from "rxjs"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts index 61b44aa98dd..2d9fcaa5633 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component-cache.service.ts @@ -1,7 +1,7 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; import { Jsonify } from "type-fest"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index 2bb354a8cc3..c99722fb8e4 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -1,10 +1,14 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LegacyKeyMigrationAction { PREVENT_LOGIN_AND_SHOW_REQUIRE_MIGRATION_WARNING, NAVIGATE_TO_MIGRATION_COMPONENT, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DuoLaunchAction { DIRECT_LAUNCH, SINGLE_ACTION_POPOUT, diff --git a/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts b/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts index bceccc7f965..ef2bd1855c6 100644 --- a/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts +++ b/libs/auth/src/angular/user-verification/active-client-verification-option.enum.ts @@ -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 ActiveClientVerificationOption { MasterPassword = "masterPassword", Pin = "pin", diff --git a/libs/auth/src/angular/validators/compare-inputs.validator.ts b/libs/auth/src/angular/validators/compare-inputs.validator.ts index 24568ade0e3..79d547859a4 100644 --- a/libs/auth/src/angular/validators/compare-inputs.validator.ts +++ b/libs/auth/src/angular/validators/compare-inputs.validator.ts @@ -1,5 +1,7 @@ import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from "@angular/forms"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ValidationGoal { InputsShouldMatch, InputsShouldNotMatch, diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts index 89b78500f1f..67ac605e611 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.spec.ts @@ -1,9 +1,8 @@ import { signal } from "@angular/core"; import { TestBed } from "@angular/core/testing"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { LoginViaAuthRequestCacheService } from "./default-login-via-auth-request-cache.service"; @@ -14,74 +13,40 @@ describe("LoginViaAuthRequestCache", () => { const cacheSignal = signal(null); const getCacheSignal = jest.fn().mockReturnValue(cacheSignal); - const getFeatureFlag = jest.fn().mockResolvedValue(false); const cacheSetMock = jest.spyOn(cacheSignal, "set"); beforeEach(() => { getCacheSignal.mockClear(); - getFeatureFlag.mockClear(); cacheSetMock.mockClear(); testBed = TestBed.configureTestingModule({ providers: [ { provide: ViewCacheService, useValue: { signal: getCacheSignal } }, - { provide: ConfigService, useValue: { getFeatureFlag } }, LoginViaAuthRequestCacheService, ], }); }); - describe("feature enabled", () => { - beforeEach(() => { - getFeatureFlag.mockResolvedValue(true); - }); + it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => { + cacheSignal.set({ ...buildMockState() }); + service = testBed.inject(LoginViaAuthRequestCacheService); - it("`getCachedLoginViaAuthRequestView` returns the cached data", async () => { - cacheSignal.set({ ...buildMockState() }); - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - - expect(service.getCachedLoginViaAuthRequestView()).toEqual({ - ...buildMockState(), - }); - }); - - it("updates the signal value", async () => { - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - - const parameters = buildAuthenticMockAuthView(); - - service.cacheLoginView(parameters.id, parameters.privateKey, parameters.accessCode); - - expect(cacheSignal.set).toHaveBeenCalledWith({ - id: parameters.id, - privateKey: Utils.fromBufferToB64(parameters.privateKey), - accessCode: parameters.accessCode, - }); + expect(service.getCachedLoginViaAuthRequestView()).toEqual({ + ...buildMockState(), }); }); - describe("feature disabled", () => { - beforeEach(async () => { - cacheSignal.set({ ...buildMockState() } as LoginViaAuthRequestView); - getFeatureFlag.mockResolvedValue(false); - cacheSetMock.mockClear(); + it("updates the signal value", async () => { + service = testBed.inject(LoginViaAuthRequestCacheService); - service = testBed.inject(LoginViaAuthRequestCacheService); - await service.init(); - }); + const parameters = buildAuthenticMockAuthView(); - it("`getCachedCipherView` returns null", () => { - expect(service.getCachedLoginViaAuthRequestView()).toBeNull(); - }); + service.cacheLoginView(parameters.id, parameters.privateKey, parameters.accessCode); - it("does not update the signal value", () => { - const params = buildAuthenticMockAuthView(); - - service.cacheLoginView(params.id, params.privateKey, params.accessCode); - - expect(cacheSignal.set).not.toHaveBeenCalled(); + expect(cacheSignal.set).toHaveBeenCalledWith({ + id: parameters.id, + privateKey: Utils.fromBufferToB64(parameters.privateKey), + accessCode: parameters.accessCode, }); }); diff --git a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts index 493fea5c14b..80dbafd3159 100644 --- a/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts +++ b/libs/auth/src/common/services/auth-request/default-login-via-auth-request-cache.service.ts @@ -1,9 +1,7 @@ import { inject, Injectable, WritableSignal } from "@angular/core"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { LoginViaAuthRequestView } from "@bitwarden/common/auth/models/view/login-via-auth-request.view"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache"; @@ -17,10 +15,6 @@ const LOGIN_VIA_AUTH_CACHE_KEY = "login-via-auth-request-form-cache"; @Injectable() export class LoginViaAuthRequestCacheService { private viewCacheService: ViewCacheService = inject(ViewCacheService); - private configService: ConfigService = inject(ConfigService); - - /** True when the `PM9112_DeviceApproval` flag is enabled */ - private featureEnabled: boolean = false; private defaultLoginViaAuthRequestCache: WritableSignal = this.viewCacheService.signal({ @@ -31,23 +25,10 @@ export class LoginViaAuthRequestCacheService { constructor() {} - /** - * Must be called once before interacting with the cached data, otherwise methods will be noop. - */ - async init() { - this.featureEnabled = await this.configService.getFeatureFlag( - FeatureFlag.PM9112_DeviceApprovalPersistence, - ); - } - /** * Update the cache with the new LoginView. */ cacheLoginView(id: string, privateKey: Uint8Array, accessCode: string): void { - if (!this.featureEnabled) { - return; - } - // When the keys get stored they should be converted to a B64 string to ensure // data can be properly formed when json-ified. If not done, they are not stored properly and // will not be parsable by the cryptography library after coming out of storage. @@ -59,10 +40,6 @@ export class LoginViaAuthRequestCacheService { } clearCacheLoginView(): void { - if (!this.featureEnabled) { - return; - } - this.defaultLoginViaAuthRequestCache.set(null); } @@ -70,10 +47,6 @@ export class LoginViaAuthRequestCacheService { * Returns the cached LoginViaAuthRequestView when available. */ getCachedLoginViaAuthRequestView(): LoginViaAuthRequestView | null { - if (!this.featureEnabled) { - return null; - } - return this.defaultLoginViaAuthRequestCache(); } } diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts index 074c3a1e0b1..632a2812cfe 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.spec.ts @@ -92,4 +92,27 @@ describe("SsoUrlService", () => { ); expect(result).toBe(expectedUrl); }); + + it("should build CLI SSO URL with Org SSO Identifier correctly", () => { + const baseUrl = "https://web-vault.bitwarden.com"; + const clientType = ClientType.Cli; + const redirectUri = "https://localhost:1000"; + const state = "abc123"; + const codeChallenge = "xyz789"; + const email = "test@bitwarden.com"; + const orgSsoIdentifier = "test-org"; + + const expectedUrl = `${baseUrl}/#/sso?clientId=cli&redirectUri=${encodeURIComponent(redirectUri)}&state=${state}&codeChallenge=${codeChallenge}&email=${encodeURIComponent(email)}&identifier=${encodeURIComponent(orgSsoIdentifier)}`; + + const result = service.buildSsoUrl( + baseUrl, + clientType, + redirectUri, + state, + codeChallenge, + email, + orgSsoIdentifier, + ); + expect(result).toBe(expectedUrl); + }); }); diff --git a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts index 667a27ad598..b2d6231db7c 100644 --- a/libs/auth/src/common/services/sso-redirect/sso-url.service.ts +++ b/libs/auth/src/common/services/sso-redirect/sso-url.service.ts @@ -11,6 +11,7 @@ export class SsoUrlService { * @param state A state value that will be peristed through the SSO flow * @param codeChallenge A challenge value that will be used to verify the SSO code after authentication * @param email The optional email adddress of the user initiating SSO, which will be used to look up the org SSO identifier + * @param orgSsoIdentifier The optional SSO identifier of the org that is initiating SSO * @returns The URL for redirecting users to the web app SSO component */ buildSsoUrl( @@ -20,6 +21,7 @@ export class SsoUrlService { state: string, codeChallenge: string, email?: string, + orgSsoIdentifier?: string, ): string { let url = webAppUrl + @@ -36,6 +38,10 @@ export class SsoUrlService { url += "&email=" + encodeURIComponent(email); } + if (orgSsoIdentifier) { + url += "&identifier=" + encodeURIComponent(orgSsoIdentifier); + } + return url; } } diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts index 1e13a3064f4..e4453359015 100644 --- a/libs/common/src/abstractions/api.service.ts +++ b/libs/common/src/abstractions/api.service.ts @@ -475,7 +475,6 @@ export abstract class ApiService { getSponsorshipSyncStatus: ( sponsoredOrgId: string, ) => Promise; - deleteRevokeSponsorship: (sponsoringOrganizationId: string) => Promise; deleteRemoveSponsorship: (sponsoringOrgId: string) => Promise; postPreValidateSponsorshipToken: ( sponsorshipToken: string, diff --git a/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts b/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts index 44ba7f8391d..bb98ea8718b 100644 --- a/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-api-key-type.enum.ts @@ -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 OrganizationApiKeyType { Default = 0, BillingSync = 1, diff --git a/libs/common/src/admin-console/enums/organization-connection-type.enum.ts b/libs/common/src/admin-console/enums/organization-connection-type.enum.ts index d2f9700a6a0..3cd11a29496 100644 --- a/libs/common/src/admin-console/enums/organization-connection-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-connection-type.enum.ts @@ -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 OrganizationConnectionType { CloudBillingSync = 1, Scim = 2, diff --git a/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts b/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts index f5fa0e25c91..df9ccc8b430 100644 --- a/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-user-status-type.enum.ts @@ -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 OrganizationUserStatusType { Invited = 0, Accepted = 1, diff --git a/libs/common/src/admin-console/enums/organization-user-type.enum.ts b/libs/common/src/admin-console/enums/organization-user-type.enum.ts index da50bfbdc20..098374becb4 100644 --- a/libs/common/src/admin-console/enums/organization-user-type.enum.ts +++ b/libs/common/src/admin-console/enums/organization-user-type.enum.ts @@ -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 OrganizationUserType { Owner = 0, Admin = 1, diff --git a/libs/common/src/admin-console/enums/policy-type.enum.ts b/libs/common/src/admin-console/enums/policy-type.enum.ts index 336b834ca56..42ab798eabf 100644 --- a/libs/common/src/admin-console/enums/policy-type.enum.ts +++ b/libs/common/src/admin-console/enums/policy-type.enum.ts @@ -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 PolicyType { TwoFactorAuthentication = 0, // Requires users to have 2fa enabled MasterPassword = 1, // Sets minimum requirements for master password complexity diff --git a/libs/common/src/admin-console/enums/provider-status-type.enum.ts b/libs/common/src/admin-console/enums/provider-status-type.enum.ts index 8da60af0eb3..32b730510e0 100644 --- a/libs/common/src/admin-console/enums/provider-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-status-type.enum.ts @@ -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 ProviderStatusType { Pending = 0, Created = 1, diff --git a/libs/common/src/admin-console/enums/provider-type.enum.ts b/libs/common/src/admin-console/enums/provider-type.enum.ts index eb48e362e7d..dbb60fb3638 100644 --- a/libs/common/src/admin-console/enums/provider-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-type.enum.ts @@ -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 ProviderType { Msp = 0, Reseller = 1, diff --git a/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts b/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts index 38d6c2e4961..253128aa25f 100644 --- a/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-user-status-type.enum.ts @@ -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 ProviderUserStatusType { Invited = 0, Accepted = 1, diff --git a/libs/common/src/admin-console/enums/provider-user-type.enum.ts b/libs/common/src/admin-console/enums/provider-user-type.enum.ts index 00490adcfca..d20755c3196 100644 --- a/libs/common/src/admin-console/enums/provider-user-type.enum.ts +++ b/libs/common/src/admin-console/enums/provider-user-type.enum.ts @@ -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 ProviderUserType { ProviderAdmin = 0, ServiceUser = 1, diff --git a/libs/common/src/admin-console/enums/scim-provider-type.enum.ts b/libs/common/src/admin-console/enums/scim-provider-type.enum.ts index 43c518fdfbf..cd33a1cf891 100644 --- a/libs/common/src/admin-console/enums/scim-provider-type.enum.ts +++ b/libs/common/src/admin-console/enums/scim-provider-type.enum.ts @@ -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 ScimProviderType { Default = 0, AzureAd = 1, diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index fae24133502..7d66e7bc0d5 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -59,6 +59,7 @@ describe("ORGANIZATIONS state", () => { userIsManagedByOrganization: false, useRiskInsights: false, useAdminSponsoredFamilies: false, + isAdminInitiated: false, }, }; const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index 799d062aefa..e0783957117 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -61,6 +61,7 @@ export class OrganizationData { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor( response?: ProfileOrganizationResponse, @@ -124,6 +125,7 @@ export class OrganizationData { this.userIsManagedByOrganization = response.userIsManagedByOrganization; this.useRiskInsights = response.useRiskInsights; this.useAdminSponsoredFamilies = response.useAdminSponsoredFamilies; + this.isAdminInitiated = response.isAdminInitiated; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts index 984d80ba519..297bcf08d8c 100644 --- a/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts +++ b/libs/common/src/admin-console/models/domain/encrypted-organization-key.ts @@ -56,14 +56,14 @@ export class ProviderEncryptedOrganizationKey implements BaseEncryptedOrganizati ) {} async decrypt(encryptService: EncryptService, providerKeys: Record) { - const decValue = await encryptService.decryptToBytes( + const decValue = await encryptService.unwrapSymmetricKey( new EncString(this.key), providerKeys[this.providerId], ); if (decValue == null) { throw new Error("Failed to decrypt organization key"); } - return new SymmetricCryptoKey(decValue) as OrgKey; + return decValue as OrgKey; } get encryptedOrganizationKey() { diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 2e51c54b0ad..1864d56649b 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -91,6 +91,7 @@ export class Organization { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -150,6 +151,7 @@ export class Organization { this.userIsManagedByOrganization = obj.userIsManagedByOrganization; this.useRiskInsights = obj.useRiskInsights; this.useAdminSponsoredFamilies = obj.useAdminSponsoredFamilies; + this.isAdminInitiated = obj.isAdminInitiated; } get canAccess() { diff --git a/libs/common/src/admin-console/models/request/organization-create.request.ts b/libs/common/src/admin-console/models/request/organization-create.request.ts index e8561307b20..d9c62f1e20a 100644 --- a/libs/common/src/admin-console/models/request/organization-create.request.ts +++ b/libs/common/src/admin-console/models/request/organization-create.request.ts @@ -6,4 +6,5 @@ import { OrganizationNoPaymentMethodCreateRequest } from "../../../billing/model export class OrganizationCreateRequest extends OrganizationNoPaymentMethodCreateRequest { paymentMethodType: PaymentMethodType; paymentToken: string; + skipTrial?: boolean; } diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index da97a1034b1..3a86c764eb8 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -56,6 +56,7 @@ export class ProfileOrganizationResponse extends BaseResponse { userIsManagedByOrganization: boolean; useRiskInsights: boolean; useAdminSponsoredFamilies: boolean; + isAdminInitiated: boolean; constructor(response: any) { super(response); @@ -123,5 +124,6 @@ export class ProfileOrganizationResponse extends BaseResponse { this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization"); this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); this.useAdminSponsoredFamilies = this.getResponseProperty("UseAdminSponsoredFamilies"); + this.isAdminInitiated = this.getResponseProperty("IsAdminInitiated"); } } diff --git a/libs/common/src/auth/enums/auth-request-type.ts b/libs/common/src/auth/enums/auth-request-type.ts index 31db2467861..d23fec03205 100644 --- a/libs/common/src/auth/enums/auth-request-type.ts +++ b/libs/common/src/auth/enums/auth-request-type.ts @@ -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 AuthRequestType { AuthenticateAndUnlock = 0, Unlock = 1, diff --git a/libs/common/src/auth/enums/authentication-status.ts b/libs/common/src/auth/enums/authentication-status.ts index 17b4f1f21e8..6a6f9467ae7 100644 --- a/libs/common/src/auth/enums/authentication-status.ts +++ b/libs/common/src/auth/enums/authentication-status.ts @@ -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 AuthenticationStatus { LoggedOut = 0, Locked = 1, diff --git a/libs/common/src/auth/enums/authentication-type.ts b/libs/common/src/auth/enums/authentication-type.ts index 35b50e6400a..13a3d70ddda 100644 --- a/libs/common/src/auth/enums/authentication-type.ts +++ b/libs/common/src/auth/enums/authentication-type.ts @@ -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 AuthenticationType { Password = 0, Sso = 1, diff --git a/libs/common/src/auth/enums/sso.ts b/libs/common/src/auth/enums/sso.ts index 0c86a27151f..1e5766c7afd 100644 --- a/libs/common/src/auth/enums/sso.ts +++ b/libs/common/src/auth/enums/sso.ts @@ -1,25 +1,35 @@ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SsoType { None = 0, OpenIdConnect = 1, Saml2 = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum MemberDecryptionType { MasterPassword = 0, KeyConnector = 1, TrustedDeviceEncryption = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum OpenIdConnectRedirectBehavior { RedirectGet = 0, FormPost = 1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2BindingType { HttpRedirect = 1, HttpPost = 2, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2NameIdFormat { NotConfigured = 0, Unspecified = 1, @@ -32,6 +42,8 @@ export enum Saml2NameIdFormat { Transient = 8, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Saml2SigningBehavior { IfIdpWantAuthnRequestsSigned = 0, Always = 1, diff --git a/libs/common/src/auth/enums/two-factor-provider-type.ts b/libs/common/src/auth/enums/two-factor-provider-type.ts index b3308b6c12f..9be22bf90c8 100644 --- a/libs/common/src/auth/enums/two-factor-provider-type.ts +++ b/libs/common/src/auth/enums/two-factor-provider-type.ts @@ -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 TwoFactorProviderType { Authenticator = 0, Email = 1, diff --git a/libs/common/src/auth/enums/verification-type.ts b/libs/common/src/auth/enums/verification-type.ts index c1991162f91..f0ad4c0d19c 100644 --- a/libs/common/src/auth/enums/verification-type.ts +++ b/libs/common/src/auth/enums/verification-type.ts @@ -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 VerificationType { MasterPassword = 0, OTP = 1, diff --git a/libs/common/src/auth/models/domain/force-set-password-reason.ts b/libs/common/src/auth/models/domain/force-set-password-reason.ts index 56d52860443..68392125281 100644 --- a/libs/common/src/auth/models/domain/force-set-password-reason.ts +++ b/libs/common/src/auth/models/domain/force-set-password-reason.ts @@ -2,6 +2,8 @@ * This enum is used to determine if a user should be forced to initially set or reset their password * on login (server flag) or unlock via MP (client evaluation). */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ForceSetPasswordReason { /** * A password reset should not be forced. diff --git a/libs/common/src/auth/services/token.service.ts b/libs/common/src/auth/services/token.service.ts index c27afa6805a..61c00f69215 100644 --- a/libs/common/src/auth/services/token.service.ts +++ b/libs/common/src/auth/services/token.service.ts @@ -43,6 +43,8 @@ import { SECURITY_STAMP_MEMORY, } from "./token.state"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum TokenStorageLocation { Disk = "disk", SecureStorage = "secureStorage", diff --git a/libs/common/src/autofill/constants/index.ts b/libs/common/src/autofill/constants/index.ts index 62ad10e9a90..dc79e27b6aa 100644 --- a/libs/common/src/autofill/constants/index.ts +++ b/libs/common/src/autofill/constants/index.ts @@ -38,7 +38,7 @@ export const ClearClipboardDelay = { FiveMinutes: 300, } as const; -/* Context Menu item Ids */ +/* Ids for context menu items and messaging events */ export const AUTOFILL_CARD_ID = "autofill-card"; export const AUTOFILL_ID = "autofill"; export const SHOW_AUTOFILL_BUTTON = "show-autofill-button"; @@ -54,6 +54,7 @@ export const GENERATE_PASSWORD_ID = "generate-password"; export const NOOP_COMMAND_SUFFIX = "noop"; export const ROOT_ID = "root"; export const SEPARATOR_ID = "separator"; +export const UPDATE_PASSWORD = "update-password"; export const NOTIFICATION_BAR_LIFESPAN_MS = 150000; // 150 seconds diff --git a/libs/common/src/billing/abstractions/organization-billing.service.ts b/libs/common/src/billing/abstractions/organization-billing.service.ts index 8024a120b0a..58c537c99cc 100644 --- a/libs/common/src/billing/abstractions/organization-billing.service.ts +++ b/libs/common/src/billing/abstractions/organization-billing.service.ts @@ -39,6 +39,7 @@ export type BillingInformation = { export type PaymentInformation = { paymentMethod: [string, PaymentMethodType]; billing: BillingInformation; + skipTrial?: boolean; }; export type SubscriptionInformation = { diff --git a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts index 7bd6aac17bd..5e69f57ca19 100644 --- a/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/organizations/organization-sponsorship-api.service.abstraction.ts @@ -10,4 +10,9 @@ export abstract class OrganizationSponsorshipApiServiceAbstraction { sponsoringOrgId: string, friendlyName?: string, ): Promise; + + abstract deleteRevokeSponsorship: ( + sponsoringOrganizationId: string, + isAdminInitiated?: boolean, + ) => Promise; } diff --git a/libs/common/src/billing/abstractions/tax.service.abstraction.ts b/libs/common/src/billing/abstractions/tax.service.abstraction.ts index 7a744dae856..73dc848c95f 100644 --- a/libs/common/src/billing/abstractions/tax.service.abstraction.ts +++ b/libs/common/src/billing/abstractions/tax.service.abstraction.ts @@ -1,7 +1,9 @@ import { CountryListItem } from "../models/domain"; import { PreviewIndividualInvoiceRequest } from "../models/request/preview-individual-invoice.request"; import { PreviewOrganizationInvoiceRequest } from "../models/request/preview-organization-invoice.request"; +import { PreviewTaxAmountForOrganizationTrialRequest } from "../models/request/tax"; import { PreviewInvoiceResponse } from "../models/response/preview-invoice.response"; +import { PreviewTaxAmountResponse } from "../models/response/tax"; export abstract class TaxServiceAbstraction { abstract getCountries(): CountryListItem[]; @@ -15,4 +17,8 @@ export abstract class TaxServiceAbstraction { abstract previewOrganizationInvoice( request: PreviewOrganizationInvoiceRequest, ): Promise; + + abstract previewTaxAmountForOrganizationTrial: ( + request: PreviewTaxAmountForOrganizationTrialRequest, + ) => Promise; } diff --git a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts b/libs/common/src/billing/enums/bitwarden-product-type.enum.ts index 76b0899fd9c..4389d283c00 100644 --- a/libs/common/src/billing/enums/bitwarden-product-type.enum.ts +++ b/libs/common/src/billing/enums/bitwarden-product-type.enum.ts @@ -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 BitwardenProductType { PasswordManager = 0, SecretsManager = 1, diff --git a/libs/common/src/billing/enums/payment-method-type.enum.ts b/libs/common/src/billing/enums/payment-method-type.enum.ts index 9b29263175c..6ea98fdc5ec 100644 --- a/libs/common/src/billing/enums/payment-method-type.enum.ts +++ b/libs/common/src/billing/enums/payment-method-type.enum.ts @@ -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 PaymentMethodType { Card = 0, BankAccount = 1, diff --git a/libs/common/src/billing/enums/plan-interval.enum.ts b/libs/common/src/billing/enums/plan-interval.enum.ts index 546336748cd..c048160310b 100644 --- a/libs/common/src/billing/enums/plan-interval.enum.ts +++ b/libs/common/src/billing/enums/plan-interval.enum.ts @@ -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 PlanInterval { Monthly = 0, Annually = 1, diff --git a/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts b/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts index 3b4c00467c2..845b698f509 100644 --- a/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts +++ b/libs/common/src/billing/enums/plan-sponsorship-type.enum.ts @@ -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 PlanSponsorshipType { FamiliesForEnterprise = 0, } diff --git a/libs/common/src/billing/enums/plan-type.enum.ts b/libs/common/src/billing/enums/plan-type.enum.ts index c8977703454..5c356ce42fe 100644 --- a/libs/common/src/billing/enums/plan-type.enum.ts +++ b/libs/common/src/billing/enums/plan-type.enum.ts @@ -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 PlanType { Free = 0, FamiliesAnnually2019 = 1, diff --git a/libs/common/src/billing/enums/product-tier-type.enum.ts b/libs/common/src/billing/enums/product-tier-type.enum.ts index fadf57ccdc5..e94243de588 100644 --- a/libs/common/src/billing/enums/product-tier-type.enum.ts +++ b/libs/common/src/billing/enums/product-tier-type.enum.ts @@ -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 ProductTierType { Free = 0, Families = 1, diff --git a/libs/common/src/billing/enums/product-type.enum.ts b/libs/common/src/billing/enums/product-type.enum.ts index 3072ad0f96a..0c11eb92c79 100644 --- a/libs/common/src/billing/enums/product-type.enum.ts +++ b/libs/common/src/billing/enums/product-type.enum.ts @@ -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 ProductType { PasswordManager = 0, SecretsManager = 1, diff --git a/libs/common/src/billing/enums/transaction-type.enum.ts b/libs/common/src/billing/enums/transaction-type.enum.ts index 34731a2ec33..d4c48253a39 100644 --- a/libs/common/src/billing/enums/transaction-type.enum.ts +++ b/libs/common/src/billing/enums/transaction-type.enum.ts @@ -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 TransactionType { Charge = 0, Credit = 1, diff --git a/libs/common/src/billing/models/request/tax/index.ts b/libs/common/src/billing/models/request/tax/index.ts new file mode 100644 index 00000000000..cda1930c614 --- /dev/null +++ b/libs/common/src/billing/models/request/tax/index.ts @@ -0,0 +1 @@ +export * from "./preview-tax-amount-for-organization-trial.request"; diff --git a/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts b/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts new file mode 100644 index 00000000000..3f366335a47 --- /dev/null +++ b/libs/common/src/billing/models/request/tax/preview-tax-amount-for-organization-trial.request.ts @@ -0,0 +1,11 @@ +import { PlanType, ProductType } from "../../../enums"; + +export type PreviewTaxAmountForOrganizationTrialRequest = { + planType: PlanType; + productType: ProductType; + taxInformation: { + country: string; + postalCode: string; + taxId?: string; + }; +}; diff --git a/libs/common/src/billing/models/response/tax/index.ts b/libs/common/src/billing/models/response/tax/index.ts new file mode 100644 index 00000000000..525d6d7c80a --- /dev/null +++ b/libs/common/src/billing/models/response/tax/index.ts @@ -0,0 +1 @@ +export * from "./preview-tax-amount.response"; diff --git a/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts b/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts new file mode 100644 index 00000000000..cf15156551a --- /dev/null +++ b/libs/common/src/billing/models/response/tax/preview-tax-amount.response.ts @@ -0,0 +1,11 @@ +import { BaseResponse } from "../../../../models/response/base.response"; + +export class PreviewTaxAmountResponse extends BaseResponse { + taxAmount: number; + + constructor(response: any) { + super(response); + + this.taxAmount = this.getResponseProperty("TaxAmount"); + } +} diff --git a/libs/common/src/billing/services/organization-billing.service.ts b/libs/common/src/billing/services/organization-billing.service.ts index c6bd88d8dd6..fe5623fd5e6 100644 --- a/libs/common/src/billing/services/organization-billing.service.ts +++ b/libs/common/src/billing/services/organization-billing.service.ts @@ -174,6 +174,7 @@ export class OrganizationBillingService implements OrganizationBillingServiceAbs const [paymentToken, paymentMethodType] = information.paymentMethod; request.paymentToken = paymentToken; request.paymentMethodType = paymentMethodType; + request.skipTrial = information.skipTrial; const billingInformation = information.billing; request.billingAddressPostalCode = billingInformation.postalCode; diff --git a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts index 22ab3ec86ee..99440b10dec 100644 --- a/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts +++ b/libs/common/src/billing/services/organization/organization-sponsorship-api.service.ts @@ -1,12 +1,16 @@ import { ApiService } from "../../../abstractions/api.service"; import { ListResponse } from "../../../models/response/list.response"; +import { PlatformUtilsService } from "../../../platform/abstractions/platform-utils.service"; import { OrganizationSponsorshipApiServiceAbstraction } from "../../abstractions/organizations/organization-sponsorship-api.service.abstraction"; import { OrganizationSponsorshipInvitesResponse } from "../../models/response/organization-sponsorship-invites.response"; export class OrganizationSponsorshipApiService implements OrganizationSponsorshipApiServiceAbstraction { - constructor(private apiService: ApiService) {} + constructor( + private apiService: ApiService, + private platformUtilsService: PlatformUtilsService, + ) {} async getOrganizationSponsorship( sponsoredOrgId: string, ): Promise> { @@ -33,4 +37,21 @@ export class OrganizationSponsorshipApiService return await this.apiService.send("POST", url, null, true, false); } + + async deleteRevokeSponsorship( + sponsoringOrganizationId: string, + isAdminInitiated: boolean = false, + ): Promise { + const basePath = "/organization/sponsorship/"; + const hostPath = this.platformUtilsService.isSelfHost() ? "self-hosted/" : ""; + const queryParam = `?isAdminInitiated=${isAdminInitiated}`; + + return await this.apiService.send( + "DELETE", + basePath + hostPath + sponsoringOrganizationId + queryParam, + null, + true, + false, + ); + } } diff --git a/libs/common/src/billing/services/tax.service.ts b/libs/common/src/billing/services/tax.service.ts index aa27c99adc8..2632ca7083b 100644 --- a/libs/common/src/billing/services/tax.service.ts +++ b/libs/common/src/billing/services/tax.service.ts @@ -1,3 +1,6 @@ +import { PreviewTaxAmountForOrganizationTrialRequest } from "@bitwarden/common/billing/models/request/tax"; +import { PreviewTaxAmountResponse } from "@bitwarden/common/billing/models/response/tax"; + import { ApiService } from "../../abstractions/api.service"; import { TaxServiceAbstraction } from "../abstractions/tax.service.abstraction"; import { CountryListItem } from "../models/domain"; @@ -300,4 +303,16 @@ export class TaxService implements TaxServiceAbstraction { ); return new PreviewInvoiceResponse(response); } + + async previewTaxAmountForOrganizationTrial( + request: PreviewTaxAmountForOrganizationTrialRequest, + ): Promise { + return await this.apiService.send( + "POST", + "/tax/preview-amount/organization-trial", + request, + true, + true, + ); + } } diff --git a/libs/common/src/enums/client-type.enum.ts b/libs/common/src/enums/client-type.enum.ts index 54653f74462..25e9d6f3371 100644 --- a/libs/common/src/enums/client-type.enum.ts +++ b/libs/common/src/enums/client-type.enum.ts @@ -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 ClientType { Web = "web", Browser = "browser", diff --git a/libs/common/src/enums/device-type.enum.ts b/libs/common/src/enums/device-type.enum.ts index ff6329b9ac4..d5628536ff7 100644 --- a/libs/common/src/enums/device-type.enum.ts +++ b/libs/common/src/enums/device-type.enum.ts @@ -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 DeviceType { Android = 0, iOS = 1, diff --git a/libs/common/src/enums/event-system-user.enum.ts b/libs/common/src/enums/event-system-user.enum.ts index e2d43a4f113..f4abbb1e3e9 100644 --- a/libs/common/src/enums/event-system-user.enum.ts +++ b/libs/common/src/enums/event-system-user.enum.ts @@ -1,4 +1,6 @@ // Note: the enum key is used to describe the EventSystemUser in the UI. Be careful about changing it. +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EventSystemUser { SCIM = 1, DomainVerification = 2, diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts index 51b324bb434..9efe49b1b0f 100644 --- a/libs/common/src/enums/event-type.enum.ts +++ b/libs/common/src/enums/event-type.enum.ts @@ -1,4 +1,6 @@ // Increment by 100 for each new set of events +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum EventType { User_LoggedIn = 1000, User_ChangedPassword = 1001, diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 08de8ada25a..ddc75eb0d66 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -7,6 +7,8 @@ import { ServerConfig } from "../platform/abstractions/config/server-config"; * * Flags should be grouped by team to have visibility of ownership and cleanup. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FeatureFlag { /* Admin Console Team */ VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", @@ -16,7 +18,6 @@ export enum FeatureFlag { SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions", /* Auth */ - PM9112_DeviceApprovalPersistence = "pm-9112-device-approval-persistence", PM9115_TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence", /* Autofill */ @@ -55,7 +56,6 @@ export enum FeatureFlag { /* Vault */ PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge", PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form", - VaultBulkManagementAction = "vault-bulk-management-action", SecurityTasks = "security-tasks", CipherKeyEncryption = "cipher-key-encryption", PM18520_UpdateDesktopCipherForm = "pm-18520-desktop-cipher-forms", @@ -107,14 +107,12 @@ export const DefaultFeatureFlagValue = { /* Vault */ [FeatureFlag.PM8851_BrowserOnboardingNudge]: FALSE, [FeatureFlag.PM9111ExtensionPersistAddEditForm]: FALSE, - [FeatureFlag.VaultBulkManagementAction]: FALSE, [FeatureFlag.SecurityTasks]: FALSE, [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.PM18520_UpdateDesktopCipherForm]: FALSE, [FeatureFlag.EndUserNotifications]: FALSE, /* Auth */ - [FeatureFlag.PM9112_DeviceApprovalPersistence]: FALSE, [FeatureFlag.PM9115_TwoFactorExtensionDataPersistence]: FALSE, /* Billing */ diff --git a/libs/common/src/enums/http-status-code.enum.ts b/libs/common/src/enums/http-status-code.enum.ts index 94226c82552..d2dba5d8d4c 100644 --- a/libs/common/src/enums/http-status-code.enum.ts +++ b/libs/common/src/enums/http-status-code.enum.ts @@ -4,6 +4,8 @@ * @see {@link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes} * src: https://gist.github.com/RWOverdijk/6cef816cfdf5722228e01cc05fd4b094 */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum HttpStatusCode { /** * The server has received the request headers and the client should proceed to send the request body diff --git a/libs/common/src/enums/integration-type.enum.ts b/libs/common/src/enums/integration-type.enum.ts index 42c385fe715..ac8bb9c6afa 100644 --- a/libs/common/src/enums/integration-type.enum.ts +++ b/libs/common/src/enums/integration-type.enum.ts @@ -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 IntegrationType { Integration = "integration", SDK = "sdk", diff --git a/libs/common/src/enums/native-messaging-version.enum.ts b/libs/common/src/enums/native-messaging-version.enum.ts index f7cf411a40a..f83ec8f4b25 100644 --- a/libs/common/src/enums/native-messaging-version.enum.ts +++ b/libs/common/src/enums/native-messaging-version.enum.ts @@ -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 NativeMessagingVersion { One = 1, // Original implementation Latest = One, diff --git a/libs/common/src/enums/notification-type.enum.ts b/libs/common/src/enums/notification-type.enum.ts index 0e4d0bfee3d..6d731253ce3 100644 --- a/libs/common/src/enums/notification-type.enum.ts +++ b/libs/common/src/enums/notification-type.enum.ts @@ -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 NotificationType { SyncCipherUpdate = 0, SyncCipherCreate = 1, diff --git a/libs/common/src/enums/push-technology.enum.ts b/libs/common/src/enums/push-technology.enum.ts index 9452c144bb7..1bc4e62cc9d 100644 --- a/libs/common/src/enums/push-technology.enum.ts +++ b/libs/common/src/enums/push-technology.enum.ts @@ -1,6 +1,8 @@ /** * The preferred push technology of the server. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum PushTechnology { /** * Indicates that we should use SignalR over web sockets to receive push notifications from the server. diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index 7a6c9bcd800..8bd58a21b6e 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -49,6 +49,7 @@ export abstract class EncryptService { key: SymmetricCryptoKey, decryptTrace?: string, ): Promise; + /** * @deprecated Replaced by BulkEncryptService, remove once the feature is tested and the featureflag PM-4154-multi-worker-encryption-service is removed * @param items The items to decrypt diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index 06999cab9c3..5e89e0a5cb7 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -209,7 +209,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { devices.data .filter((device) => device.isTrusted) .map(async (device) => { - const publicKey = await this.encryptService.decryptToBytes( + const publicKey = await this.encryptService.unwrapEncapsulationKey( new EncString(device.encryptedPublicKey), oldUserKey, ); @@ -220,7 +220,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { return null; } - const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); + const newEncryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + publicKey, + newUserKey, + ); const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( newUserKey, publicKey, @@ -278,7 +281,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { const currentDeviceKeys = await this.devicesApiService.getDeviceKeys(deviceIdentifier); // Decrypt the existing device public key with the old user key - const decryptedDevicePublicKey = await this.encryptService.decryptToBytes( + const decryptedDevicePublicKey = await this.encryptService.unwrapEncapsulationKey( currentDeviceKeys.encryptedPublicKey, oldUserKey, ); @@ -394,7 +397,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { try { // attempt to decrypt encryptedDevicePrivateKey with device key - const devicePrivateKey = await this.encryptService.decryptToBytes( + const devicePrivateKey = await this.encryptService.unwrapDecapsulationKey( encryptedDevicePrivateKey, deviceKey, ); diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 7d4a25c1f08..de9de5d781a 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -623,9 +623,9 @@ describe("deviceTrustService", () => { }); it("successfully returns the user key when provided keys (including device key) can decrypt it", async () => { - const decryptToBytesSpy = jest - .spyOn(encryptService, "decryptToBytes") - .mockResolvedValue(new Uint8Array(userKeyBytesLength)); + const unwrapDecapsulationKeySpy = jest + .spyOn(encryptService, "unwrapDecapsulationKey") + .mockResolvedValue(new Uint8Array(2048)); const rsaDecryptSpy = jest .spyOn(encryptService, "decapsulateKeyUnsigned") .mockResolvedValue(new SymmetricCryptoKey(new Uint8Array(userKeyBytesLength))); @@ -638,13 +638,13 @@ describe("deviceTrustService", () => { ); expect(result).toEqual(mockUserKey); - expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); + expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); expect(rsaDecryptSpy).toHaveBeenCalledTimes(1); }); it("returns null and removes device key when the decryption fails", async () => { - const decryptToBytesSpy = jest - .spyOn(encryptService, "decryptToBytes") + const unwrapDecapsulationKeySpy = jest + .spyOn(encryptService, "unwrapDecapsulationKey") .mockRejectedValue(new Error("Decryption error")); const setDeviceKeySpy = jest.spyOn(deviceTrustService as any, "setDeviceKey"); @@ -656,7 +656,7 @@ describe("deviceTrustService", () => { ); expect(result).toBeNull(); - expect(decryptToBytesSpy).toHaveBeenCalledTimes(1); + expect(unwrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); expect(setDeviceKeySpy).toHaveBeenCalledTimes(1); expect(setDeviceKeySpy).toHaveBeenCalledWith(mockUserId, null); }); @@ -704,8 +704,8 @@ describe("deviceTrustService", () => { DeviceResponse, ), ); - encryptService.decryptToBytes.mockResolvedValue(null); - encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.decryptBytes.mockResolvedValue(null); + encryptService.encryptString.mockResolvedValue(new EncString("test_encrypted_data")); encryptService.encapsulateKeyUnsigned.mockResolvedValue( new EncString("test_encrypted_data"), ); @@ -752,9 +752,11 @@ describe("deviceTrustService", () => { DeviceResponse, ), ); - encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64)); - encryptService.encrypt.mockResolvedValue(new EncString("test_encrypted_data")); - encryptService.rsaEncrypt.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.unwrapEncapsulationKey.mockResolvedValue(new Uint8Array(64)); + encryptService.wrapEncapsulationKey.mockResolvedValue(new EncString("test_encrypted_data")); + encryptService.encapsulateKeyUnsigned.mockResolvedValue( + new EncString("test_encrypted_data"), + ); const protectedDeviceResponse = new ProtectedDeviceResponse({ id: "", @@ -862,13 +864,15 @@ describe("deviceTrustService", () => { }); // Mock the decryption of the public key with the old user key - encryptService.decryptToBytes.mockImplementationOnce((_encValue, privateKeyValue) => { - expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); - expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); - const data = new Uint8Array(250); - data.fill(FakeDecryptedPublicKeyMarker, 0, 1); - return Promise.resolve(data); - }); + encryptService.unwrapEncapsulationKey.mockImplementationOnce( + (_encValue, privateKeyValue) => { + expect(privateKeyValue.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64); + expect(new Uint8Array(privateKeyValue.toEncoded())[0]).toBe(FakeOldUserKeyMarker); + const data = new Uint8Array(250); + data.fill(FakeDecryptedPublicKeyMarker, 0, 1); + return Promise.resolve(data); + }, + ); // Mock the encryption of the new user key with the decrypted public key encryptService.encapsulateKeyUnsigned.mockImplementationOnce((data, publicKey) => { diff --git a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts index 221ce8ed6ef..fded0cea023 100644 --- a/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts +++ b/libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts @@ -37,13 +37,13 @@ export abstract class MasterPasswordServiceAbstraction { * @param userKey The user's encrypted symmetric key * @throws If either the MasterKey or UserKey are not resolved, or if the UserKey encryption type * is neither AesCbc256_B64 nor AesCbc256_HmacSha256_B64 - * @returns The user key + * @returns The user key or null if the masterkey is wrong */ abstract decryptUserKeyWithMasterKey: ( masterKey: MasterKey, userId: string, userKey?: EncString, - ) => Promise; + ) => Promise; } export abstract class InternalMasterPasswordServiceAbstraction extends MasterPasswordServiceAbstraction { diff --git a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts index 93439ac8caa..b55e770f865 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.spec.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.spec.ts @@ -2,12 +2,16 @@ import { mock, MockProxy } from "jest-mock-extended"; import { of } from "rxjs"; import * as rxjs from "rxjs"; +import { makeSymmetricCryptoKey } from "../../../../spec"; import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason"; import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service"; import { LogService } from "../../../platform/abstractions/log.service"; import { StateService } from "../../../platform/abstractions/state.service"; +import { EncString } from "../../../platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; import { StateProvider } from "../../../platform/state"; import { UserId } from "../../../types/guid"; +import { MasterKey } from "../../../types/key"; import { EncryptService } from "../../crypto/abstractions/encrypt.service"; import { MasterPasswordService } from "./master-password.service"; @@ -27,6 +31,14 @@ describe("MasterPasswordService", () => { update: jest.fn().mockResolvedValue(null), }; + const testUserKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 1); + const testMasterKey: MasterKey = makeSymmetricCryptoKey(32, 2); + const testStretchedMasterKey: SymmetricCryptoKey = makeSymmetricCryptoKey(64, 3); + const testMasterKeyEncryptedKey = + "0.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc="; + const testStretchedMasterKeyEncryptedKey = + "2.gbauOANURUHqvhLTDnva1A==|nSW+fPumiuTaDB/s12+JO88uemV6rhwRSR+YR1ZzGr5j6Ei3/h+XEli2Unpz652NlZ9NTuRpHxeOqkYYJtp7J+lPMoclgteXuAzUu9kqlRc=|DeUFkhIwgkGdZA08bDnDqMMNmZk21D+H5g8IostPKAY="; + beforeEach(() => { stateProvider = mock(); stateService = mock(); @@ -45,6 +57,9 @@ describe("MasterPasswordService", () => { encryptService, logService, ); + + encryptService.unwrapSymmetricKey.mockResolvedValue(makeSymmetricCryptoKey(64, 1)); + keyGenerationService.stretchKey.mockResolvedValue(makeSymmetricCryptoKey(64, 3)); }); describe("setForceSetPasswordReason", () => { @@ -101,4 +116,41 @@ describe("MasterPasswordService", () => { expect(mockUserState.update).toHaveBeenCalled(); }); }); + describe("decryptUserKeyWithMasterKey", () => { + it("decrypts a userkey wrapped in AES256-CBC", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); + await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testMasterKeyEncryptedKey), + ); + expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( + new EncString(testMasterKeyEncryptedKey), + testMasterKey, + ); + }); + it("decrypts a userkey wrapped in AES256-CBC-HMAC", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(testUserKey); + keyGenerationService.stretchKey.mockResolvedValue(testStretchedMasterKey); + await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testStretchedMasterKeyEncryptedKey), + ); + expect(encryptService.unwrapSymmetricKey).toHaveBeenCalledWith( + new EncString(testStretchedMasterKeyEncryptedKey), + testStretchedMasterKey, + ); + expect(keyGenerationService.stretchKey).toHaveBeenCalledWith(testMasterKey); + }); + it("returns null if failed to decrypt", async () => { + encryptService.unwrapSymmetricKey.mockResolvedValue(null); + const result = await sut.decryptUserKeyWithMasterKey( + testMasterKey, + userId, + new EncString(testStretchedMasterKeyEncryptedKey), + ); + expect(result).toBeNull(); + }); + }); }); diff --git a/libs/common/src/key-management/master-password/services/master-password.service.ts b/libs/common/src/key-management/master-password/services/master-password.service.ts index 72b18d8bfba..9e58680d453 100644 --- a/libs/common/src/key-management/master-password/services/master-password.service.ts +++ b/libs/common/src/key-management/master-password/services/master-password.service.ts @@ -166,7 +166,7 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr masterKey: MasterKey, userId: UserId, userKey?: EncString, - ): Promise { + ): Promise { userKey ??= await this.getMasterKeyEncryptedUserKey(userId); masterKey ??= await firstValueFrom(this.masterKey$(userId)); @@ -174,30 +174,32 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr throw new Error("No master key found."); } - let decUserKey: Uint8Array; + let decUserKey: SymmetricCryptoKey; if (userKey.encryptionType === EncryptionType.AesCbc256_B64) { - decUserKey = await this.encryptService.decryptToBytes( - userKey, - masterKey, - "Content: User Key; Encrypting Key: Master Key", - ); + try { + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, masterKey); + } catch { + this.logService.warning("Failed to decrypt user key with master key."); + return null; + } } else if (userKey.encryptionType === EncryptionType.AesCbc256_HmacSha256_B64) { - const newKey = await this.keyGenerationService.stretchKey(masterKey); - decUserKey = await this.encryptService.decryptToBytes( - userKey, - newKey, - "Content: User Key; Encrypting Key: Stretched Master Key", - ); + try { + const newKey = await this.keyGenerationService.stretchKey(masterKey); + decUserKey = await this.encryptService.unwrapSymmetricKey(userKey, newKey); + } catch { + this.logService.warning("Failed to decrypt user key with stretched master key."); + return null; + } } else { throw new Error("Unsupported encryption type."); } if (decUserKey == null) { - this.logService.warning("Failed to decrypt user key with master key."); + this.logService.warning("Failed to decrypt user key with master key, user key is null."); return null; } - return new SymmetricCryptoKey(decUserKey) as UserKey; + return decUserKey as UserKey; } } diff --git a/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts b/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts index 239a7490191..9e70b7ebb12 100644 --- a/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts +++ b/libs/common/src/key-management/vault-timeout/enums/vault-timeout-action.enum.ts @@ -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 VaultTimeoutAction { Lock = "lock", LogOut = "logOut", diff --git a/libs/common/src/platform/abstractions/environment.service.ts b/libs/common/src/platform/abstractions/environment.service.ts index 4a10f856893..b8931656848 100644 --- a/libs/common/src/platform/abstractions/environment.service.ts +++ b/libs/common/src/platform/abstractions/environment.service.ts @@ -17,6 +17,8 @@ export type Urls = { /** * A subset of available regions, additional regions can be loaded through configuration. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Region { US = "US", EU = "EU", diff --git a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts index e9e68ca92c3..15655393362 100644 --- a/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/platform/abstractions/fido2/fido2-authenticator.service.abstraction.ts @@ -46,11 +46,15 @@ export abstract class Fido2AuthenticatorService { silentCredentialDiscovery: (rpId: string) => Promise; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Fido2AlgorithmIdentifier { ES256 = -7, RS256 = -257, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum Fido2AuthenticatorErrorCode { Unknown = "UnknownError", NotSupported = "NotSupportedError", diff --git a/libs/common/src/platform/enums/encryption-type.enum.ts b/libs/common/src/platform/enums/encryption-type.enum.ts index fd484dc2fdf..7f4b5048a45 100644 --- a/libs/common/src/platform/enums/encryption-type.enum.ts +++ b/libs/common/src/platform/enums/encryption-type.enum.ts @@ -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 EncryptionType { AesCbc256_B64 = 0, // Type 1 was the unused and removed AesCbc128_HmacSha256_B64 diff --git a/libs/common/src/platform/enums/file-upload-type.enum.ts b/libs/common/src/platform/enums/file-upload-type.enum.ts index 4cf654fd0ff..a94797b9ef3 100644 --- a/libs/common/src/platform/enums/file-upload-type.enum.ts +++ b/libs/common/src/platform/enums/file-upload-type.enum.ts @@ -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 FileUploadType { Direct = 0, Azure = 1, diff --git a/libs/common/src/platform/enums/hash-purpose.enum.ts b/libs/common/src/platform/enums/hash-purpose.enum.ts index 887931b9edd..4b61db914a1 100644 --- a/libs/common/src/platform/enums/hash-purpose.enum.ts +++ b/libs/common/src/platform/enums/hash-purpose.enum.ts @@ -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 HashPurpose { ServerAuthorization = 1, LocalAuthorization = 2, diff --git a/libs/common/src/platform/enums/html-storage-location.enum.ts b/libs/common/src/platform/enums/html-storage-location.enum.ts index 19c84fe88c7..1d018a72869 100644 --- a/libs/common/src/platform/enums/html-storage-location.enum.ts +++ b/libs/common/src/platform/enums/html-storage-location.enum.ts @@ -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 HtmlStorageLocation { Local = "local", Memory = "memory", diff --git a/libs/common/src/platform/enums/key-suffix-options.enum.ts b/libs/common/src/platform/enums/key-suffix-options.enum.ts index 98fa215be6a..7cc412f563b 100644 --- a/libs/common/src/platform/enums/key-suffix-options.enum.ts +++ b/libs/common/src/platform/enums/key-suffix-options.enum.ts @@ -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 KeySuffixOptions { Auto = "auto", Pin = "pin", diff --git a/libs/common/src/platform/enums/log-level-type.enum.ts b/libs/common/src/platform/enums/log-level-type.enum.ts index 709871dd5e9..b5f84467d6e 100644 --- a/libs/common/src/platform/enums/log-level-type.enum.ts +++ b/libs/common/src/platform/enums/log-level-type.enum.ts @@ -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 LogLevelType { Debug, Info, diff --git a/libs/common/src/platform/enums/storage-location.enum.ts b/libs/common/src/platform/enums/storage-location.enum.ts index 46d50d1d38d..9f6e22babec 100644 --- a/libs/common/src/platform/enums/storage-location.enum.ts +++ b/libs/common/src/platform/enums/storage-location.enum.ts @@ -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 StorageLocation { Both = "both", Disk = "disk", diff --git a/libs/common/src/platform/enums/theme-type.enum.ts b/libs/common/src/platform/enums/theme-type.enum.ts index d1767c4990a..c0f9aa2d52f 100644 --- a/libs/common/src/platform/enums/theme-type.enum.ts +++ b/libs/common/src/platform/enums/theme-type.enum.ts @@ -1,6 +1,8 @@ /** * @deprecated prefer the `ThemeTypes` constants and `Theme` type over unsafe enum types **/ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ThemeType { System = "system", Light = "light", diff --git a/libs/common/src/platform/services/cryptography/initializer-key.ts b/libs/common/src/platform/services/cryptography/initializer-key.ts index 88e36d90515..59287a62c46 100644 --- a/libs/common/src/platform/services/cryptography/initializer-key.ts +++ b/libs/common/src/platform/services/cryptography/initializer-key.ts @@ -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 InitializerKey { Cipher = 0, CipherView = 1, diff --git a/libs/common/src/platform/services/key-generation.service.spec.ts b/libs/common/src/platform/services/key-generation.service.spec.ts index f75eaeb25be..0a9e997b428 100644 --- a/libs/common/src/platform/services/key-generation.service.spec.ts +++ b/libs/common/src/platform/services/key-generation.service.spec.ts @@ -5,6 +5,7 @@ import { PBKDF2KdfConfig, Argon2KdfConfig } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; import { CsprngArray } from "../../types/csprng"; import { EncryptionType } from "../enums"; +import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; import { KeyGenerationService } from "./key-generation.service"; @@ -98,4 +99,23 @@ describe("KeyGenerationService", () => { expect(key.inner().type).toEqual(EncryptionType.AesCbc256_B64); }); }); + + describe("stretchKey", () => { + it("should stretch a key", async () => { + const key = new SymmetricCryptoKey(new Uint8Array(32)); + + cryptoFunctionService.hkdf.mockResolvedValue(new Uint8Array(64)); + + const stretchedKey = await sut.stretchKey(key); + + expect(stretchedKey.inner().type).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + }); + it("should throw if key is not 32 bytes", async () => { + const key = new SymmetricCryptoKey(new Uint8Array(64)); + + await expect(sut.stretchKey(key)).rejects.toThrow( + "Key passed into stretchKey is not a 256-bit key.", + ); + }); + }); }); diff --git a/libs/common/src/platform/services/key-generation.service.ts b/libs/common/src/platform/services/key-generation.service.ts index 8f9e6856aa0..dcd1f4f95d7 100644 --- a/libs/common/src/platform/services/key-generation.service.ts +++ b/libs/common/src/platform/services/key-generation.service.ts @@ -1,11 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { MasterKey, PinKey } from "@bitwarden/common/types/key"; import { KdfConfig, PBKDF2KdfConfig, Argon2KdfConfig, KdfType } from "@bitwarden/key-management"; import { CryptoFunctionService } from "../../key-management/crypto/abstractions/crypto-function.service"; import { CsprngArray } from "../../types/csprng"; import { KeyGenerationService as KeyGenerationServiceAbstraction } from "../abstractions/key-generation.service"; +import { EncryptionType } from "../enums"; import { Utils } from "../misc/utils"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key"; @@ -79,7 +79,13 @@ export class KeyGenerationService implements KeyGenerationServiceAbstraction { return new SymmetricCryptoKey(key); } - async stretchKey(key: MasterKey | PinKey): Promise { + async stretchKey(key: SymmetricCryptoKey): Promise { + // The key to be stretched is actually usually the output of a KDF, and not actually meant for AesCbc256_B64 encryption, + // but has the same key length. Only 256-bit key materials should be stretched. + if (key.inner().type != EncryptionType.AesCbc256_B64) { + throw new Error("Key passed into stretchKey is not a 256-bit key."); + } + const newKey = new Uint8Array(64); // Master key and pin key are always 32 bytes const encKey = await this.cryptoFunctionService.hkdfExpand( diff --git a/libs/common/src/services/api.service.spec.ts b/libs/common/src/services/api.service.spec.ts new file mode 100644 index 00000000000..eca6066b9b7 --- /dev/null +++ b/libs/common/src/services/api.service.spec.ts @@ -0,0 +1,203 @@ +import { mock, MockProxy } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { LogoutReason } from "@bitwarden/auth/common"; + +import { TokenService } from "../auth/abstractions/token.service"; +import { DeviceType } from "../enums"; +import { VaultTimeoutSettingsService } from "../key-management/vault-timeout"; +import { ErrorResponse } from "../models/response/error.response"; +import { AppIdService } from "../platform/abstractions/app-id.service"; +import { Environment, EnvironmentService } from "../platform/abstractions/environment.service"; +import { LogService } from "../platform/abstractions/log.service"; +import { PlatformUtilsService } from "../platform/abstractions/platform-utils.service"; + +import { ApiService, HttpOperations } from "./api.service"; + +describe("ApiService", () => { + let tokenService: MockProxy; + let platformUtilsService: MockProxy; + let environmentService: MockProxy; + let appIdService: MockProxy; + let refreshAccessTokenErrorCallback: jest.Mock; + let logService: MockProxy; + let logoutCallback: jest.Mock, [reason: LogoutReason]>; + let vaultTimeoutSettingsService: MockProxy; + let httpOperations: MockProxy; + + let sut: ApiService; + + beforeEach(() => { + tokenService = mock(); + platformUtilsService = mock(); + platformUtilsService.getDevice.mockReturnValue(DeviceType.ChromeExtension); + + environmentService = mock(); + appIdService = mock(); + refreshAccessTokenErrorCallback = jest.fn(); + logService = mock(); + logoutCallback = jest.fn(); + vaultTimeoutSettingsService = mock(); + httpOperations = mock(); + + sut = new ApiService( + tokenService, + platformUtilsService, + environmentService, + appIdService, + refreshAccessTokenErrorCallback, + logService, + logoutCallback, + vaultTimeoutSettingsService, + httpOperations, + "custom-user-agent", + ); + }); + + describe("send", () => { + it("handles ok GET", async () => { + environmentService.environment$ = of({ + getApiUrl: () => "https://example.com", + } satisfies Partial as Environment); + + httpOperations.createRequest.mockImplementation((url, request) => { + return { + url: url, + cache: request.cache, + credentials: request.credentials, + method: request.method, + mode: request.mode, + signal: request.signal, + headers: new Headers(request.headers), + } satisfies Partial as unknown as Request; + }); + + tokenService.getAccessToken.mockResolvedValue("access_token"); + tokenService.tokenNeedsRefresh.mockResolvedValue(false); + + const nativeFetch = jest.fn, [request: Request]>(); + + nativeFetch.mockImplementation((request) => { + return Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve({ hello: "world" }), + headers: new Headers({ + "content-type": "application/json", + }), + } satisfies Partial as Response); + }); + + sut.nativeFetch = nativeFetch; + + const response = await sut.send("GET", "/something", null, true, true, null, null); + + expect(nativeFetch).toHaveBeenCalledTimes(1); + const request = nativeFetch.mock.calls[0][0]; + // This should get set for users of send + expect(request.cache).toBe("no-store"); + // TODO: Could expect on the credentials parameter + expect(request.headers.get("Device-Type")).toBe("2"); // Chrome Extension + // Custom user agent should get set + expect(request.headers.get("User-Agent")).toBe("custom-user-agent"); + // This should be set when the caller has indicated there is a response + expect(request.headers.get("Accept")).toBe("application/json"); + // If they have indicated that it's authed, then the authorization header should get set. + expect(request.headers.get("Authorization")).toBe("Bearer access_token"); + // The response body + expect(response).toEqual({ hello: "world" }); + }); + }); + + const errorData: { + name: string; + input: Partial; + error: Partial; + }[] = [ + { + name: "json response in camel case", + input: { + json: () => Promise.resolve({ message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "json response in pascal case", + input: { + json: () => Promise.resolve({ Message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "json response with charset in content type", + input: { + json: () => Promise.resolve({ message: "Something bad happened." }), + headers: new Headers({ + "content-type": "application/json; charset=utf-8", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + { + name: "text/plain response", + input: { + text: () => Promise.resolve("Something bad happened."), + headers: new Headers({ + "content-type": "text/plain", + }), + }, + error: { + message: "Something bad happened.", + }, + }, + ]; + + it.each(errorData)( + "throws error-like response when not ok response with $name", + async ({ input, error }) => { + environmentService.environment$ = of({ + getApiUrl: () => "https://example.com", + } satisfies Partial as Environment); + + httpOperations.createRequest.mockImplementation((url, request) => { + return { + url: url, + cache: request.cache, + credentials: request.credentials, + method: request.method, + mode: request.mode, + signal: request.signal, + headers: new Headers(request.headers), + } satisfies Partial as unknown as Request; + }); + + const nativeFetch = jest.fn, [request: Request]>(); + + nativeFetch.mockImplementation((request) => { + return Promise.resolve({ + ok: false, + status: 400, + ...input, + } satisfies Partial as Response); + }); + + sut.nativeFetch = nativeFetch; + + await expect( + async () => await sut.send("GET", "/something", null, true, true, null, null), + ).rejects.toMatchObject(error); + }, + ); +}); diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts index fb4d08db81c..639daa7c658 100644 --- a/libs/common/src/services/api.service.ts +++ b/libs/common/src/services/api.service.ts @@ -139,6 +139,10 @@ import { AttachmentResponse } from "../vault/models/response/attachment.response import { CipherResponse } from "../vault/models/response/cipher.response"; import { OptionalCipherResponse } from "../vault/models/response/optional-cipher.response"; +export type HttpOperations = { + createRequest: (url: string, request: RequestInit) => Request; +}; + /** * @deprecated The `ApiService` class is deprecated and calls should be extracted into individual * api services. The `send` method is still allowed to be used within api services. For background @@ -166,6 +170,7 @@ export class ApiService implements ApiServiceAbstraction { private logService: LogService, private logoutCallback: (logoutReason: LogoutReason) => Promise, private vaultTimeoutSettingsService: VaultTimeoutSettingsService, + private readonly httpOperations: HttpOperations, private customUserAgent: string = null, ) { this.device = platformUtilsService.getDevice(); @@ -217,7 +222,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const response = await this.fetch( - new Request(env.getIdentityUrl() + "/connect/token", { + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { body: this.qsStringify(identityToken), credentials: await this.getCredentials(), cache: "no-store", @@ -1409,7 +1414,7 @@ export class ApiService implements ApiServiceAbstraction { } const env = await firstValueFrom(this.environmentService.environment$); const response = await this.fetch( - new Request(env.getEventsUrl() + "/collect", { + this.httpOperations.createRequest(env.getEventsUrl() + "/collect", { cache: "no-store", credentials: await this.getCredentials(), method: "POST", @@ -1456,7 +1461,7 @@ export class ApiService implements ApiServiceAbstraction { const authHeader = await this.getActiveBearerToken(); const response = await this.fetch( - new Request(keyConnectorUrl + "/user-keys", { + this.httpOperations.createRequest(keyConnectorUrl + "/user-keys", { cache: "no-store", method: "GET", headers: new Headers({ @@ -1481,7 +1486,7 @@ export class ApiService implements ApiServiceAbstraction { const authHeader = await this.getActiveBearerToken(); const response = await this.fetch( - new Request(keyConnectorUrl + "/user-keys", { + this.httpOperations.createRequest(keyConnectorUrl + "/user-keys", { cache: "no-store", method: "POST", headers: new Headers({ @@ -1501,7 +1506,7 @@ export class ApiService implements ApiServiceAbstraction { async getKeyConnectorAlive(keyConnectorUrl: string) { const response = await this.fetch( - new Request(keyConnectorUrl + "/alive", { + this.httpOperations.createRequest(keyConnectorUrl + "/alive", { cache: "no-store", method: "GET", headers: new Headers({ @@ -1570,7 +1575,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const path = `/sso/prevalidate?domainHint=${encodeURIComponent(identifier)}`; const response = await this.fetch( - new Request(env.getIdentityUrl() + path, { + this.httpOperations.createRequest(env.getIdentityUrl() + path, { cache: "no-store", credentials: await this.getCredentials(), headers: headers, @@ -1616,18 +1621,6 @@ export class ApiService implements ApiServiceAbstraction { return new OrganizationSponsorshipSyncStatusResponse(response); } - async deleteRevokeSponsorship(sponsoringOrganizationId: string): Promise { - return await this.send( - "DELETE", - "/organization/sponsorship/" + - (this.platformUtilsService.isSelfHost() ? "self-hosted/" : "") + - sponsoringOrganizationId, - null, - true, - false, - ); - } - async deleteRemoveSponsorship(sponsoringOrgId: string): Promise { return await this.send( "DELETE", @@ -1711,7 +1704,7 @@ export class ApiService implements ApiServiceAbstraction { const env = await firstValueFrom(this.environmentService.environment$); const decodedToken = await this.tokenService.decodeAccessToken(); const response = await this.fetch( - new Request(env.getIdentityUrl() + "/connect/token", { + this.httpOperations.createRequest(env.getIdentityUrl() + "/connect/token", { body: this.qsStringify({ grant_type: "refresh_token", client_id: decodedToken.client_id, @@ -1820,7 +1813,7 @@ export class ApiService implements ApiServiceAbstraction { }; requestInit.headers = requestHeaders; requestInit.body = requestBody; - const response = await this.fetch(new Request(requestUrl, requestInit)); + const response = await this.fetch(this.httpOperations.createRequest(requestUrl, requestInit)); const responseType = response.headers.get("content-type"); const responseIsJson = responseType != null && responseType.indexOf("application/json") !== -1; @@ -1889,7 +1882,7 @@ export class ApiService implements ApiServiceAbstraction { let responseJson: any = null; if (this.isJsonResponse(response)) { responseJson = await response.json(); - } else if (this.isTextResponse(response)) { + } else if (this.isTextPlainResponse(response)) { responseJson = { Message: await response.text() }; } @@ -1945,8 +1938,8 @@ export class ApiService implements ApiServiceAbstraction { return typeHeader != null && typeHeader.indexOf("application/json") > -1; } - private isTextResponse(response: Response): boolean { + private isTextPlainResponse(response: Response): boolean { const typeHeader = response.headers.get("content-type"); - return typeHeader != null && typeHeader.indexOf("text") > -1; + return typeHeader != null && typeHeader.indexOf("text/plain") > -1; } } diff --git a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts index 96f0a9acd5d..5ae943438d2 100644 --- a/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/28-move-provider-state-to-state-provider.ts @@ -5,6 +5,8 @@ import { Jsonify } from "type-fest"; import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderUserStatusType { Invited = 0, Accepted = 1, @@ -12,6 +14,8 @@ enum ProviderUserStatusType { Revoked = -1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderUserType { ProviderAdmin = 0, ServiceUser = 1, diff --git a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts index 0b4c95cb55c..f188bc0a33e 100644 --- a/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/30-move-policy-state-to-state-provider.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum PolicyType { TwoFactorAuthentication = 0, // Requires users to have 2fa enabled MasterPassword = 1, // Sets minimum requirements for master password complexity diff --git a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts index 862fe33bd4e..b23b712aefe 100644 --- a/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/40-move-organization-state-to-state-provider.ts @@ -7,6 +7,8 @@ import { Migrator } from "../migrator"; // Local declarations of `OrganizationData` and the types of it's properties. // Duplicated to remain frozen in time when migration occurs. +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum OrganizationUserStatusType { Invited = 0, Accepted = 1, @@ -14,6 +16,8 @@ enum OrganizationUserStatusType { Revoked = -1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum OrganizationUserType { Owner = 0, Admin = 1, @@ -40,11 +44,15 @@ type PermissionsApi = { manageScim: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProviderType { Msp = 0, Reseller = 1, } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum ProductType { Free = 0, Families = 1, diff --git a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts index 7d3a2bebbf9..489fb2258ee 100644 --- a/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts +++ b/libs/common/src/state-migrations/migrations/54-move-encrypted-sends.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendType { Text = 0, File = 1, diff --git a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts index 81ee193867b..a01ef42a8ff 100644 --- a/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/59-move-kdf-config-to-state-provider.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum KdfType { PBKDF2_SHA256 = 0, Argon2id = 1, diff --git a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts index 5874a80dc60..3557bfae083 100644 --- a/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/62-migrate-vault-timeout-settings-svc-to-state-provider.ts @@ -57,6 +57,8 @@ const vaultTimeoutTypeRollbackRecord: Record = { onIdle: -4, }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ClientType { Web = "web", Browser = "browser", diff --git a/libs/common/src/tools/send/enums/send-type.ts b/libs/common/src/tools/send/enums/send-type.ts index 487930c90c7..5b03c71d22a 100644 --- a/libs/common/src/tools/send/enums/send-type.ts +++ b/libs/common/src/tools/send/enums/send-type.ts @@ -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 SendType { Text = 0, File = 1, diff --git a/libs/common/src/vault/enums/cipher-reprompt-type.ts b/libs/common/src/vault/enums/cipher-reprompt-type.ts index 1d0a523ced0..190a9bad042 100644 --- a/libs/common/src/vault/enums/cipher-reprompt-type.ts +++ b/libs/common/src/vault/enums/cipher-reprompt-type.ts @@ -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 CipherRepromptType { None = 0, Password = 1, diff --git a/libs/common/src/vault/enums/cipher-type.ts b/libs/common/src/vault/enums/cipher-type.ts index 0b7bbf1ee17..30d80cdef7e 100644 --- a/libs/common/src/vault/enums/cipher-type.ts +++ b/libs/common/src/vault/enums/cipher-type.ts @@ -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 CipherType { Login = 1, SecureNote = 2, diff --git a/libs/common/src/vault/enums/field-type.enum.ts b/libs/common/src/vault/enums/field-type.enum.ts index d6deb30e691..df5016890b2 100644 --- a/libs/common/src/vault/enums/field-type.enum.ts +++ b/libs/common/src/vault/enums/field-type.enum.ts @@ -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 FieldType { Text = 0, Hidden = 1, diff --git a/libs/common/src/vault/enums/linked-id-type.enum.ts b/libs/common/src/vault/enums/linked-id-type.enum.ts index c38ebc1c6e8..b329aecb3f4 100644 --- a/libs/common/src/vault/enums/linked-id-type.enum.ts +++ b/libs/common/src/vault/enums/linked-id-type.enum.ts @@ -1,12 +1,16 @@ export type LinkedIdType = LoginLinkedId | CardLinkedId | IdentityLinkedId; // LoginView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginLinkedId { Username = 100, Password = 101, } // CardView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CardLinkedId { CardholderName = 300, ExpMonth = 301, @@ -17,6 +21,8 @@ export enum CardLinkedId { } // IdentityView +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum IdentityLinkedId { Title = 400, MiddleName = 401, diff --git a/libs/common/src/vault/enums/secure-note-type.enum.ts b/libs/common/src/vault/enums/secure-note-type.enum.ts index 8015236d148..4fbd05e6bd4 100644 --- a/libs/common/src/vault/enums/secure-note-type.enum.ts +++ b/libs/common/src/vault/enums/secure-note-type.enum.ts @@ -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 SecureNoteType { Generic = 0, } diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index a32568c8112..169568d44e9 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -519,8 +519,15 @@ export class CipherService implements CipherServiceAbstraction { includeOtherTypes?: CipherType[], defaultMatch: UriMatchStrategySetting = null, ): Promise { - const ciphers = await this.getAllDecrypted(userId); - return await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch); + return await firstValueFrom( + this.cipherViews$(userId).pipe( + filter((c) => c != null), + switchMap( + async (ciphers) => + await this.filterCiphersForUrl(ciphers, url, includeOtherTypes, defaultMatch), + ), + ), + ); } async filterCiphersForUrl( diff --git a/libs/common/src/vault/tasks/enums/security-task-status.enum.ts b/libs/common/src/vault/tasks/enums/security-task-status.enum.ts index 1c6e7decc20..c8c26266e66 100644 --- a/libs/common/src/vault/tasks/enums/security-task-status.enum.ts +++ b/libs/common/src/vault/tasks/enums/security-task-status.enum.ts @@ -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 SecurityTaskStatus { /** * Default status for newly created tasks that have not been completed. diff --git a/libs/common/src/vault/tasks/enums/security-task-type.enum.ts b/libs/common/src/vault/tasks/enums/security-task-type.enum.ts index 264cd88696b..79a2d23c8b3 100644 --- a/libs/common/src/vault/tasks/enums/security-task-type.enum.ts +++ b/libs/common/src/vault/tasks/enums/security-task-type.enum.ts @@ -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 SecurityTaskType { /** * Task to update a cipher's password that was found to be at-risk by an administrator diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index 0e3dbd6f1b9..554f55636fc 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -123,7 +123,7 @@ export class AvatarComponent implements OnChanges { textTag.setAttribute("fill", Utils.pickTextColorBasedOnBgColor(color, 135, true)); textTag.setAttribute( "font-family", - '"DM Sans","Helvetica Neue",Helvetica,Arial,' + + 'Roboto,"Helvetica Neue",Helvetica,Arial,' + 'sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"', ); // Warning do not use innerHTML here, characters are user provided diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 86ccb41ba3c..2dd78e8525d 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -2,6 +2,8 @@ import { Component, computed, HostBinding, input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums enum CharacterType { Letter, Emoji, diff --git a/libs/components/src/variables.scss b/libs/components/src/variables.scss index bc9cded4981..e3651f9c37d 100644 --- a/libs/components/src/variables.scss +++ b/libs/components/src/variables.scss @@ -21,7 +21,7 @@ $body-bg: $white; $body-color: #333333; $font-family-sans-serif: - "DM Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; $h1-font-size: 1.7rem; diff --git a/libs/eslint/platform/index.mjs b/libs/eslint/platform/index.mjs index 049d95e2074..c7ea3f1dd89 100644 --- a/libs/eslint/platform/index.mjs +++ b/libs/eslint/platform/index.mjs @@ -1,3 +1,4 @@ import requiredUsing from "./required-using.mjs"; +import noEnums from "./no-enums.mjs"; -export default { rules: { "required-using": requiredUsing } }; +export default { rules: { "required-using": requiredUsing, "no-enums": noEnums } }; diff --git a/libs/eslint/platform/no-enums.mjs b/libs/eslint/platform/no-enums.mjs new file mode 100644 index 00000000000..4f3039f7e34 --- /dev/null +++ b/libs/eslint/platform/no-enums.mjs @@ -0,0 +1,23 @@ +export const errorMessage = "Enums are discouraged, please use a const object instead"; + +export default { + meta: { + type: "suggestion", + docs: { + description: "Enforce using consts instead of enums", + category: "Best Practices", + recommended: false, + }, + schema: [], + }, + create(context) { + return { + TSEnumDeclaration(node) { + context.report({ + node, + message: errorMessage, + }); + }, + }; + }, +}; diff --git a/libs/eslint/platform/no-enums.spec.mjs b/libs/eslint/platform/no-enums.spec.mjs new file mode 100644 index 00000000000..5ded12d6ed3 --- /dev/null +++ b/libs/eslint/platform/no-enums.spec.mjs @@ -0,0 +1,71 @@ +import { RuleTester } from "@typescript-eslint/rule-tester"; + +import rule, { errorMessage } from "./no-enums.mjs"; + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + project: [__dirname + "/../tsconfig.spec.json"], + projectService: { + allowDefaultProject: ["*.ts*"], + }, + tsconfigRootDir: __dirname + "/..", + }, + }, +}); + +ruleTester.run("no-enums", rule.default, { + valid: [ + { + name: "Using const instead of enum", + code: ` + const Status = { + Active: "active", + Inactive: "inactive", + } as const; + `, + }, + { + name: "Using const with type", + code: ` + const Status = { + Active: "active", + Inactive: "inactive", + } as const; + type Status = typeof Status[keyof typeof Status]; + `, + }, + ], + invalid: [ + { + name: "Using enum declaration", + code: ` + enum Status { + Active = "active", + Inactive = "inactive", + } + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + { + name: "Using enum with numeric values", + code: ` + enum Direction { + Up = 1, + Down = 2, + Left = 3, + Right = 4, + } + `, + errors: [ + { + message: errorMessage, + }, + ], + }, + ], +}); diff --git a/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts b/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts index 71797a0f8cd..1235426d683 100644 --- a/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts +++ b/libs/importer/src/importers/fsecure/fsecure-fsk-types.ts @@ -6,6 +6,8 @@ export interface Data { [key: string]: FskEntry; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum FskEntryTypesEnum { Login = 1, CreditCard = 2, diff --git a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts index 32e74c36ee1..01c4572fcf9 100644 --- a/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts +++ b/libs/importer/src/importers/lastpass/access/enums/idp-provider.ts @@ -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 IdpProvider { Azure = 0, OktaAuthServer = 1, diff --git a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts index 611dd0b6dab..a3be36c790e 100644 --- a/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts +++ b/libs/importer/src/importers/lastpass/access/enums/lastpass-login-type.ts @@ -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 LastpassLoginType { MasterPassword = 0, // Not sure what Types 1 and 2 are? diff --git a/libs/importer/src/importers/lastpass/access/enums/otp-method.ts b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts index 6b940486ff0..f1237160179 100644 --- a/libs/importer/src/importers/lastpass/access/enums/otp-method.ts +++ b/libs/importer/src/importers/lastpass/access/enums/otp-method.ts @@ -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 OtpMethod { GoogleAuth, MicrosoftAuth, diff --git a/libs/importer/src/importers/lastpass/access/enums/platform.ts b/libs/importer/src/importers/lastpass/access/enums/platform.ts index 283e0c36ce2..6870fc28c24 100644 --- a/libs/importer/src/importers/lastpass/access/enums/platform.ts +++ b/libs/importer/src/importers/lastpass/access/enums/platform.ts @@ -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 Platform { Desktop, Mobile, diff --git a/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts b/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts index 63a0427b2c7..d7f4dec8f95 100644 --- a/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts +++ b/libs/importer/src/importers/onepassword/types/onepassword-1pux-importer-types.ts @@ -25,6 +25,8 @@ export interface VaultAttributes { type: string; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CategoryEnum { Login = "001", CreditCard = "002", @@ -67,6 +69,8 @@ export interface Details { password?: string | null; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum LoginFieldTypeEnum { TextOrHtml = "T", EmailAddress = "E", diff --git a/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts b/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts index 20fa314a314..af2eb15a740 100644 --- a/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts +++ b/libs/importer/src/importers/protonpass/types/protonpass-json-type.ts @@ -27,6 +27,8 @@ export type ProtonPassItem = { pinned: boolean; }; +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum ProtonPassItemState { ACTIVE = 1, TRASHED = 2, diff --git a/libs/key-management-ui/src/lock/components/lock.component.ts b/libs/key-management-ui/src/lock/components/lock.component.ts index 80d64e17b84..3cb0dbaca52 100644 --- a/libs/key-management-ui/src/lock/components/lock.component.ts +++ b/libs/key-management-ui/src/lock/components/lock.component.ts @@ -556,6 +556,15 @@ export class LockComponent implements OnInit, OnDestroy { masterPasswordVerificationResponse!.masterKey, this.activeAccount.id, ); + if (userKey == null) { + this.toastService.showToast({ + variant: "error", + title: this.i18nService.t("errorOccurred"), + message: this.i18nService.t("invalidMasterPassword"), + }); + return; + } + await this.setUserKeyAndContinue(userKey, true); } diff --git a/libs/key-management/src/biometrics/biometrics-commands.ts b/libs/key-management/src/biometrics/biometrics-commands.ts index 81f0ea747e4..1ef31a31fb4 100644 --- a/libs/key-management/src/biometrics/biometrics-commands.ts +++ b/libs/key-management/src/biometrics/biometrics-commands.ts @@ -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 BiometricsCommands { /** Perform biometric authentication for the system's user. Does not require setup, and does not return cryptographic material, only yes or no. */ AuthenticateWithBiometrics = "authenticateWithBiometrics", diff --git a/libs/key-management/src/biometrics/biometrics-status.ts b/libs/key-management/src/biometrics/biometrics-status.ts index fb46ed1c4be..e436574ff7d 100644 --- a/libs/key-management/src/biometrics/biometrics-status.ts +++ b/libs/key-management/src/biometrics/biometrics-status.ts @@ -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 BiometricsStatus { /** For the biometrics interface, this means that biometric unlock is available and can be used. Querying for the user specifically, this means that biometric can be used for to unlock this user */ Available, diff --git a/libs/key-management/src/enums/kdf-type.enum.ts b/libs/key-management/src/enums/kdf-type.enum.ts index 29fcd9f1f8e..4f468e63a29 100644 --- a/libs/key-management/src/enums/kdf-type.enum.ts +++ b/libs/key-management/src/enums/kdf-type.enum.ts @@ -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 KdfType { PBKDF2_SHA256 = 0, Argon2id = 1, diff --git a/libs/key-management/src/key.service.spec.ts b/libs/key-management/src/key.service.spec.ts index 57291462924..25d8aff99fb 100644 --- a/libs/key-management/src/key.service.spec.ts +++ b/libs/key-management/src/key.service.spec.ts @@ -330,7 +330,7 @@ describe("keyService", () => { everHadUserKeyState.nextState(null); // Mock private key decryption - encryptService.decryptToBytes.mockResolvedValue(mockRandomBytes); + encryptService.unwrapDecapsulationKey.mockResolvedValue(mockRandomBytes); }); it("throws if userKey is null", async () => { @@ -352,7 +352,7 @@ describe("keyService", () => { }); it("throws if encPrivateKey cannot be decrypted with the userKey", async () => { - encryptService.decryptToBytes.mockResolvedValue(null); + encryptService.unwrapDecapsulationKey.mockResolvedValue(null); await expect( keyService.setUserKeys(mockUserKey, mockEncPrivateKey, mockUserId), @@ -452,17 +452,16 @@ describe("keyService", () => { // Decryption of the user private key const fakeDecryptedUserPrivateKey = makeStaticByteArray(10, 1); - encryptService.decryptToBytes.mockResolvedValue(fakeDecryptedUserPrivateKey); + encryptService.unwrapDecapsulationKey.mockResolvedValue(fakeDecryptedUserPrivateKey); const fakeUserPublicKey = makeStaticByteArray(10, 2); cryptoFunctionService.rsaExtractPublicKey.mockResolvedValue(fakeUserPublicKey); const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); - expect(encryptService.decryptToBytes).toHaveBeenCalledWith( + expect(encryptService.unwrapDecapsulationKey).toHaveBeenCalledWith( fakeEncryptedUserPrivateKey, userKey, - "Content: Encrypted Private Key", ); expect(userPrivateKey).toBe(fakeDecryptedUserPrivateKey); @@ -473,7 +472,7 @@ describe("keyService", () => { const userPrivateKey = await firstValueFrom(keyService.userPrivateKey$(mockUserId)); - expect(encryptService.decryptToBytes).not.toHaveBeenCalled(); + expect(encryptService.unwrapDecapsulationKey).not.toHaveBeenCalled(); expect(userPrivateKey).toBeFalsy(); }); @@ -552,10 +551,12 @@ describe("keyService", () => { providerKeysState.nextState(keys.providerKeys!); } - encryptService.decryptToBytes.mockImplementation((encryptedPrivateKey, userKey) => { - // TOOD: Branch between provider and private key? + encryptService.unwrapDecapsulationKey.mockImplementation((encryptedPrivateKey, userKey) => { return Promise.resolve(fakePrivateKeyDecryption(encryptedPrivateKey, userKey)); }); + encryptService.unwrapSymmetricKey.mockImplementation((encryptedPrivateKey, userKey) => { + return Promise.resolve(new SymmetricCryptoKey(new Uint8Array(64))); + }); encryptService.decapsulateKeyUnsigned.mockImplementation((data, privateKey) => { return Promise.resolve(new SymmetricCryptoKey(fakeOrgKeyDecryption(data, privateKey))); @@ -617,6 +618,7 @@ describe("keyService", () => { }); it("returns decryption keys when some of the org keys are providers", async () => { + encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(64)); const org2Id = "org2Id" as OrganizationId; updateKeys({ userKey: makeSymmetricCryptoKey(64), @@ -647,7 +649,7 @@ describe("keyService", () => { const org2Key = decryptionKeys!.orgKeys![org2Id]; expect(org2Key).not.toBeNull(); - expect(org2Key.keyB64).toContain("provider1Key"); + expect(org2Key.toEncoded()).toHaveLength(64); }); it("returns a stream that pays attention to updates of all data", async () => { diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 01e7b713e1b..849b9db6a50 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -738,7 +738,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { const storePin = await this.shouldStoreKey(KeySuffixOptions.Pin, userId); if (storePin) { // Decrypt userKeyEncryptedPin with user key - const pin = await this.encryptService.decryptToUtf8( + const pin = await this.encryptService.decryptString( (await this.pinService.getUserKeyEncryptedPin(userId))!, key, ); @@ -945,10 +945,9 @@ export class DefaultKeyService implements KeyServiceAbstraction { return null; } - return (await this.encryptService.decryptToBytes( + return (await this.encryptService.unwrapDecapsulationKey( new EncString(encryptedPrivateKey), key, - "Content: Encrypted Private Key", )) as UserPrivateKey; } diff --git a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts index 8e2ce8ab6b9..35cef914588 100644 --- a/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts +++ b/libs/key-management/src/user-asymmetric-key-regeneration/services/default-user-asymmetric-key-regeneration.service.spec.ts @@ -58,6 +58,9 @@ function setupUserKeyValidation( cipher.notes = mockEnc("EncryptedString"); cipher.key = mockEnc("EncKey"); cipherService.getAll.mockResolvedValue([cipher]); + encryptService.unwrapSymmetricKey.mockResolvedValue( + new SymmetricCryptoKey(makeStaticByteArray(64)), + ); encryptService.decryptToBytes.mockResolvedValue(makeStaticByteArray(64)); encryptService.decryptString.mockResolvedValue("mockDecryptedString"); (window as any).bitwardenContainerService = new ContainerService(keyService, encryptService); diff --git a/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts b/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts index 4767869f7d0..2f416e4a49a 100644 --- a/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts +++ b/libs/tools/export/vault-export/vault-export-ui/src/enums/encrypted-export-type.enum.ts @@ -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 EncryptedExportType { AccountEncrypted = 0, FileEncrypted = 1, diff --git a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts index 5da2c6f8bed..0bb753d3f37 100644 --- a/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts +++ b/libs/tools/send/send-ui/src/add-edit/send-add-edit-dialog.component.ts @@ -35,6 +35,8 @@ export interface SendItemDialogParams { disableForm?: boolean; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum SendItemDialogResult { /** * A Send was saved (created or updated). diff --git a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts index c77c44467d8..9ca9aefb4ac 100644 --- a/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts +++ b/libs/tools/send/send-ui/src/send-form/components/send-details/send-details.component.ts @@ -30,6 +30,8 @@ import { SendFileDetailsComponent } from "./send-file-details.component"; import { SendTextDetailsComponent } from "./send-text-details.component"; // Value = hours +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum DatePreset { OneHour = 1, OneDay = 24, diff --git a/libs/vault/src/cipher-form/cipher-form.stories.ts b/libs/vault/src/cipher-form/cipher-form.stories.ts index 9943f07292d..7090502ef14 100644 --- a/libs/vault/src/cipher-form/cipher-form.stories.ts +++ b/libs/vault/src/cipher-form/cipher-form.stories.ts @@ -13,7 +13,7 @@ import { import { BehaviorSubject } from "rxjs"; import { CollectionView } from "@bitwarden/admin-console/common"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts index 4c61ad5d2d5..da687f33ef9 100644 --- a/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts +++ b/libs/vault/src/cipher-form/components/cipher-form.component.spec.ts @@ -3,7 +3,7 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ReactiveFormsModule } from "@angular/forms"; import { mock } from "jest-mock-extended"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { Cipher } from "@bitwarden/common/vault/models/domain/cipher"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts index 6236e2d3dac..0dec46e1b20 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.spec.ts @@ -1,7 +1,7 @@ import { signal } from "@angular/core"; import { TestBed } from "@angular/core/testing"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts index 268b2db306b..b4a8138e025 100644 --- a/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts +++ b/libs/vault/src/cipher-form/services/default-cipher-form-cache.service.ts @@ -1,6 +1,6 @@ import { inject, Injectable } from "@angular/core"; -import { ViewCacheService } from "@bitwarden/angular/platform/abstractions/view-cache.service"; +import { ViewCacheService } from "@bitwarden/angular/platform/view-cache"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; diff --git a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts index 68660c4bbd9..b34d0d3a312 100644 --- a/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts +++ b/libs/vault/src/cipher-view/attachments/attachments-v2.component.ts @@ -24,6 +24,8 @@ export interface AttachmentsDialogParams { /** * Enum representing the possible results of the attachment dialog. */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum AttachmentDialogResult { Uploaded = "uploaded", Removed = "removed", diff --git a/libs/vault/src/cipher-view/card-details/card-details-view.component.html b/libs/vault/src/cipher-view/card-details/card-details-view.component.html index fff771b6465..ff61addd7db 100644 --- a/libs/vault/src/cipher-view/card-details/card-details-view.component.html +++ b/libs/vault/src/cipher-view/card-details/card-details-view.component.html @@ -33,6 +33,7 @@ bitIconButton bitPasswordInputToggle data-testid="toggle-number" + [toggled]="revealCardNumber" (toggledChange)="logCardEvent($event, EventType.Cipher_ClientToggledCardNumberVisible)" > diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts index 27d81f32ee6..d99ac438f27 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.ts @@ -1,7 +1,15 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { CommonModule, DatePipe } from "@angular/common"; -import { Component, EventEmitter, inject, Input, Output } from "@angular/core"; +import { + Component, + EventEmitter, + inject, + Input, + OnChanges, + Output, + SimpleChanges, +} from "@angular/core"; import { Observable, switchMap } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; @@ -51,7 +59,7 @@ type TotpCodeValues = { LinkModule, ], }) -export class LoginCredentialsViewComponent { +export class LoginCredentialsViewComponent implements OnChanges { @Input() cipher: CipherView; @Input() activeUserId: UserId; @Input() hadPendingChangePasswordTask: boolean; @@ -85,6 +93,13 @@ export class LoginCredentialsViewComponent { return `${dateCreated} ${creationDate}`; } + ngOnChanges(changes: SimpleChanges): void { + if (changes["cipher"]) { + this.passwordRevealed = false; + this.showPasswordCount = false; + } + } + async getPremium(organizationId?: string) { await this.premiumUpgradeService.promptForPremium(organizationId); } diff --git a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts index c8559799eec..dd3cbc4c5c9 100644 --- a/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts +++ b/libs/vault/src/components/add-edit-folder-dialog/add-edit-folder-dialog.component.ts @@ -34,6 +34,8 @@ import { } 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 AddEditFolderDialogResult { Created = "created", Deleted = "deleted", diff --git a/libs/vault/src/components/assign-collections.component.ts b/libs/vault/src/components/assign-collections.component.ts index 45d25a037e9..6a0c45cfbe3 100644 --- a/libs/vault/src/components/assign-collections.component.ts +++ b/libs/vault/src/components/assign-collections.component.ts @@ -80,6 +80,8 @@ export interface CollectionAssignmentParams { isSingleCipherAdmin?: boolean; } +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum CollectionAssignmentResult { Saved = "saved", Canceled = "canceled", diff --git a/libs/vault/src/services/vault-nudges.service.spec.ts b/libs/vault/src/services/vault-nudges.service.spec.ts index 89465fc5382..40c58644309 100644 --- a/libs/vault/src/services/vault-nudges.service.spec.ts +++ b/libs/vault/src/services/vault-nudges.service.spec.ts @@ -12,7 +12,11 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi import { FakeStateProvider, mockAccountServiceWith } from "../../../common/spec"; -import { HasItemsNudgeService, EmptyVaultNudgeService } from "./custom-nudges-services"; +import { + HasItemsNudgeService, + EmptyVaultNudgeService, + DownloadBitwardenNudgeService, +} from "./custom-nudges-services"; import { DefaultSingleNudgeService } from "./default-single-nudge.service"; import { VaultNudgesService, VaultNudgeType } from "./vault-nudges.service"; @@ -25,6 +29,8 @@ describe("Vault Nudges Service", () => { getFeatureFlag: jest.fn().mockReturnValue(true), }; + const vaultNudgeServices = [EmptyVaultNudgeService, DownloadBitwardenNudgeService]; + beforeEach(async () => { fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId)); @@ -46,6 +52,10 @@ describe("Vault Nudges Service", () => { provide: HasItemsNudgeService, useValue: mock(), }, + { + provide: DownloadBitwardenNudgeService, + useValue: mock(), + }, { provide: EmptyVaultNudgeService, useValue: mock(), @@ -130,11 +140,14 @@ describe("Vault Nudges Service", () => { describe("HasActiveBadges", () => { it("should return true if a nudgeType with hasBadgeDismissed === false", async () => { - TestBed.overrideProvider(EmptyVaultNudgeService, { - useValue: { - nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), - }, + vaultNudgeServices.forEach((service) => { + TestBed.overrideProvider(service, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: false, hasSpotlightDismissed: false }), + }, + }); }); + const service = testBed.inject(VaultNudgesService); const result = await firstValueFrom(service.hasActiveBadges$("user-id" as UserId)); @@ -142,10 +155,12 @@ describe("Vault Nudges Service", () => { expect(result).toBe(true); }); it("should return false if all nudgeTypes have hasBadgeDismissed === true", async () => { - TestBed.overrideProvider(EmptyVaultNudgeService, { - useValue: { - nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: true }), - }, + vaultNudgeServices.forEach((service) => { + TestBed.overrideProvider(service, { + useValue: { + nudgeStatus$: () => of({ hasBadgeDismissed: true, hasSpotlightDismissed: false }), + }, + }); }); const service = testBed.inject(VaultNudgesService); diff --git a/libs/vault/src/services/vault-nudges.service.ts b/libs/vault/src/services/vault-nudges.service.ts index e04cb609d7f..d27cd09e954 100644 --- a/libs/vault/src/services/vault-nudges.service.ts +++ b/libs/vault/src/services/vault-nudges.service.ts @@ -23,6 +23,8 @@ export type NudgeStatus = { /** * Enum to list the various nudge types, to be used by components/badges to show/hide the nudge */ +// FIXME: update to use a const object instead of a typescript enum +// eslint-disable-next-line @bitwarden/platform/no-enums export enum VaultNudgeType { /** Nudge to show when user has no items in their vault * Add future nudges here @@ -114,7 +116,7 @@ export class VaultNudgesService { */ hasActiveBadges$(userId: UserId): Observable { // Add more nudge types here if they have the settings badge feature - const nudgeTypes = [VaultNudgeType.EmptyVaultNudge]; + const nudgeTypes = [VaultNudgeType.EmptyVaultNudge, VaultNudgeType.DownloadBitwarden]; const nudgeTypesWithBadge$ = nudgeTypes.map((nudge) => { return this.getNudgeService(nudge) diff --git a/package-lock.json b/package-lock.json index 0db144090b9..d1378d63ec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@bitwarden/sdk-internal": "0.2.0-main.159", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", @@ -54,7 +54,7 @@ "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", @@ -74,9 +74,9 @@ "zxcvbn": "4.4.2" }, "devDependencies": { - "@angular-devkit/build-angular": "18.2.12", + "@angular-devkit/build-angular": "18.2.19", "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.12", + "@angular/cli": "18.2.19", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", @@ -84,13 +84,14 @@ "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.12", + "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", "@storybook/addon-essentials": "8.6.12", "@storybook/addon-interactions": "8.6.12", "@storybook/addon-links": "8.6.12", + "@storybook/test-runner": "0.22.0", "@storybook/addon-themes": "8.6.12", "@storybook/angular": "8.6.12", "@storybook/manager-api": "8.6.12", @@ -110,7 +111,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.14.1", + "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.15", @@ -123,6 +124,7 @@ "@yao-pkg/pkg": "5.16.1", "angular-eslint": "18.4.3", "autoprefixer": "10.4.21", + "axe-playwright": "2.1.0", "babel-loader": "9.2.1", "base64-loader": "1.0.0", "browserslist": "4.23.2", @@ -198,13 +200,14 @@ "version": "2025.4.0", "license": "SEE LICENSE IN LICENSE.txt", "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", @@ -215,7 +218,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", @@ -387,6 +390,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -414,29 +418,28 @@ } }, "node_modules/@angular-devkit/build-angular": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.12.tgz", - "integrity": "sha512-quVUi7eqTq9OHumQFNl9Y8t2opm8miu4rlYnuF6rbujmmBDvdUvR6trFChueRczl2p5HWqTOr6NPoDGQm8AyNw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.2.19.tgz", + "integrity": "sha512-xwY7v+nGE7TXOc4pgY6u57bLzIPSHuecosYr3TiWHAl9iEcKHzkCCFKsLZyunohHmq/i1uA6g3cC6iwp2xNYyg==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.12", - "@angular-devkit/build-webpack": "0.1802.12", - "@angular-devkit/core": "18.2.12", - "@angular/build": "18.2.12", - "@babel/core": "7.25.2", - "@babel/generator": "7.25.0", - "@babel/helper-annotate-as-pure": "7.24.7", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/build-webpack": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular/build": "18.2.19", + "@babel/core": "7.26.10", + "@babel/generator": "7.26.10", + "@babel/helper-annotate-as-pure": "7.25.9", "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.25.0", - "@babel/plugin-transform-async-to-generator": "7.24.7", - "@babel/plugin-transform-runtime": "7.24.7", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", + "@babel/plugin-transform-async-generator-functions": "7.26.8", + "@babel/plugin-transform-async-to-generator": "7.25.9", + "@babel/plugin-transform-runtime": "7.26.10", + "@babel/preset-env": "7.26.9", + "@babel/runtime": "7.26.10", "@discoveryjs/json-ext": "0.6.1", - "@ngtools/webpack": "18.2.12", - "@vitejs/plugin-basic-ssl": "1.1.0", + "@ngtools/webpack": "18.2.19", "ansi-colors": "4.1.3", "autoprefixer": "10.4.20", "babel-loader": "9.1.3", @@ -446,7 +449,7 @@ "css-loader": "7.1.2", "esbuild-wasm": "0.23.0", "fast-glob": "3.3.2", - "http-proxy-middleware": "3.0.3", + "http-proxy-middleware": "3.0.5", "https-proxy-agent": "7.0.5", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", @@ -475,7 +478,6 @@ "terser": "5.31.6", "tree-kill": "1.2.2", "tslib": "2.6.3", - "vite": "5.4.6", "watchpack": "2.4.1", "webpack": "5.94.0", "webpack-dev-middleware": "7.4.2", @@ -543,13 +545,13 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -559,9 +561,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -587,22 +589,22 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -627,95 +629,110 @@ "semver": "bin/semver.js" } }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", - "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "node_modules/@angular-devkit/build-angular/node_modules/@babel/helper-annotate-as-pure": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", + "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/preset-env": { + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.9.tgz", + "integrity": "sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.9", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.9", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.9", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.25.9", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.9", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.7", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-import-assertions": "^7.26.0", + "@babel/plugin-syntax-import-attributes": "^7.26.0", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "@babel/plugin-transform-async-generator-functions": "^7.25.0", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoped-functions": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.25.0", - "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-dotall-regex": "^7.24.7", - "@babel/plugin-transform-duplicate-keys": "^7.24.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", - "@babel/plugin-transform-dynamic-import": "^7.24.7", - "@babel/plugin-transform-exponentiation-operator": "^7.24.7", - "@babel/plugin-transform-export-namespace-from": "^7.24.7", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-function-name": "^7.25.1", - "@babel/plugin-transform-json-strings": "^7.24.7", - "@babel/plugin-transform-literals": "^7.25.2", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", - "@babel/plugin-transform-member-expression-literals": "^7.24.7", - "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-modules-systemjs": "^7.25.0", - "@babel/plugin-transform-modules-umd": "^7.24.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-new-target": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-numeric-separator": "^7.24.7", - "@babel/plugin-transform-object-rest-spread": "^7.24.7", - "@babel/plugin-transform-object-super": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-parameters": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-property-literals": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-reserved-words": "^7.24.7", - "@babel/plugin-transform-shorthand-properties": "^7.24.7", - "@babel/plugin-transform-spread": "^7.24.7", - "@babel/plugin-transform-sticky-regex": "^7.24.7", - "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.8", - "@babel/plugin-transform-unicode-escapes": "^7.24.7", - "@babel/plugin-transform-unicode-property-regex": "^7.24.7", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.25.9", + "@babel/plugin-transform-async-generator-functions": "^7.26.8", + "@babel/plugin-transform-async-to-generator": "^7.25.9", + "@babel/plugin-transform-block-scoped-functions": "^7.26.5", + "@babel/plugin-transform-block-scoping": "^7.25.9", + "@babel/plugin-transform-class-properties": "^7.25.9", + "@babel/plugin-transform-class-static-block": "^7.26.0", + "@babel/plugin-transform-classes": "^7.25.9", + "@babel/plugin-transform-computed-properties": "^7.25.9", + "@babel/plugin-transform-destructuring": "^7.25.9", + "@babel/plugin-transform-dotall-regex": "^7.25.9", + "@babel/plugin-transform-duplicate-keys": "^7.25.9", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-dynamic-import": "^7.25.9", + "@babel/plugin-transform-exponentiation-operator": "^7.26.3", + "@babel/plugin-transform-export-namespace-from": "^7.25.9", + "@babel/plugin-transform-for-of": "^7.26.9", + "@babel/plugin-transform-function-name": "^7.25.9", + "@babel/plugin-transform-json-strings": "^7.25.9", + "@babel/plugin-transform-literals": "^7.25.9", + "@babel/plugin-transform-logical-assignment-operators": "^7.25.9", + "@babel/plugin-transform-member-expression-literals": "^7.25.9", + "@babel/plugin-transform-modules-amd": "^7.25.9", + "@babel/plugin-transform-modules-commonjs": "^7.26.3", + "@babel/plugin-transform-modules-systemjs": "^7.25.9", + "@babel/plugin-transform-modules-umd": "^7.25.9", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.25.9", + "@babel/plugin-transform-new-target": "^7.25.9", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.26.6", + "@babel/plugin-transform-numeric-separator": "^7.25.9", + "@babel/plugin-transform-object-rest-spread": "^7.25.9", + "@babel/plugin-transform-object-super": "^7.25.9", + "@babel/plugin-transform-optional-catch-binding": "^7.25.9", + "@babel/plugin-transform-optional-chaining": "^7.25.9", + "@babel/plugin-transform-parameters": "^7.25.9", + "@babel/plugin-transform-private-methods": "^7.25.9", + "@babel/plugin-transform-private-property-in-object": "^7.25.9", + "@babel/plugin-transform-property-literals": "^7.25.9", + "@babel/plugin-transform-regenerator": "^7.25.9", + "@babel/plugin-transform-regexp-modifiers": "^7.26.0", + "@babel/plugin-transform-reserved-words": "^7.25.9", + "@babel/plugin-transform-shorthand-properties": "^7.25.9", + "@babel/plugin-transform-spread": "^7.25.9", + "@babel/plugin-transform-sticky-regex": "^7.25.9", + "@babel/plugin-transform-template-literals": "^7.26.8", + "@babel/plugin-transform-typeof-symbol": "^7.26.7", + "@babel/plugin-transform-unicode-escapes": "^7.25.9", + "@babel/plugin-transform-unicode-property-regex": "^7.25.9", + "@babel/plugin-transform-unicode-regex": "^7.25.9", + "@babel/plugin-transform-unicode-sets-regex": "^7.25.9", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.37.1", + "core-js-compat": "^3.40.0", "semver": "^6.3.1" }, "engines": { @@ -827,6 +844,20 @@ "webpack": ">=5" } }, + "node_modules/@angular-devkit/build-angular/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/browserslist": { "version": "4.24.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", @@ -1345,9 +1376,9 @@ } }, "node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1416,13 +1447,13 @@ } }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.12.tgz", - "integrity": "sha512-0Z3fdbZVRnjYWE2/VYyfy+uieY+6YZyEp4ylzklVkc+fmLNsnz4Zw6cK1LzzcBqAwKIyh1IdW20Cg7o8b0sONA==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1802.19.tgz", + "integrity": "sha512-axz1Sasn+c+GJpJexBL+B3Rh1w3wJrQq8k8gkniodjJ594p4ti2qGk7i9Tj8A4cXx5fGY+EpuZvKfI/9Tr7QwA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/architect": "0.1802.19", "rxjs": "7.8.1" }, "engines": { @@ -1436,13 +1467,13 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -1452,9 +1483,9 @@ } }, "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1581,13 +1612,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.14.tgz", - "integrity": "sha512-mukjZIHHB7gWratq8fZwUq5WZ+1bF4feG/idXr1wgQ+/FqWjs2PP7HDesHVcPymmRulpTyCpB7TNB1O1fgnCpA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.19.tgz", + "integrity": "sha512-P/0KjkzOf2ZShuShx3cBbjLI7XlcS6B/yCRBo1MQfCC4cZfmzPQoUEOSQeYZgy5pnC24f+dKh/+TWc5uYL/Lvg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.14", + "@angular-devkit/core": "18.2.19", "jsonc-parser": "3.3.1", "magic-string": "0.30.11", "ora": "5.4.1", @@ -1600,9 +1631,9 @@ } }, "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.14.tgz", - "integrity": "sha512-UGIGOjXuOyCW+5S4tINu7e6LOu738CmTw3h7Ui1I8OzdTIYJcYJrei8sgrwDwOYADRal+p0MeMlnykH3TM5XBA==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2034,14 +2065,14 @@ } }, "node_modules/@angular/build": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.12.tgz", - "integrity": "sha512-4Ohz+OSILoL+cCAQ4UTiCT5v6pctu3fXNoNpTEUK46OmxELk9jDITO5rNyNS7TxBn9wY69kjX5VcDf7MenquFQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.2.19.tgz", + "integrity": "sha512-dTqR+mhcZWtCRyOafvzHNVpYxMQnt8HHHqNM0kyEMzcztXL2L9zDlKr0H9d+AgGGq/v4qwCh+1gFDxsHByZwMQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.1802.12", + "@angular-devkit/architect": "0.1802.19", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -2063,7 +2094,7 @@ "rollup": "4.22.4", "sass": "1.77.6", "semver": "7.6.3", - "vite": "5.4.6", + "vite": "~5.4.17", "watchpack": "2.4.1" }, "engines": { @@ -2103,13 +2134,13 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -2119,9 +2150,9 @@ } }, "node_modules/@angular/build/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2325,18 +2356,18 @@ } }, "node_modules/@angular/cli": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.12.tgz", - "integrity": "sha512-xhuZ/b7IhqNw1MgXf+arWf4x+GfUSt/IwbdWU4+CO8A7h0Y46zQywouP/KUK3cMQZfVdHdciTBvlpF3vFacA6Q==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.2.19.tgz", + "integrity": "sha512-LGVMTc36JQuw8QX8Sclxyei306EQW3KslopXbf7cfqt6D5/fHS+FqqA0O7V8ob/vOGMca+l6hQD27nW5Y3W6pA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1802.12", - "@angular-devkit/core": "18.2.12", - "@angular-devkit/schematics": "18.2.12", + "@angular-devkit/architect": "0.1802.19", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "@inquirer/prompts": "5.3.8", "@listr2/prompt-adapter-inquirer": "2.0.15", - "@schematics/angular": "18.2.12", + "@schematics/angular": "18.2.19", "@yarnpkg/lockfile": "1.1.0", "ini": "4.1.3", "jsonc-parser": "3.3.1", @@ -2359,13 +2390,13 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { - "version": "0.1802.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.12.tgz", - "integrity": "sha512-bepVb2/GtJppYKaeW8yTGE6egmoWZ7zagFDsmBdbF+BYp+HmeoPsclARcdryBPVq68zedyTRdvhWSUTbw1AYuw==", + "version": "0.1802.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1802.19.tgz", + "integrity": "sha512-M4B1tzxGX1nWCZr9GMM8OO0yBJO2HFSdK8M8P74vEFQfKIeq3y16IQ5zlEveJrkCOFVtmlIy2C9foMCdNyBRMA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", + "@angular-devkit/core": "18.2.19", "rxjs": "7.8.1" }, "engines": { @@ -2375,9 +2406,9 @@ } }, "node_modules/@angular/cli/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2402,25 +2433,6 @@ } } }, - "node_modules/@angular/cli/node_modules/@angular-devkit/schematics": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", - "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, "node_modules/@angular/cli/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -2718,14 +2730,14 @@ "license": "ISC" }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -2735,6 +2747,7 @@ "version": "7.26.8", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2744,6 +2757,7 @@ "version": "7.24.9", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", @@ -2774,27 +2788,31 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz", + "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.0", + "@babel/parser": "^7.26.10", + "@babel/types": "^7.26.10", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -2817,6 +2835,7 @@ "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.26.5", @@ -2833,6 +2852,7 @@ "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2865,6 +2885,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2916,13 +2937,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", - "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", + "@babel/helper-annotate-as-pure": "^7.27.1", "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, @@ -2934,13 +2955,13 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", + "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.25.9" + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -3004,6 +3025,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", @@ -3031,9 +3053,10 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3116,18 +3139,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3137,6 +3160,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3158,25 +3182,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", - "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -3269,23 +3294,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", @@ -3318,7 +3326,6 @@ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -3372,6 +3379,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" @@ -3444,7 +3452,6 @@ "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -3571,7 +3578,6 @@ "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, @@ -3616,16 +3622,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", - "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", + "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-remap-async-to-generator": "^7.25.0", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/traverse": "^7.25.0" + "@babel/helper-plugin-utils": "^7.26.5", + "@babel/helper-remap-async-to-generator": "^7.25.9", + "@babel/traverse": "^7.26.8" }, "engines": { "node": ">=6.9.0" @@ -3635,15 +3640,15 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", - "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", + "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-remap-async-to-generator": "^7.24.7" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.25.9", + "@babel/helper-remap-async-to-generator": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -4003,6 +4008,7 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.26.0", @@ -4281,6 +4287,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", @@ -4298,16 +4321,16 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", + "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.11.0", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -4318,6 +4341,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -4598,9 +4635,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -4610,30 +4647,30 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -4642,13 +4679,13 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -4657,26 +4694,14 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@babel/types": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", - "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -4687,8 +4712,7 @@ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@bitwarden/admin-console": { "resolved": "libs/admin-console", @@ -5022,23 +5046,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@compodoc/compodoc/node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", @@ -5055,42 +5062,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.26.8.tgz", - "integrity": "sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-remap-async-to-generator": "^7.25.9", - "@babel/traverse": "^7.26.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", - "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-remap-async-to-generator": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@compodoc/compodoc/node_modules/@babel/plugin-transform-private-methods": { "version": "7.25.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.7.tgz", @@ -5233,19 +5204,6 @@ "node": ">= 6" } }, - "node_modules/@compodoc/compodoc/node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@compodoc/compodoc/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7218,7 +7176,6 @@ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -7236,7 +7193,6 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -7247,7 +7203,6 @@ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7262,7 +7217,6 @@ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -7277,7 +7231,6 @@ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -7291,7 +7244,6 @@ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -7308,7 +7260,6 @@ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -7322,7 +7273,6 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7332,8 +7282,7 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", @@ -7351,7 +7300,6 @@ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -7370,7 +7318,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7381,7 +7328,6 @@ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -7430,7 +7376,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -7444,7 +7389,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -7459,8 +7403,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/core/node_modules/slash": { "version": "3.0.0", @@ -7468,11 +7411,23 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/@jest/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -7495,7 +7450,6 @@ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" @@ -7541,7 +7495,6 @@ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -7558,7 +7511,6 @@ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -7603,7 +7555,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7616,7 +7567,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7638,7 +7588,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7652,7 +7601,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7676,7 +7624,6 @@ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -7692,7 +7639,6 @@ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", @@ -7709,7 +7655,6 @@ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -7726,7 +7671,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7737,7 +7681,6 @@ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", @@ -7764,8 +7707,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@jest/transform/node_modules/slash": { "version": "3.0.0", @@ -7773,7 +7715,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -7913,15 +7854,12 @@ } }, "node_modules/@koa/multer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@koa/multer/-/multer-3.0.2.tgz", - "integrity": "sha512-Q6WfPpE06mJWyZD1fzxM6zWywaoo+zocAn2YA9QYz4RsecoASr1h/kSzG0c5seDpFVKCMZM9raEfuM7XfqbRLw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@koa/multer/-/multer-3.1.0.tgz", + "integrity": "sha512-ETf4OLpOew9XE9lyU+5HIqk3TCmdGAw9pUXgxzrlYip+PkxLGoU4meiVTxiW4B6lxdBNijb3DFQ7M2woLcDL1g==", "license": "MIT", - "dependencies": { - "fix-esm": "1.0.1" - }, "engines": { - "node": ">= 8" + "node": ">= 14" }, "peerDependencies": { "multer": "*" @@ -8353,9 +8291,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.12.tgz", - "integrity": "sha512-FFJAwtWbtpncMOVNuULPBwFJB7GSjiUwO93eGTzRp8O4EPQ8lCQeFbezQm/NP34+T0+GBLGzPSuQT+muob8YKw==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.19.tgz", + "integrity": "sha512-bExj5JrByKPibsqBbn5Pjn8lo91AUOTsyP2hgKpnOnmSr62rhWSiRwXltgz2MCiZRmuUznpt93WiOLixgYfYvQ==", "dev": true, "license": "MIT", "engines": { @@ -9633,14 +9571,14 @@ "license": "MIT" }, "node_modules/@schematics/angular": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", - "integrity": "sha512-sIoeipsisK5eTLW3XuNZYcal83AfslBbgI7LnV+3VrXwpasKPGHwo2ZdwhCd2IXAkuJ02Iyu7MyV0aQRM9i/3g==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.19.tgz", + "integrity": "sha512-s9aynH/fwB/LT94miVfsaL2C4Qd5BLgjMzWFx7iJ8Hyv7FjOBGYO6eGVovjCt2c6/abG+GQAk4EBOCfg3AUtCA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "18.2.12", - "@angular-devkit/schematics": "18.2.12", + "@angular-devkit/core": "18.2.19", + "@angular-devkit/schematics": "18.2.19", "jsonc-parser": "3.3.1" }, "engines": { @@ -9650,9 +9588,9 @@ } }, "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.12.tgz", - "integrity": "sha512-NtB6ypsaDyPE6/fqWOdfTmACs+yK5RqfH5tStEzWFeeDsIEDYKsJ06ypuRep7qTjYus5Rmttk0Ds+cFgz8JdUQ==", + "version": "18.2.19", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.19.tgz", + "integrity": "sha512-Ptf92Zomc6FCr7GWmHKdgOUbA1GpctZwH/hRcpYpU3tM56MG2t5FOFpufnE595GgolOCktabkFEoODMG8PBVDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9677,25 +9615,6 @@ } } }, - "node_modules/@schematics/angular/node_modules/@angular-devkit/schematics": { - "version": "18.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.12.tgz", - "integrity": "sha512-mMea9txHbnCX5lXLHlo0RAgfhFHDio45/jMsREM2PA8UtVf2S8ltXz7ZwUrUyMQRv8vaSfn4ijDstF4hDMnRgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/core": "18.2.12", - "jsonc-parser": "3.3.1", - "magic-string": "0.30.11", - "ora": "5.4.1", - "rxjs": "7.8.1" - }, - "engines": { - "node": "^18.19.1 || ^20.11.1 || >=22.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - } - }, "node_modules/@schematics/angular/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -10877,6 +10796,43 @@ "storybook": "^8.6.12" } }, + "node_modules/@storybook/test-runner": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/@storybook/test-runner/-/test-runner-0.22.0.tgz", + "integrity": "sha512-fKY6MTE/bcvMaulKXy+z0fPmRXJx1REkYMOMcGn8zn6uffyBigGgaVf/sZ+AZfibwvjzg/StWhJ9HvAM8pc14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.5", + "@babel/generator": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5", + "@jest/types": "^29.6.3", + "@storybook/csf": "^0.1.11", + "@swc/core": "^1.5.22", + "@swc/jest": "^0.2.23", + "expect-playwright": "^0.8.0", + "jest": "^29.6.4", + "jest-circus": "^29.6.4", + "jest-environment-node": "^29.6.4", + "jest-junit": "^16.0.0", + "jest-playwright-preset": "^4.0.0", + "jest-runner": "^29.6.4", + "jest-serializer-html": "^7.1.0", + "jest-watch-typeahead": "^2.0.0", + "nyc": "^15.1.0", + "playwright": "^1.14.0" + }, + "bin": { + "test-storybook": "dist/test-storybook.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "storybook": "^0.0.0-0 || ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 || ^9.0.0-0" + } + }, "node_modules/@storybook/theming": { "version": "8.6.12", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.12.tgz", @@ -10940,6 +10896,250 @@ "storybook": "^8.6.12" } }, + "node_modules/@swc/core": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", + "integrity": "sha512-MaQEIpfcEMzx3VWWopbofKJvaraqmL6HbLlw2bFZ7qYqYw3rkhM0cQVEgyzbHtTWwCwPMFZSC2DUbhlZgrMfLg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.21" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.11.24", + "@swc/core-darwin-x64": "1.11.24", + "@swc/core-linux-arm-gnueabihf": "1.11.24", + "@swc/core-linux-arm64-gnu": "1.11.24", + "@swc/core-linux-arm64-musl": "1.11.24", + "@swc/core-linux-x64-gnu": "1.11.24", + "@swc/core-linux-x64-musl": "1.11.24", + "@swc/core-win32-arm64-msvc": "1.11.24", + "@swc/core-win32-ia32-msvc": "1.11.24", + "@swc/core-win32-x64-msvc": "1.11.24" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.24.tgz", + "integrity": "sha512-dhtVj0PC1APOF4fl5qT2neGjRLgHAAYfiVP8poJelhzhB/318bO+QCFWAiimcDoyMgpCXOhTp757gnoJJrheWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.24.tgz", + "integrity": "sha512-H/3cPs8uxcj2Fe3SoLlofN5JG6Ny5bl8DuZ6Yc2wr7gQFBmyBkbZEz+sPVgsID7IXuz7vTP95kMm1VL74SO5AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.24.tgz", + "integrity": "sha512-PHJgWEpCsLo/NGj+A2lXZ2mgGjsr96ULNW3+T3Bj2KTc8XtMUkE8tmY2Da20ItZOvPNC/69KroU7edyo1Flfbw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.24.tgz", + "integrity": "sha512-C2FJb08+n5SD4CYWCTZx1uR88BN41ZieoHvI8A55hfVf2woT8+6ZiBzt74qW2g+ntZ535Jts5VwXAKdu41HpBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.24.tgz", + "integrity": "sha512-ypXLIdszRo0re7PNNaXN0+2lD454G8l9LPK/rbfRXnhLWDBPURxzKlLlU/YGd2zP98wPcVooMmegRSNOKfvErw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.24.tgz", + "integrity": "sha512-IM7d+STVZD48zxcgo69L0yYptfhaaE9cMZ+9OoMxirNafhKKXwoZuufol1+alEFKc+Wbwp+aUPe/DeWC/Lh3dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.24.tgz", + "integrity": "sha512-DZByJaMVzSfjQKKQn3cqSeqwy6lpMaQDQQ4HPlch9FWtDx/dLcpdIhxssqZXcR2rhaQVIaRQsCqwV6orSDGAGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.24.tgz", + "integrity": "sha512-Q64Ytn23y9aVDKN5iryFi8mRgyHw3/kyjTjT4qFCa8AEb5sGUuSj//AUZ6c0J7hQKMHlg9do5Etvoe61V98/JQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.24.tgz", + "integrity": "sha512-9pKLIisE/Hh2vJhGIPvSoTK4uBSPxNVyXHmOrtdDot4E1FUUI74Vi8tFdlwNbaj8/vusVnb8xPXsxF1uB0VgiQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.11.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.24.tgz", + "integrity": "sha512-sybnXtOsdB+XvzVFlBVGgRHLqp3yRpHK7CrmpuDKszhj/QhmsaZzY/GHSeALlMtLup13M0gqbcQvsTNlAHTg3w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/jest": { + "version": "0.2.38", + "resolved": "https://registry.npmjs.org/@swc/jest/-/jest-0.2.38.tgz", + "integrity": "sha512-HMoZgXWMqChJwffdDjvplH53g9G2ALQes3HKXDEdliB/b85OQ0CTSbxG8VSeCwiAn7cOaDVEt4mwmZvbHcS52w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/create-cache-key-function": "^29.7.0", + "@swc/counter": "^0.1.3", + "jsonc-parser": "^3.2.0" + }, + "engines": { + "npm": ">= 7.0.0" + }, + "peerDependencies": { + "@swc/core": "*" + } + }, + "node_modules/@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -11160,7 +11360,6 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -11175,7 +11374,6 @@ "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -11186,7 +11384,6 @@ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -11198,7 +11395,6 @@ "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.20.7" } @@ -11394,7 +11590,6 @@ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -11564,6 +11759,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/junit-report-builder": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/junit-report-builder/-/junit-report-builder-3.0.2.tgz", + "integrity": "sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keygrip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", @@ -11714,9 +11916,9 @@ } }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.3.tgz", + "integrity": "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==", "dev": true, "license": "MIT", "dependencies": { @@ -11820,9 +12022,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", - "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "version": "18.3.20", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz", + "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "dev": true, "license": "MIT", "dependencies": { @@ -11966,6 +12168,16 @@ "license": "MIT", "optional": true }, + "node_modules/@types/wait-on": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/@types/wait-on/-/wait-on-5.3.4.tgz", + "integrity": "sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/webpack-env": { "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", @@ -13903,6 +14115,19 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -14039,6 +14264,13 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true, + "license": "MIT" + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -14414,6 +14646,39 @@ "node": ">=4" } }, + "node_modules/axe-html-reporter": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/axe-html-reporter/-/axe-html-reporter-2.2.11.tgz", + "integrity": "sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mustache": "^4.0.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "axe-core": ">=3" + } + }, + "node_modules/axe-playwright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axe-playwright/-/axe-playwright-2.1.0.tgz", + "integrity": "sha512-tY48SX56XaAp16oHPyD4DXpybz8Jxdz9P7exTjF/4AV70EGUavk+1fUPWirM0OYBR+YyDx6hUeDvuHVA6fB9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/junit-report-builder": "^3.0.2", + "axe-core": "^4.10.1", + "axe-html-reporter": "2.2.11", + "junit-report-builder": "^5.1.1", + "picocolors": "^1.1.1" + }, + "peerDependencies": { + "playwright": ">1.0.0" + } + }, "node_modules/axios": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", @@ -14441,7 +14706,6 @@ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", @@ -14464,7 +14728,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -14493,7 +14756,6 @@ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -14511,7 +14773,6 @@ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -14529,7 +14790,6 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -14540,7 +14800,6 @@ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -14658,7 +14917,6 @@ "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -14686,7 +14944,6 @@ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -15101,6 +15358,7 @@ "version": "4.23.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -15148,7 +15406,6 @@ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -15529,6 +15786,68 @@ "node": ">=8" } }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/caching-transform/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -15611,7 +15930,6 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -15630,6 +15948,7 @@ "version": "1.0.30001717", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -15728,7 +16047,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -16095,8 +16413,7 @@ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", @@ -16181,6 +16498,13 @@ "node": ">=4.0.0" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, "node_modules/compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", @@ -16637,7 +16961,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", "hasInstallScript": true, - "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -16809,7 +17132,6 @@ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -17000,6 +17322,20 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/cwd": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/cwd/-/cwd-0.10.0.tgz", + "integrity": "sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-pkg": "^0.1.2", + "fs-exists-sync": "^0.1.0" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/data-urls": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", @@ -17178,7 +17514,6 @@ "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -17274,6 +17609,22 @@ "node": ">= 10" } }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -17400,7 +17751,6 @@ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -17443,6 +17793,100 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/diffable-html": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/diffable-html/-/diffable-html-4.1.0.tgz", + "integrity": "sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "htmlparser2": "^3.9.2" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/diffable-html/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/diffable-html/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/diffable-html/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "node_modules/dir-compare": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", @@ -18035,6 +18479,7 @@ "version": "1.5.151", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "dev": true, "license": "ISC" }, "node_modules/electron-updater": { @@ -18115,7 +18560,6 @@ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18440,8 +18884,7 @@ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/es6-shim": { "version": "0.35.8", @@ -18520,6 +18963,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -19243,7 +19687,6 @@ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -19258,6 +19701,19 @@ "node": ">=6" } }, + "node_modules/expand-tilde": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", + "integrity": "sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-homedir": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -19275,6 +19731,24 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-playwright": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/expect-playwright/-/expect-playwright-0.8.0.tgz", + "integrity": "sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==", + "dev": true, + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", + "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.2.tgz", @@ -19654,7 +20128,6 @@ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -19829,6 +20302,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-file-up": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", + "integrity": "sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-exists-sync": "^0.1.0", + "resolve-dir": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-pkg": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/find-pkg/-/find-pkg-0.1.2.tgz", + "integrity": "sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-file-up": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/find-process": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/find-process/-/find-process-1.4.10.tgz", + "integrity": "sha512-ncYFnWEIwL7PzmrK1yZtaccN8GhethD37RzBHG6iOZoFYB4vSmLLXfeWJjeN5nMvCJMjOtBvBBF8OgxEcikiZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~4.1.2", + "commander": "^12.1.0", + "loglevel": "^1.9.2" + }, + "bin": { + "find-process": "bin/find-process.js" + } + }, + "node_modules/find-process/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", @@ -19861,17 +20386,6 @@ "micromatch": "^4.0.2" } }, - "node_modules/fix-esm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz", - "integrity": "sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==", - "license": "WTFPL OR CC0-1.0", - "dependencies": { - "@babel/core": "^7.14.6", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-transform-modules-commonjs": "^7.14.5" - } - }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -20363,6 +20877,27 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/front-matter": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/front-matter/-/front-matter-4.0.2.tgz", @@ -20407,6 +20942,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fs-extra": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", @@ -20527,6 +21072,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -20585,7 +21131,6 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -20736,6 +21281,56 @@ "node": ">=10.0" } }, + "node_modules/global-modules": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", + "integrity": "sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^0.1.4", + "is-windows": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", + "integrity": "sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.0", + "ini": "^1.3.4", + "is-windows": "^0.2.0", + "which": "^1.2.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -21000,6 +21595,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -21022,6 +21644,19 @@ "he": "bin/he" } }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -21142,8 +21777,7 @@ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/html-loader": { "version": "5.1.0", @@ -21441,9 +22075,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", - "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", + "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", "dev": true, "license": "MIT", "dependencies": { @@ -22228,7 +22862,6 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -22533,6 +23166,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -22598,6 +23238,16 @@ "dev": true, "license": "MIT" }, + "node_modules/is-windows": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", + "integrity": "sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -22655,6 +23305,19 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-instrument": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", @@ -22672,13 +23335,116 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -22694,7 +23460,6 @@ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -22710,7 +23475,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -22721,7 +23485,6 @@ "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -22795,7 +23558,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -22823,7 +23585,6 @@ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -22839,7 +23600,6 @@ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -22872,7 +23632,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -22886,7 +23645,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -22901,8 +23659,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-circus/node_modules/slash": { "version": "3.0.0", @@ -22910,7 +23667,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -22921,7 +23677,6 @@ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -22956,7 +23711,6 @@ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -23003,7 +23757,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23017,7 +23770,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23030,7 +23782,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -23052,7 +23803,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -23066,7 +23816,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23081,8 +23830,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-config/node_modules/slash": { "version": "3.0.0", @@ -23090,7 +23838,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -23152,7 +23899,6 @@ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -23166,7 +23912,6 @@ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -23184,7 +23929,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23198,7 +23942,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23213,8 +23956,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", @@ -23443,7 +24185,6 @@ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23472,7 +24213,6 @@ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", @@ -23525,7 +24265,6 @@ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -23540,7 +24279,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -23554,7 +24292,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -23569,8 +24306,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-matcher-utils": { "version": "29.7.0", @@ -23718,13 +24454,106 @@ "typescript": "^3.0.0 || ^4.0.0 || ^5.0.0" } }, + "node_modules/jest-playwright-preset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jest-playwright-preset/-/jest-playwright-preset-4.0.0.tgz", + "integrity": "sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect-playwright": "^0.8.0", + "jest-process-manager": "^0.4.0", + "nyc": "^15.1.0", + "playwright-core": ">=1.2.0", + "rimraf": "^3.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "jest": "^29.3.1", + "jest-circus": "^29.3.1", + "jest-environment-node": "^29.3.1", + "jest-runner": "^29.3.1" + } + }, + "node_modules/jest-playwright-preset/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-playwright-preset/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-playwright-preset/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-playwright-preset/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-playwright-preset/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -23801,13 +24630,58 @@ "dev": true, "license": "MIT" }, + "node_modules/jest-process-manager": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jest-process-manager/-/jest-process-manager-0.4.0.tgz", + "integrity": "sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/wait-on": "^5.2.0", + "chalk": "^4.1.0", + "cwd": "^0.10.0", + "exit": "^0.1.2", + "find-process": "^1.4.4", + "prompts": "^2.4.1", + "signal-exit": "^3.0.3", + "spawnd": "^5.0.0", + "tree-kill": "^1.2.2", + "wait-on": "^7.0.0" + } + }, + "node_modules/jest-process-manager/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jest-process-manager/node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/jest-regex-util": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -23818,7 +24692,6 @@ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -23840,7 +24713,6 @@ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -23855,7 +24727,6 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -23866,7 +24737,6 @@ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -23900,7 +24770,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -23911,7 +24780,6 @@ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -23923,7 +24791,6 @@ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -23958,7 +24825,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -23971,7 +24837,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -23993,7 +24858,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -24007,18 +24871,26 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, + "node_modules/jest-serializer-html": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/jest-serializer-html/-/jest-serializer-html-7.1.0.tgz", + "integrity": "sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "diffable-html": "^4.1.0" + } + }, "node_modules/jest-snapshot": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -24051,7 +24923,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24065,7 +24936,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -24080,8 +24950,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jest-util": { "version": "29.7.0", @@ -24120,7 +24989,6 @@ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -24139,7 +25007,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24153,7 +25020,6 @@ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -24167,7 +25033,6 @@ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -24182,8 +25047,111 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, + "license": "MIT" + }, + "node_modules/jest-watch-typeahead": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/jest-watch-typeahead/-/jest-watch-typeahead-2.2.2.tgz", + "integrity": "sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "ansi-escapes": "^6.0.0", + "chalk": "^5.2.0", + "jest-regex-util": "^29.0.0", + "jest-watcher": "^29.0.0", + "slash": "^5.0.0", + "string-length": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": "^27.0.0 || ^28.0.0 || ^29.0.0" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watch-typeahead/node_modules/char-regex": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-2.0.2.tgz", + "integrity": "sha512-cbGOjAptfM2LVmWhwRFHEKTPkLwNddVmuqYZQt895yXwAsWsXObCG+YN4DGQ/JBtT4GP1a1lPPdio2z413LmTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/jest-watch-typeahead/node_modules/string-length": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-5.0.1.tgz", + "integrity": "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^2.0.0", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watch-typeahead/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } }, "node_modules/jest-watcher": { "version": "29.7.0", @@ -24191,7 +25159,6 @@ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", @@ -24212,7 +25179,6 @@ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -24229,7 +25195,6 @@ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -24398,15 +25363,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -24476,6 +25441,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -24576,6 +25542,47 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/junit-report-builder": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/junit-report-builder/-/junit-report-builder-5.1.1.tgz", + "integrity": "sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "make-dir": "^3.1.0", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/junit-report-builder/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/junit-report-builder/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/jwt-decode": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", @@ -24955,7 +25962,6 @@ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -25660,6 +26666,13 @@ "license": "MIT", "peer": true }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -26009,6 +27022,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -26059,7 +27073,6 @@ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -26155,7 +27168,6 @@ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -27577,9 +28589,9 @@ } }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -27645,6 +28657,16 @@ "readable-stream": "^3.6.0" } }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "dev": true, + "license": "MIT", + "bin": { + "mustache": "bin/mustache" + } + }, "node_modules/mute-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", @@ -28222,8 +29244,7 @@ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/node-machine-id": { "version": "1.1.12", @@ -28231,10 +29252,24 @@ "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, "license": "MIT" }, "node_modules/nopt": { @@ -28902,6 +29937,350 @@ "node": ">=6" } }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nyc/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nyc/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -29171,6 +30550,16 @@ "dev": true, "license": "MIT" }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/os-name": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", @@ -29328,6 +30717,22 @@ "node": ">=6" } }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -29609,6 +31014,16 @@ "node": ">= 0.10" } }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parse5": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", @@ -30170,6 +31585,53 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.52.0.tgz", + "integrity": "sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.52.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", @@ -30692,6 +32154,19 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/process-on-spawn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", + "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -30866,8 +32341,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/qrcode-parser": { "version": "2.1.3", @@ -31366,6 +32840,19 @@ "node": ">= 0.10" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "license": "ISC", + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -31545,6 +33032,13 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true, + "license": "ISC" + }, "node_modules/requireindex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", @@ -31608,6 +33102,20 @@ "node": ">=8" } }, + "node_modules/resolve-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", + "integrity": "sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^1.2.2", + "global-modules": "^0.2.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -32502,6 +34010,13 @@ "node": ">= 0.8" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, + "license": "ISC" + }, "node_modules/set-cookie-parser": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", @@ -32946,6 +34461,164 @@ "node": ">=0.10.0" } }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/spawn-wrap/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/spawn-wrap/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/spawn-wrap/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/spawn-wrap/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/spawn-wrap/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/spawnd": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spawnd/-/spawnd-5.0.0.tgz", + "integrity": "sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "exit": "^0.1.2", + "signal-exit": "^3.0.3", + "tree-kill": "^1.2.2", + "wait-port": "^0.2.9" + } + }, + "node_modules/spawnd/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -33292,7 +34965,6 @@ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -33422,7 +35094,6 @@ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -33985,7 +35656,6 @@ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -34001,7 +35671,6 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -34014,7 +35683,6 @@ "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -34036,7 +35704,6 @@ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -34206,8 +35873,7 @@ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -34957,6 +36623,16 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, "node_modules/typescript": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", @@ -35404,6 +37080,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -35548,7 +37225,6 @@ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -35563,8 +37239,7 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -35658,9 +37333,9 @@ } }, "node_modules/vite": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz", - "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==", + "version": "5.4.19", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz", + "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", "dev": true, "license": "MIT", "dependencies": { @@ -36189,6 +37864,109 @@ "tslib": "^2.1.0" } }, + "node_modules/wait-port": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/wait-port/-/wait-port-0.2.14.tgz", + "integrity": "sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.4.2", + "commander": "^3.0.2", + "debug": "^4.1.1" + }, + "bin": { + "wait-port": "bin/wait-port.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wait-port/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/wait-port/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wait-port/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true, + "license": "MIT" + }, + "node_modules/wait-port/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/wait-port/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/wait-port/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -36202,7 +37980,6 @@ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -36941,6 +38718,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true, + "license": "ISC" + }, "node_modules/which-typed-array": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", @@ -37088,7 +38872,6 @@ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -37102,8 +38885,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ws": { "version": "8.18.1", @@ -37181,6 +38963,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { diff --git a/package.json b/package.json index 7c1873090c1..2993707313f 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,8 @@ "storybook": "ng run components:storybook", "build-storybook": "ng run components:build-storybook", "build-storybook:ci": "ng run components:build-storybook --webpack-stats-json", + "test-stories": "test-storybook --url http://localhost:6006", + "test-stories:watch": "test-stories --watch", "postinstall": "patch-package" }, "workspaces": [ @@ -36,9 +38,9 @@ "libs/**/*" ], "devDependencies": { - "@angular-devkit/build-angular": "18.2.12", + "@angular-devkit/build-angular": "18.2.19", "@angular-eslint/schematics": "18.4.3", - "@angular/cli": "18.2.12", + "@angular/cli": "18.2.19", "@angular/compiler-cli": "18.2.13", "@babel/core": "7.24.9", "@babel/preset-env": "7.24.8", @@ -46,13 +48,14 @@ "@electron/notarize": "2.5.0", "@electron/rebuild": "3.7.2", "@lit-labs/signals": "0.1.2", - "@ngtools/webpack": "18.2.12", + "@ngtools/webpack": "18.2.19", "@storybook/addon-a11y": "8.6.12", "@storybook/addon-actions": "8.6.12", "@storybook/addon-designs": "8.2.1", "@storybook/addon-essentials": "8.6.12", "@storybook/addon-interactions": "8.6.12", "@storybook/addon-links": "8.6.12", + "@storybook/test-runner": "0.22.0", "@storybook/addon-themes": "8.6.12", "@storybook/angular": "8.6.12", "@storybook/manager-api": "8.6.12", @@ -72,7 +75,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.14.1", + "@types/node": "22.15.3", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/papaparse": "5.3.15", @@ -85,6 +88,7 @@ "@yao-pkg/pkg": "5.16.1", "angular-eslint": "18.4.3", "autoprefixer": "10.4.21", + "axe-playwright": "2.1.0", "babel-loader": "9.2.1", "base64-loader": "1.0.0", "browserslist": "4.23.2", @@ -159,7 +163,7 @@ "@bitwarden/sdk-internal": "0.2.0-main.159", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", - "@koa/multer": "3.0.2", + "@koa/multer": "3.1.0", "@koa/router": "13.1.0", "@microsoft/signalr": "8.0.7", "@microsoft/signalr-protocol-msgpack": "8.0.7", @@ -186,7 +190,7 @@ "lit": "3.2.1", "lowdb": "1.0.0", "lunr": "2.3.9", - "multer": "1.4.5-lts.1", + "multer": "1.4.5-lts.2", "ngx-toastr": "19.0.0", "node-fetch": "2.6.12", "node-forge": "1.3.1", @@ -212,7 +216,7 @@ }, "react": "18.3.1", "react-dom": "18.3.1", - "@types/react": "18.3.1", + "@types/react": "18.3.20", "replacestream": "4.0.3" }, "lint-staged": { diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 7ffa34df58c..e8c3f669c0c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -45,6 +45,7 @@ "files": [ ".storybook/main.ts", ".storybook/manager.js", + ".storybook/test-runner.ts", "apps/browser/src/autofill/content/components/.lit-storybook/main.ts" ], "include": ["apps/**/*", "libs/**/*", "bitwarden_license/**/*", "scripts/**/*"],