From e568a7a812d3dee9509f7797a25928f6ce2a6b34 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Tue, 14 Jan 2025 11:03:58 -0500 Subject: [PATCH 01/28] [CL-524] Ignore kitchen sink virtual scroll story (#12858) --- .../components/dialog-virtual-scroll-block.component.ts | 2 +- .../src/stories/kitchen-sink/kitchen-sink.stories.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts index 02b49a3e915..7709506f050 100644 --- a/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts +++ b/libs/components/src/stories/kitchen-sink/components/dialog-virtual-scroll-block.component.ts @@ -12,7 +12,7 @@ import { TableDataSource, TableModule } from "../../../table"; imports: [DialogModule, IconButtonModule, SectionComponent, TableModule, ScrollingModule], template: /*html*/ ` - + Id diff --git a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts index 44080e29049..a90597c1710 100644 --- a/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts +++ b/libs/components/src/stories/kitchen-sink/kitchen-sink.stories.ts @@ -196,4 +196,10 @@ export const VirtualScrollBlockingDialog: Story = { await userEvent.click(dialogButton); }, + parameters: { + chromatic: { + // TODO CL-524 fix flaky story (number of virtual scroll rows is inconsistent) + disableSnapshot: true, + }, + }, }; From 62e879940c439d40263c270371e58573cc4bcd04 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:23:59 -0500 Subject: [PATCH 02/28] [deps] SM: Update typescript-eslint monorepo to v8 (major) (#10601) * [deps] SM: Update typescript-eslint monorepo to v8 --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Hinton Co-authored-by: Daniel James Smith --- .eslintrc.json | 1 + apps/browser/postcss.config.js | 2 +- .../auto-submit-login.background.spec.ts | 4 + .../content/auto-submit-login.spec.ts | 2 + .../content/components/buttons/edit-button.ts | 2 + .../content/components/cipher/types.ts | 4 + .../content/content-message-handler.spec.ts | 2 + .../trigger-autofill-script-injection.spec.ts | 2 + ...trap-autofill-overlay-button.deprecated.ts | 2 + ...tstrap-autofill-overlay-list.deprecated.ts | 2 + .../content/fido2-content-script.spec.ts | 16 ++ .../fido2-page-script-append.mv2.spec.ts | 6 + .../fido2/content/fido2-page-script.ts | 2 + ...do2-page-script.webauthn-supported.spec.ts | 6 + ...2-page-script.webauthn-unsupported.spec.ts | 2 + apps/browser/src/autofill/notification/bar.ts | 2 + .../bootstrap-autofill-inline-menu-button.ts | 2 + .../bootstrap-autofill-inline-menu-list.ts | 2 + ...ootstrap-autofill-inline-menu-container.ts | 2 + .../autofill-overlay-content.service.ts | 2 + .../autofill/services/dom-query.service.ts | 2 + .../background/nativeMessaging.background.ts | 2 + .../background-browser-biometrics.service.ts | 4 + apps/browser/src/platform/flags.ts | 2 - .../offscreen-document.spec.ts | 2 + ...cal-backed-session-storage.service.spec.ts | 4 + .../local-backed-session-storage.service.ts | 2 + .../sdk/browser-sdk-client-factory.ts | 2 + apps/browser/src/popup/main.ts | 2 + .../content/lp-fileless-importer.spec.ts | 2 + ...-import-download-script-append.mv2.spec.ts | 2 + .../lp-suppress-import-download.spec.ts | 2 + .../admin-console/commands/share.command.ts | 2 + apps/cli/src/auth/commands/login.command.ts | 4 + apps/cli/src/commands/edit.command.ts | 2 + apps/cli/src/platform/flags.ts | 2 - .../node-env-secure-storage.service.ts | 4 + .../src/tools/send/commands/create.command.ts | 2 + .../src/tools/send/commands/edit.command.ts | 2 + .../tools/send/commands/receive.command.ts | 2 + apps/cli/src/vault/create.command.ts | 2 + apps/desktop/config/config.js | 2 + apps/desktop/postcss.config.js | 2 +- apps/desktop/scripts/after-pack.js | 2 +- apps/desktop/scripts/after-sign.js | 2 +- apps/desktop/scripts/build-macos-extension.js | 2 +- apps/desktop/scripts/start.js | 2 +- apps/desktop/sign.js | 2 +- apps/desktop/src/app/app-routing.module.ts | 1 + apps/desktop/src/app/main.ts | 2 + .../login/desktop-login-component.service.ts | 2 + .../src/auth/login/login-v1.component.ts | 2 + .../desktop/src/main/native-messaging.main.ts | 2 + apps/desktop/src/platform/flags.ts | 2 - .../biometric-message-handler.service.ts | 4 + .../duckduckgo-message-handler.service.ts | 4 + .../encrypted-message-handler.service.ts | 4 + apps/web/postcss.config.js | 2 +- .../common/base.events.component.ts | 2 + .../manage/entity-events.component.ts | 2 + ...families-for-enterprise-setup.component.ts | 2 + .../app/platform/web-sdk-client-factory.ts | 2 + .../tools/send/send-access-file.component.ts | 2 + .../folder-add-edit.component.ts | 2 + apps/web/src/connectors/captcha.ts | 6 + apps/web/src/connectors/duo-redirect.ts | 2 + apps/web/src/connectors/sso.ts | 2 + apps/web/src/connectors/webauthn-fallback.ts | 6 + apps/web/src/connectors/webauthn.ts | 6 + apps/web/src/utils/flags.ts | 2 - .../device-approvals.component.ts | 2 + .../services/sm-porting-api.service.ts | 2 + .../src/auth/components/login-v1.component.ts | 2 + .../vault/components/attachments.component.ts | 4 + .../src/vault/components/view.component.ts | 2 + .../login-decryption-options.component.ts | 2 + .../auth/src/angular/login/login.component.ts | 2 + .../decode-jwt-token-to-json.utility.ts | 4 + .../device-trust.service.implementation.ts | 2 + .../user-verification.service.ts | 4 + libs/common/src/platform/misc/flags.ts | 2 - libs/common/src/platform/misc/utils.ts | 4 + .../models/domain/domain-base.spec.ts | 4 + .../src/platform/models/domain/domain-base.ts | 2 +- .../src/platform/models/domain/enc-string.ts | 6 + ...ve-autofill-settings-to-state-providers.ts | 2 + ...ard-to-autofill-settings-state-provider.ts | 2 + ...move-domain-settings-to-state-providers.ts | 2 + .../src/tools/send/models/domain/send.ts | 2 + .../src/vault/icon/build-cipher-icon.ts | 2 + .../src/vault/models/domain/attachment.ts | 2 + .../src/vault/models/view/cipher.view.ts | 2 + .../src/vault/models/view/login-uri.view.ts | 2 + .../src/a11y/a11y-grid.directive.ts | 2 + .../src/navigation/nav-group.component.ts | 2 + .../src/services/import.service.spec.ts | 2 +- libs/key-management/src/key.service.ts | 2 + libs/shared/jest.config.angular.js | 2 +- ...edit-custom-field-dialog.component.spec.ts | 2 + .../download-attachment.component.ts | 2 + package-lock.json | 192 ++++++++++-------- package.json | 4 +- tsconfig.eslint.json | 1 + 103 files changed, 355 insertions(+), 106 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c10904c8670..3fd6dec3d7e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -56,6 +56,7 @@ "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }], "@typescript-eslint/no-this-alias": ["error", { "allowedNames": ["self"] }], + "@typescript-eslint/no-unused-expressions": ["error", { "allowTernary": true }], "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], "no-console": "error", "import/no-unresolved": "off", // TODO: Look into turning off once each package is an actual package. diff --git a/apps/browser/postcss.config.js b/apps/browser/postcss.config.js index c4513687e89..83e237f06e5 100644 --- a/apps/browser/postcss.config.js +++ b/apps/browser/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable no-undef, @typescript-eslint/no-require-imports */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts index 73f936bb591..a300ac08660 100644 --- a/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts +++ b/apps/browser/src/autofill/background/auto-submit-login.background.spec.ts @@ -453,12 +453,16 @@ describe("AutoSubmitLoginBackground", () => { sendMockExtensionMessage({ command: "triggerAutoSubmitLogin" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); it("skips acting on messages whose command does not have a registered handler", () => { sendMockExtensionMessage({ command: "someInvalidCommand" }, sender); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(autofillService.doAutoFillOnTab).not.toHaveBeenCalled; }); diff --git a/apps/browser/src/autofill/content/auto-submit-login.spec.ts b/apps/browser/src/autofill/content/auto-submit-login.spec.ts index ff1dbd4e945..d70fc1e7446 100644 --- a/apps/browser/src/autofill/content/auto-submit-login.spec.ts +++ b/apps/browser/src/autofill/content/auto-submit-login.spec.ts @@ -46,6 +46,8 @@ describe("AutoSubmitLogin content script", () => { beforeEach(() => { jest.useFakeTimers(); setupEnvironmentDefaults(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./auto-submit-login"); }); diff --git a/apps/browser/src/autofill/content/components/buttons/edit-button.ts b/apps/browser/src/autofill/content/components/buttons/edit-button.ts index 695cbfd3b9d..cacd2b59f0e 100644 --- a/apps/browser/src/autofill/content/components/buttons/edit-button.ts +++ b/apps/browser/src/autofill/content/components/buttons/edit-button.ts @@ -23,6 +23,8 @@ export function EditButton({ title=${buttonText} class=${editButtonStyles({ disabled, theme })} @click=${(event: Event) => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions !disabled && buttonAction(event); }} > diff --git a/apps/browser/src/autofill/content/components/cipher/types.ts b/apps/browser/src/autofill/content/components/cipher/types.ts index 24f528c5246..acdee756570 100644 --- a/apps/browser/src/autofill/content/components/cipher/types.ts +++ b/apps/browser/src/autofill/content/components/cipher/types.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherTypes = { Login: 1, SecureNote: 2, @@ -7,6 +9,8 @@ const CipherTypes = { type CipherType = (typeof CipherTypes)[keyof typeof CipherTypes]; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const CipherRepromptTypes = { None: 0, Password: 1, diff --git a/apps/browser/src/autofill/content/content-message-handler.spec.ts b/apps/browser/src/autofill/content/content-message-handler.spec.ts index 226fcb4bd61..a37a2e07678 100644 --- a/apps/browser/src/autofill/content/content-message-handler.spec.ts +++ b/apps/browser/src/autofill/content/content-message-handler.spec.ts @@ -19,6 +19,8 @@ describe("ContentMessageHandler", () => { ); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./content-message-handler"); }); diff --git a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts index 1ad985bc8e9..317f63e756c 100644 --- a/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts +++ b/apps/browser/src/autofill/content/trigger-autofill-script-injection.spec.ts @@ -6,6 +6,8 @@ describe("TriggerAutofillScriptInjection", () => { describe("init", () => { it("sends a message to the extension background", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../content/trigger-autofill-script-injection"); expect(chrome.runtime.sendMessage).toHaveBeenCalledWith({ diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts index fde98a58a5f..fd6a79733cb 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/button/bootstrap-autofill-overlay-button.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayButton from "./autofill-overlay-button.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-button.scss"); (function () { diff --git a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts index 714ccbfbee5..5d587bd4293 100644 --- a/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts +++ b/apps/browser/src/autofill/deprecated/overlay/pages/list/bootstrap-autofill-overlay-list.deprecated.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import AutofillOverlayList from "./autofill-overlay-list.deprecated"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./legacy-list.scss"); (function () { diff --git a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts index 94bef354a79..8885ed6299c 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-content-script.spec.ts @@ -60,6 +60,8 @@ describe("Fido2 Content Script", () => { chrome.runtime.connect = jest.fn(() => portSpy); it("destroys the messenger when the port is disconnected", () => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); triggerPortOnDisconnectEvent(portSpy); @@ -75,6 +77,8 @@ describe("Fido2 Content Script", () => { const mockResult = { credentialId: "mock" } as CreateCredentialResult; jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue(mockResult); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const response = await messenger.handler!(message, new AbortController()); @@ -99,6 +103,8 @@ describe("Fido2 Content Script", () => { data: mock(), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, new AbortController()); @@ -121,6 +127,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); const abortSpy = jest.spyOn(abortController.signal, "removeEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -141,6 +149,8 @@ describe("Fido2 Content Script", () => { abortController.abort(); }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); await messenger.handler!(message, abortController); @@ -161,6 +171,8 @@ describe("Fido2 Content Script", () => { const abortController = new AbortController(); jest.spyOn(chrome.runtime, "sendMessage").mockResolvedValue({ error: errorMessage }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); const result = messenger.handler!(message, abortController); @@ -175,6 +187,8 @@ describe("Fido2 Content Script", () => { contentType: "application/json", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); @@ -193,6 +207,8 @@ describe("Fido2 Content Script", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(messengerForDOMCommunicationSpy).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts index 6b9b41b5aac..69e17d26fe5 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script-append.mv2.spec.ts @@ -22,6 +22,8 @@ describe("FIDO2 page-script for manifest v2", () => { it("skips appending the `page-script.js` file if the document contentType is not `text/html`", () => { Object.defineProperty(window.document, "contentType", { value: "text/plain", writable: true }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).not.toHaveBeenCalled(); @@ -33,6 +35,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); @@ -48,6 +52,8 @@ describe("FIDO2 page-script for manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts index fd033090cd4..4c1761c37ba 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.ts @@ -267,6 +267,8 @@ import { Messenger } from "./messaging/messenger"; clearWaitForFocus(); void messenger.destroy(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { /** empty */ } diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts index 31e8c941e86..f1aec69193b 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-supported.spec.ts @@ -55,6 +55,8 @@ describe("Fido2 page script with native WebAuthn support", () => { setupMockedWebAuthnSupport(); beforeAll(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); }); @@ -166,6 +168,8 @@ describe("Fido2 page script with native WebAuthn support", () => { contentType: "json/application", })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); @@ -184,6 +188,8 @@ describe("Fido2 page script with native WebAuthn support", () => { }, })); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-content-script"); expect(Messenger.forDOMCommunication).not.toHaveBeenCalled(); diff --git a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts index e354453ca59..af1838ec942 100644 --- a/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts +++ b/apps/browser/src/autofill/fido2/content/fido2-page-script.webauthn-unsupported.spec.ts @@ -50,6 +50,8 @@ describe("Fido2 page script without native WebAuthn support", () => { const mockCreateCredentialsResult = createCreateCredentialResultMock(); const mockCredentialRequestOptions = createCredentialRequestOptionsMock(); const mockCredentialAssertResult = createAssertCredentialResultMock(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./fido2-page-script"); afterEach(() => { diff --git a/apps/browser/src/autofill/notification/bar.ts b/apps/browser/src/autofill/notification/bar.ts index a4b8ae44b6a..2c0ebe8e8e7 100644 --- a/apps/browser/src/autofill/notification/bar.ts +++ b/apps/browser/src/autofill/notification/bar.ts @@ -15,6 +15,8 @@ import { NotificationBarIframeInitData, } from "./abstractions/notification-bar"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./bar.scss"); const logService = new ConsoleLogService(false); diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts index 0ed14a520c1..36ef3897c56 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/button/bootstrap-autofill-inline-menu-button.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuButton } from "./autofill-inline-menu-button"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./button.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts index c302c50b4a4..b46b208b084 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/bootstrap-autofill-inline-menu-list.ts @@ -2,6 +2,8 @@ import { AutofillOverlayElement } from "../../../../enums/autofill-overlay.enum" import { AutofillInlineMenuList } from "./autofill-inline-menu-list"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./list.scss"); (function () { diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts index 16d5c29d574..522b968e533 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/menu-container/bootstrap-autofill-inline-menu-container.ts @@ -1,3 +1,5 @@ +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./menu-container.scss"); import { AutofillInlineMenuContainer } from "./autofill-inline-menu-container"; diff --git a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts index 2db08db1872..acf0dedde27 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -1417,6 +1417,8 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ url.origin + pathWithoutTrailingSlashAndSearch, url.origin + pathWithoutTrailingSlashSearchAndHash, ]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (_error) { return null; } diff --git a/apps/browser/src/autofill/services/dom-query.service.ts b/apps/browser/src/autofill/services/dom-query.service.ts index 0dbc246b235..16310397a03 100644 --- a/apps/browser/src/autofill/services/dom-query.service.ts +++ b/apps/browser/src/autofill/services/dom-query.service.ts @@ -235,6 +235,8 @@ export class DomQueryService implements DomQueryServiceInterface { if ((chrome as any).dom?.openOrClosedShadowRoot) { try { return (chrome as any).dom.openOrClosedShadowRoot(node); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index c134860f1a8..b08f1c8b566 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -342,6 +342,8 @@ export class NativeMessagingBackground { }; } this.port.postMessage(msg); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info( "[Native Messaging IPC] Disconnected from Bitwarden Desktop app because of the native port disconnecting.", diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index a0f54c58a64..4c4753c3f7f 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -75,6 +75,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { } } return BiometricsStatus.Available; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } @@ -142,6 +144,8 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { userId: id, }) ).response; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return BiometricsStatus.DesktopDisconnected; } diff --git a/apps/browser/src/platform/flags.ts b/apps/browser/src/platform/flags.ts index 383e982f065..2b1040bcd8a 100644 --- a/apps/browser/src/platform/flags.ts +++ b/apps/browser/src/platform/flags.ts @@ -11,13 +11,11 @@ import { GroupPolicyEnvironment } from "../admin-console/types/group-policy-envi import { BrowserApi } from "./browser/browser-api"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = { accountSwitching?: boolean; } & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = { managedEnvironment?: GroupPolicyEnvironment; } & SharedDevFlags; diff --git a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts index 4065f2e46d7..67fa920d18d 100644 --- a/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts +++ b/apps/browser/src/platform/offscreen-document/offscreen-document.spec.ts @@ -8,6 +8,8 @@ describe("OffscreenDocument", () => { const browserClipboardServiceReadSpy = jest.spyOn(BrowserClipboardService, "read"); const consoleErrorSpy = jest.spyOn(console, "error"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("../offscreen-document/offscreen-document"); describe("init", () => { diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts index 949ecebde8a..4d6a403a18a 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.spec.ts @@ -48,6 +48,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, @@ -69,6 +71,8 @@ describe("LocalBackedSessionStorage", () => { localStorage.internalStore["session_test"] = encrypted.encryptedString; encryptService.decryptToUtf8.mockResolvedValue(JSON.stringify("decrypted")); const result = await sut.get("test"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions expect(encryptService.decryptToUtf8).toHaveBeenCalledWith( encrypted, sessionKey, diff --git a/apps/browser/src/platform/services/local-backed-session-storage.service.ts b/apps/browser/src/platform/services/local-backed-session-storage.service.ts index bdc0bed80c0..900212ddefa 100644 --- a/apps/browser/src/platform/services/local-backed-session-storage.service.ts +++ b/apps/browser/src/platform/services/local-backed-session-storage.service.ts @@ -198,6 +198,8 @@ export class LocalBackedSessionStorageService private compareValues(value1: T, value2: T): boolean { try { return compareValues(value1, value2); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.error( `error comparing values\n${JSON.stringify(value1)}\n${JSON.stringify(value2)}`, diff --git a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts index a2f0c78cd52..0499f34a4ae 100644 --- a/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts +++ b/apps/browser/src/platform/services/sdk/browser-sdk-client-factory.ts @@ -17,6 +17,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/browser/src/popup/main.ts b/apps/browser/src/popup/main.ts index 8ffe0743bf6..dadd7917b99 100644 --- a/apps/browser/src/popup/main.ts +++ b/apps/browser/src/popup/main.ts @@ -4,7 +4,9 @@ import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; import { PopupSizeService } from "../platform/popup/layout/popup-size.service"; import { BrowserPlatformUtilsService } from "../platform/services/platform-utils/browser-platform-utils.service"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/popup.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts index 432754ab91c..21fa44b8d3f 100644 --- a/apps/browser/src/tools/content/lp-fileless-importer.spec.ts +++ b/apps/browser/src/tools/content/lp-fileless-importer.spec.ts @@ -12,6 +12,8 @@ describe("LpFilelessImporter", () => { chrome.runtime.connect = jest.fn(() => portSpy); beforeEach(() => { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-fileless-importer"); lpFilelessImporter = (globalThis as any).lpFilelessImporter; }); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts index 95b49ea00ec..8479235cc17 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download-script-append.mv2.spec.ts @@ -7,6 +7,8 @@ describe("LP Suppress Import Download for Manifest v2", () => { return node; }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download-script-append.mv2"); expect(window.document.createElement).toHaveBeenCalledWith("script"); diff --git a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts index bfff3787506..ff0ed381599 100644 --- a/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts +++ b/apps/browser/src/tools/content/lp-suppress-import-download.spec.ts @@ -10,6 +10,8 @@ describe("LP Suppress Import Download", () => { jest.spyOn(Element.prototype, "appendChild"); jest.spyOn(window, "addEventListener"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./lp-suppress-import-download"); anchor = document.createElement("a"); diff --git a/apps/cli/src/admin-console/commands/share.command.ts b/apps/cli/src/admin-console/commands/share.command.ts index 5b351efe459..e26d073326e 100644 --- a/apps/cli/src/admin-console/commands/share.command.ts +++ b/apps/cli/src/admin-console/commands/share.command.ts @@ -34,6 +34,8 @@ export class ShareCommand { if (req == null || req.length === 0) { return Response.badRequest("You must provide at least one collection id for this item."); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 2a3d5d85408..359ed08ca99 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -165,6 +165,8 @@ export class LoginCommand { if (options.method != null) { twoFactorMethod = parseInt(options.method, null); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } @@ -240,6 +242,8 @@ export class LoginCommand { if (twoFactorMethod != null) { try { selectedProvider = twoFactorProviders.filter((p) => p.type === twoFactorMethod)[0]; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.error("Invalid two-step login method."); } diff --git a/apps/cli/src/commands/edit.command.ts b/apps/cli/src/commands/edit.command.ts index dd99d03b086..8efb414f5b2 100644 --- a/apps/cli/src/commands/edit.command.ts +++ b/apps/cli/src/commands/edit.command.ts @@ -57,6 +57,8 @@ export class EditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/platform/flags.ts b/apps/cli/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/cli/src/platform/flags.ts +++ b/apps/cli/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/cli/src/platform/services/node-env-secure-storage.service.ts b/apps/cli/src/platform/services/node-env-secure-storage.service.ts index 5e9fdd26b3c..2807509e428 100644 --- a/apps/cli/src/platform/services/node-env-secure-storage.service.ts +++ b/apps/cli/src/platform/services/node-env-secure-storage.service.ts @@ -87,6 +87,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } return Utils.fromBufferToB64(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Decrypt error."); return null; @@ -104,6 +106,8 @@ export class NodeEnvSecureStorageService implements AbstractStorageService { } } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.logService.info("Session key is invalid."); } diff --git a/apps/cli/src/tools/send/commands/create.command.ts b/apps/cli/src/tools/send/commands/create.command.ts index eff351be22a..a9264c50126 100644 --- a/apps/cli/src/tools/send/commands/create.command.ts +++ b/apps/cli/src/tools/send/commands/create.command.ts @@ -49,6 +49,8 @@ export class SendCreateCommand { if (req == null) { throw new Error("Null request"); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/edit.command.ts b/apps/cli/src/tools/send/commands/edit.command.ts index 11508d5c417..ed719b58311 100644 --- a/apps/cli/src/tools/send/commands/edit.command.ts +++ b/apps/cli/src/tools/send/commands/edit.command.ts @@ -41,6 +41,8 @@ export class SendEditCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = SendResponse.fromJson(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index d27ba4f88ec..41a6681af55 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -47,6 +47,8 @@ export class SendReceiveCommand extends DownloadCommand { let urlObject: URL; try { urlObject = new URL(url); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Failed to parse the provided Send url"); } diff --git a/apps/cli/src/vault/create.command.ts b/apps/cli/src/vault/create.command.ts index 13cd666754f..a28d070d19e 100644 --- a/apps/cli/src/vault/create.command.ts +++ b/apps/cli/src/vault/create.command.ts @@ -66,6 +66,8 @@ export class CreateCommand { try { const reqJson = Buffer.from(requestJson, "base64").toString(); req = JSON.parse(reqJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return Response.badRequest("Error parsing the encoded request data."); } diff --git a/apps/desktop/config/config.js b/apps/desktop/config/config.js index 404295dd0db..30a5c16bb2a 100644 --- a/apps/desktop/config/config.js +++ b/apps/desktop/config/config.js @@ -32,6 +32,8 @@ function log(configObj) { function loadConfig(configName) { try { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports return require(`./${configName}.json`); } catch (e) { if (e instanceof Error && e.code === "MODULE_NOT_FOUND") { diff --git a/apps/desktop/postcss.config.js b/apps/desktop/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/desktop/postcss.config.js +++ b/apps/desktop/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/desktop/scripts/after-pack.js b/apps/desktop/scripts/after-pack.js index fd16cd5ffbe..30b17a44d12 100644 --- a/apps/desktop/scripts/after-pack.js +++ b/apps/desktop/scripts/after-pack.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const child_process = require("child_process"); const path = require("path"); diff --git a/apps/desktop/scripts/after-sign.js b/apps/desktop/scripts/after-sign.js index dc60e9d1838..20c24c8a76b 100644 --- a/apps/desktop/scripts/after-sign.js +++ b/apps/desktop/scripts/after-sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ require("dotenv").config(); const path = require("path"); diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js index 4cb643c34d4..649fe3b6736 100644 --- a/apps/desktop/scripts/build-macos-extension.js +++ b/apps/desktop/scripts/build-macos-extension.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ const child = require("child_process"); const { exit } = require("process"); diff --git a/apps/desktop/scripts/start.js b/apps/desktop/scripts/start.js index c1a2fb3cffc..d2c984a6f24 100644 --- a/apps/desktop/scripts/start.js +++ b/apps/desktop/scripts/start.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const concurrently = require("concurrently"); const rimraf = require("rimraf"); diff --git a/apps/desktop/sign.js b/apps/desktop/sign.js index 74c63932306..f0110ea195b 100644 --- a/apps/desktop/sign.js +++ b/apps/desktop/sign.js @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-var-requires, no-console */ +/* eslint-disable @typescript-eslint/no-require-imports, no-console */ exports.default = async function (configuration) { if (parseInt(process.env.ELECTRON_BUILDER_SIGN) === 1 && configuration.path.slice(-4) == ".exe") { diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index e4ee5ec0473..e565681de93 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -67,6 +67,7 @@ import { SendComponent } from "./tools/send/send.component"; /** * Data properties acceptable for use in route objects in the desktop */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RouteDataProperties { // For any new route data properties, add them here. // then assert that the data object satisfies this interface in the route object. diff --git a/apps/desktop/src/app/main.ts b/apps/desktop/src/app/main.ts index a0b490edaa6..287d66795d2 100644 --- a/apps/desktop/src/app/main.ts +++ b/apps/desktop/src/app/main.ts @@ -1,7 +1,9 @@ import { enableProdMode } from "@angular/core"; import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/styles.scss"); +// eslint-disable-next-line @typescript-eslint/no-require-imports require("../scss/tailwind.css"); import { AppModule } from "./app.module"; diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.ts b/apps/desktop/src/auth/login/desktop-login-component.service.ts index 8a2daef49ff..dbf689e801c 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -65,6 +65,8 @@ export class DesktopLoginComponentService try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.toastService.showToast({ variant: "error", diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts index 5b1a1c68d29..e0c3f794dba 100644 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ b/apps/desktop/src/auth/login/login-v1.component.ts @@ -249,6 +249,8 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); try { await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (err) { this.platformUtilsService.showToast( "error", diff --git a/apps/desktop/src/main/native-messaging.main.ts b/apps/desktop/src/main/native-messaging.main.ts index 6915a18deda..107d546811c 100644 --- a/apps/desktop/src/main/native-messaging.main.ts +++ b/apps/desktop/src/main/native-messaging.main.ts @@ -405,6 +405,8 @@ export class NativeMessagingMain { this.logService.info(`Error reading preferences: ${e}`); } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Browser is not installed, we can just skip it } diff --git a/apps/desktop/src/platform/flags.ts b/apps/desktop/src/platform/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/desktop/src/platform/flags.ts +++ b/apps/desktop/src/platform/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 72d00988e88..74f80785fca 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -265,6 +265,8 @@ export class BiometricMessageHandlerService { } else { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send({ command: "biometricUnlock", response: "canceled" }, appId); } @@ -342,6 +344,8 @@ export class BiometricMessageHandlerService { appId, ); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { await this.send( { command: BiometricsCommands.UnlockWithBiometricsForUser, messageId, response: false }, diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index 9a914f238b5..fa5c2f4d9f7 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -130,6 +130,8 @@ export class DuckDuckGoMessageHandlerService { sharedKey: Utils.fromBufferToB64(encryptedSecret), }, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.sendResponse({ messageId: messageId, @@ -153,6 +155,8 @@ export class DuckDuckGoMessageHandlerService { await this.encryptedMessageHandlerService.responseDataForCommand(decryptedCommandData); await this.sendEncryptedResponse(message, { command, payload: responseData }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises diff --git a/apps/desktop/src/services/encrypted-message-handler.service.ts b/apps/desktop/src/services/encrypted-message-handler.service.ts index 09c0fe2a07c..43c4b9065a7 100644 --- a/apps/desktop/src/services/encrypted-message-handler.service.ts +++ b/apps/desktop/src/services/encrypted-message-handler.service.ts @@ -178,6 +178,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } @@ -222,6 +224,8 @@ export class EncryptedMessageHandlerService { await this.messagingService.send("refreshCiphers"); return { status: "success" }; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return { status: "failure" }; } diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js index c4513687e89..c39e7ea0355 100644 --- a/apps/web/postcss.config.js +++ b/apps/web/postcss.config.js @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ +/* eslint-disable @typescript-eslint/no-require-imports, no-undef */ module.exports = { plugins: [require("tailwindcss"), require("autoprefixer"), require("postcss-nested")], }; diff --git a/apps/web/src/app/admin-console/common/base.events.component.ts b/apps/web/src/app/admin-console/common/base.events.component.ts index 1080880a466..9d06be92eb8 100644 --- a/apps/web/src/app/admin-console/common/base.events.component.ts +++ b/apps/web/src/app/admin-console/common/base.events.component.ts @@ -167,6 +167,8 @@ export abstract class BaseEventsComponent { let dates: string[] = null; try { dates = this.eventService.formatDateFilters(this.start, this.end); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts index 1ba1431cbd5..58efc7348e1 100644 --- a/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts +++ b/apps/web/src/app/admin-console/organizations/manage/entity-events.component.ts @@ -113,6 +113,8 @@ export class EntityEventsComponent implements OnInit { this.filterFormGroup.value.start, this.filterFormGroup.value.end, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts index b3c046d1fef..031bd7bf180 100644 --- a/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts +++ b/apps/web/src/app/admin-console/organizations/sponsorships/families-for-enterprise-setup.component.ts @@ -170,6 +170,8 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy { // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["/"]); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { if (this.showNewOrganization) { const dialog = openDeleteOrganizationDialog(this.dialogService, { diff --git a/apps/web/src/app/platform/web-sdk-client-factory.ts b/apps/web/src/app/platform/web-sdk-client-factory.ts index 2ebb2bcc10f..0dd43ecbb92 100644 --- a/apps/web/src/app/platform/web-sdk-client-factory.ts +++ b/apps/web/src/app/platform/web-sdk-client-factory.ts @@ -27,6 +27,8 @@ const supported = (() => { return new WebAssembly.Instance(module) instanceof WebAssembly.Instance; } } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // ignore } diff --git a/apps/web/src/app/tools/send/send-access-file.component.ts b/apps/web/src/app/tools/send/send-access-file.component.ts index 0b2a971bbe8..b55e955f355 100644 --- a/apps/web/src/app/tools/send/send-access-file.component.ts +++ b/apps/web/src/app/tools/send/send-access-file.component.ts @@ -70,6 +70,8 @@ export class SendAccessFileComponent { blobData: decBuf, downloadMethod: "save", }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", 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 88af1ef601b..bc639ff2f61 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 @@ -46,6 +46,8 @@ export class FolderAddEditComponent extends BaseFolderAddEditComponent { dialogService, formBuilder, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions params?.folderId ? (this.folderId = params.folderId) : null; } diff --git a/apps/web/src/connectors/captcha.ts b/apps/web/src/connectors/captcha.ts index 01a14a79a23..aad6eaa3d47 100644 --- a/apps/web/src/connectors/captcha.ts +++ b/apps/web/src/connectors/captcha.ts @@ -5,8 +5,12 @@ import { b64Decode, getQsParam } from "./common"; declare let hcaptcha: any; if (window.location.pathname.includes("mobile")) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha-mobile.scss"); } else { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-require-imports require("./captcha.scss"); } @@ -50,6 +54,8 @@ async function start() { let decodedData: any; try { decodedData = JSON.parse(b64Decode(data, true)); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/duo-redirect.ts b/apps/web/src/connectors/duo-redirect.ts index 0f067b583b9..b5300ff65e7 100644 --- a/apps/web/src/connectors/duo-redirect.ts +++ b/apps/web/src/connectors/duo-redirect.ts @@ -3,6 +3,8 @@ import { getQsParam } from "./common"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./duo-redirect.scss"); const mobileDesktopCallback = "bitwarden://duo-callback"; diff --git a/apps/web/src/connectors/sso.ts b/apps/web/src/connectors/sso.ts index 3ec6a8f7a3d..b48c2b49d72 100644 --- a/apps/web/src/connectors/sso.ts +++ b/apps/web/src/connectors/sso.ts @@ -2,6 +2,8 @@ // @ts-strict-ignore import { getQsParam } from "./common"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./sso.scss"); window.addEventListener("load", () => { diff --git a/apps/web/src/connectors/webauthn-fallback.ts b/apps/web/src/connectors/webauthn-fallback.ts index 971bc5e44a1..6f32bbaecf8 100644 --- a/apps/web/src/connectors/webauthn-fallback.ts +++ b/apps/web/src/connectors/webauthn-fallback.ts @@ -4,6 +4,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; import { TranslationService } from "./translation.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); let parsed = false; @@ -52,6 +54,8 @@ function parseParametersV2() { let dataObj: { data: any; btnText: string } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -103,6 +107,8 @@ function start() { let json: any; try { json = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; diff --git a/apps/web/src/connectors/webauthn.ts b/apps/web/src/connectors/webauthn.ts index 14ba8a280ee..32e6e0ab673 100644 --- a/apps/web/src/connectors/webauthn.ts +++ b/apps/web/src/connectors/webauthn.ts @@ -3,6 +3,8 @@ import { b64Decode, getQsParam } from "./common"; import { buildDataString, parseWebauthnJson } from "./common-webauthn"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports require("./webauthn.scss"); const mobileCallbackUri = "bitwarden://webauthn-callback"; @@ -88,6 +90,8 @@ function parseParametersV2() { } = null; try { dataObj = JSON.parse(b64Decode(getQsParam("data"))); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse data."); return; @@ -116,6 +120,8 @@ function start() { try { obj = parseWebauthnJson(webauthnJson); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { error("Cannot parse webauthn data."); return; diff --git a/apps/web/src/utils/flags.ts b/apps/web/src/utils/flags.ts index dc0103e2436..a762053da35 100644 --- a/apps/web/src/utils/flags.ts +++ b/apps/web/src/utils/flags.ts @@ -7,11 +7,9 @@ import { } from "@bitwarden/common/platform/misc/flags"; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type Flags = {} & SharedFlags; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type DevFlags = {} & SharedDevFlags; export function flagEnabled(flag: keyof Flags): boolean { diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts index 760877ff8bc..ac8ad3112b9 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/device-approvals/device-approvals.component.ts @@ -98,6 +98,8 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy { title: null, message: this.i18nService.t("loginRequestApproved"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { this.toastService.showToast({ variant: "error", diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts index 9ae9b2acec3..c5934067fd7 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/settings/services/sm-porting-api.service.ts @@ -107,6 +107,8 @@ export class SecretsManagerPortingApiService { return secret; }), ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { return null; } diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts index 0775114bd8c..ffe1dda3aed 100644 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ b/libs/angular/src/auth/components/login-v1.component.ts @@ -397,6 +397,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni email, deviceIdentifier, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.showLoginWithDevice = false; } diff --git a/libs/angular/src/vault/components/attachments.component.ts b/libs/angular/src/vault/components/attachments.component.ts index 425b4be2840..1a4c428aae2 100644 --- a/libs/angular/src/vault/components/attachments.component.ts +++ b/libs/angular/src/vault/components/attachments.component.ts @@ -192,6 +192,8 @@ export class AttachmentsComponent implements OnInit { title: null, message: this.i18nService.t("fileSavedToDevice"), }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } @@ -285,6 +287,8 @@ export class AttachmentsComponent implements OnInit { this.i18nService.t("attachmentSaved"), ); this.onReuploadedAttachment.emit(); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts index 724a1507be1..ef9aff736ed 100644 --- a/libs/angular/src/vault/components/view.component.ts +++ b/libs/angular/src/vault/components/view.component.ts @@ -466,6 +466,8 @@ export class ViewComponent implements OnDestroy, OnInit { fileName: attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred")); } 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 5600077c363..8f22f391b13 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 @@ -107,6 +107,8 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, ) { + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.clientType === this.platformUtilsService.getClientType(); } diff --git a/libs/auth/src/angular/login/login.component.ts b/libs/auth/src/angular/login/login.component.ts index 40f85e6d75c..f9aaa5d1e05 100644 --- a/libs/auth/src/angular/login/login.component.ts +++ b/libs/auth/src/angular/login/login.component.ts @@ -484,6 +484,8 @@ export class LoginComponent implements OnInit, OnDestroy { try { const deviceIdentifier = await this.appIdService.getAppId(); this.isKnownDevice = await this.devicesApiService.getKnownDevice(email, deviceIdentifier); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.isKnownDevice = false; } diff --git a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts index 717e80b110d..24b3adacc21 100644 --- a/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts +++ b/libs/auth/src/common/utilities/decode-jwt-token-to-json.utility.ts @@ -18,6 +18,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { try { // Attempt to decode from URL-safe Base64 to UTF-8 decodedPayloadJSON = Utils.fromUrlB64ToUtf8(encodedPayload); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (decodingError) { throw new Error("Cannot decode the token"); } @@ -26,6 +28,8 @@ export function decodeJwtTokenToJson(jwtToken: string): any { // Attempt to parse the JSON payload const decodedToken = JSON.parse(decodedPayloadJSON); return decodedToken; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (jsonError) { throw new Error("Cannot parse the token's payload into JSON"); } diff --git a/libs/common/src/auth/services/device-trust.service.implementation.ts b/libs/common/src/auth/services/device-trust.service.implementation.ts index c40091a66fa..a94c8b6e422 100644 --- a/libs/common/src/auth/services/device-trust.service.implementation.ts +++ b/libs/common/src/auth/services/device-trust.service.implementation.ts @@ -337,6 +337,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); return new SymmetricCryptoKey(userKey) as UserKey; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // If either decryption effort fails, we want to remove the device key this.logService.error("Failed to decrypt using device key. Removing device key."); diff --git a/libs/common/src/auth/services/user-verification/user-verification.service.ts b/libs/common/src/auth/services/user-verification/user-verification.service.ts index 3f0a0adfcb5..4735da32b6b 100644 --- a/libs/common/src/auth/services/user-verification/user-verification.service.ts +++ b/libs/common/src/auth/services/user-verification/user-verification.service.ts @@ -163,6 +163,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti const request = new VerifyOTPRequest(verification.secret); try { await this.userVerificationApiService.postAccountVerifyOTP(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidVerificationCode")); } @@ -221,6 +223,8 @@ export class UserVerificationService implements UserVerificationServiceAbstracti request.masterPasswordHash = serverKeyHash; try { policyOptions = await this.userVerificationApiService.postAccountVerifyPassword(request); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error(this.i18nService.t("invalidMasterPassword")); } diff --git a/libs/common/src/platform/misc/flags.ts b/libs/common/src/platform/misc/flags.ts index 8ed19ce57fc..30531b6799e 100644 --- a/libs/common/src/platform/misc/flags.ts +++ b/libs/common/src/platform/misc/flags.ts @@ -1,14 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedFlags = { sdk?: boolean; prereleaseBuild?: boolean; }; // required to avoid linting errors when there are no flags -// eslint-disable-next-line @typescript-eslint/ban-types export type SharedDevFlags = { noopNotifications: boolean; skipWelcomeOnInstall: boolean; diff --git a/libs/common/src/platform/misc/utils.ts b/libs/common/src/platform/misc/utils.ts index 95d17e6d046..f654897e9e2 100644 --- a/libs/common/src/platform/misc/utils.ts +++ b/libs/common/src/platform/misc/utils.ts @@ -14,6 +14,8 @@ import { KeyService } from "../../../../key-management/src/abstractions/key.serv import { EncryptService } from "../abstractions/encrypt.service"; import { I18nService } from "../abstractions/i18n.service"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-require-imports const nodeURL = typeof self === "undefined" ? require("url") : null; declare global { @@ -610,6 +612,8 @@ export class Utils { } return new URL(uriString); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error } diff --git a/libs/common/src/platform/models/domain/domain-base.spec.ts b/libs/common/src/platform/models/domain/domain-base.spec.ts index 0bdee21e3c1..80a4e5e8606 100644 --- a/libs/common/src/platform/models/domain/domain-base.spec.ts +++ b/libs/common/src/platform/models/domain/domain-base.spec.ts @@ -67,9 +67,13 @@ describe("DomainBase", () => { ); // @ts-expect-error -- encString2 was not decrypted + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: string; plainText: string }; // encString2 was not decrypted, so it's still an EncString + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions decrypted as { encToString: string; encString2: EncString; plainText: string }; }); diff --git a/libs/common/src/platform/models/domain/domain-base.ts b/libs/common/src/platform/models/domain/domain-base.ts index 688cf52d4c0..110a1dc7208 100644 --- a/libs/common/src/platform/models/domain/domain-base.ts +++ b/libs/common/src/platform/models/domain/domain-base.ts @@ -8,7 +8,7 @@ import { EncryptService } from "../../abstractions/encrypt.service"; import { EncString } from "./enc-string"; import { SymmetricCryptoKey } from "./symmetric-crypto-key"; -// eslint-disable-next-line @typescript-eslint/ban-types +// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type type EncStringKeys = ConditionalKeys, EncString>; export type DecryptedObject< TEncryptedObject, diff --git a/libs/common/src/platform/models/domain/enc-string.ts b/libs/common/src/platform/models/domain/enc-string.ts index b8e0006942a..f148664a4f9 100644 --- a/libs/common/src/platform/models/domain/enc-string.ts +++ b/libs/common/src/platform/models/domain/enc-string.ts @@ -125,6 +125,8 @@ export class EncString implements Encrypted { try { encType = parseInt(headerPieces[0], null); encPieces = headerPieces[1].split("|"); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return { encType: NaN, encPieces: [] }; } @@ -186,6 +188,8 @@ export class EncString implements Encrypted { key, decryptTrace == null ? context : `${decryptTrace}${context || ""}`, ); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } @@ -203,6 +207,8 @@ export class EncString implements Encrypted { } this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.decryptedValue = DECRYPT_ERROR; } diff --git a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts index 53ae94c30b2..84bcaeec608 100644 --- a/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/18-move-autofill-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const AutofillOverlayVisibility = { Off: 0, OnButtonClick: 1, diff --git a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts index 04b4a50e0ed..9397191f54b 100644 --- a/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts +++ b/libs/common/src/state-migrations/migrations/25-move-clear-clipboard-to-autofill-settings-state-provider.ts @@ -3,6 +3,8 @@ import { StateDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const ClearClipboardDelay = { Never: null as null, TenSeconds: 10, diff --git a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts index b465a69131a..71e5c531d50 100644 --- a/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts +++ b/libs/common/src/state-migrations/migrations/34-move-domain-settings-to-state-providers.ts @@ -3,6 +3,8 @@ import { KeyDefinitionLike, MigrationHelper } from "../migration-helper"; import { Migrator } from "../migrator"; +// FIXME: Remove when updating file. Eslint update +// eslint-disable-next-line @typescript-eslint/no-unused-vars const UriMatchStrategy = { Domain: 0, Host: 1, diff --git a/libs/common/src/tools/send/models/domain/send.ts b/libs/common/src/tools/send/models/domain/send.ts index 43115b65937..c2390d439e7 100644 --- a/libs/common/src/tools/send/models/domain/send.ts +++ b/libs/common/src/tools/send/models/domain/send.ts @@ -81,6 +81,8 @@ export class Send extends Domain { const sendKeyEncryptionKey = await keyService.getUserKey(); model.key = await encryptService.decryptToBytes(this.key, sendKeyEncryptionKey); model.cryptoKey = await keyService.makeSendKey(model.key); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts index 8ffe4749568..5775bc7f55e 100644 --- a/libs/common/src/vault/icon/build-cipher-icon.ts +++ b/libs/common/src/vault/icon/build-cipher-icon.ts @@ -53,6 +53,8 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show try { image = `${iconsServerUrl}/${Utils.getHostname(hostnameUri)}/icon.png`; fallbackImage = "images/bwi-globe.png"; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Ignore error since the fallback icon will be shown if image is null. } diff --git a/libs/common/src/vault/models/domain/attachment.ts b/libs/common/src/vault/models/domain/attachment.ts index 2b893e33f49..4eee0307746 100644 --- a/libs/common/src/vault/models/domain/attachment.ts +++ b/libs/common/src/vault/models/domain/attachment.ts @@ -69,6 +69,8 @@ export class Attachment extends Domain { const encryptService = Utils.getContainerService().getEncryptService(); const decValue = await encryptService.decryptToBytes(this.key, encKey); return new SymmetricCryptoKey(decValue); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // TODO: error? } diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts index c5de01293ee..20dbd23065c 100644 --- a/libs/common/src/vault/models/view/cipher.view.ts +++ b/libs/common/src/vault/models/view/cipher.view.ts @@ -155,6 +155,8 @@ export class CipherView implements View, InitializerMetadata { return null; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars const item = this.item; return this.item[linkedFieldOption.propertyKey as keyof typeof item]; } diff --git a/libs/common/src/vault/models/view/login-uri.view.ts b/libs/common/src/vault/models/view/login-uri.view.ts index a6b7a0c0a22..315adb87c75 100644 --- a/libs/common/src/vault/models/view/login-uri.view.ts +++ b/libs/common/src/vault/models/view/login-uri.view.ts @@ -142,6 +142,8 @@ export class LoginUriView implements View { try { const regex = new RegExp(this.uri, "i"); return regex.test(targetUri); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { // Invalid regex return false; diff --git a/libs/components/src/a11y/a11y-grid.directive.ts b/libs/components/src/a11y/a11y-grid.directive.ts index c8e9d3d2fe3..ef7ba68b65c 100644 --- a/libs/components/src/a11y/a11y-grid.directive.ts +++ b/libs/components/src/a11y/a11y-grid.directive.ts @@ -86,6 +86,8 @@ export class A11yGridDirective implements AfterViewInit { }); this.getActiveCellContent().tabIndex = 0; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // eslint-disable-next-line no-console console.error("Unable to initialize grid"); diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 58d93ddd3a4..d615bfe0582 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -70,6 +70,8 @@ export class NavGroupComponent extends NavBaseComponent implements AfterContentI setOpen(isOpen: boolean) { this.open = isOpen; this.openChange.emit(this.open); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions this.open && this.parentNavGroup?.setOpen(this.open); } diff --git a/libs/importer/src/services/import.service.spec.ts b/libs/importer/src/services/import.service.spec.ts index 8b497beac93..e8f63ef8705 100644 --- a/libs/importer/src/services/import.service.spec.ts +++ b/libs/importer/src/services/import.service.spec.ts @@ -267,7 +267,7 @@ describe("ImportService", () => { function createCipher(options: Partial = {}) { const cipher = new CipherView(); - cipher.name; + cipher.name = options.name; cipher.type = options.type; cipher.folderId = options.folderId; cipher.collectionIds = options.collectionIds; diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index 5425dbc53f9..98474b4babf 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -683,6 +683,8 @@ export class DefaultKeyService implements KeyServiceAbstraction { // failed to decrypt return false; } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { return false; } diff --git a/libs/shared/jest.config.angular.js b/libs/shared/jest.config.angular.js index de703e27220..311318e46a4 100644 --- a/libs/shared/jest.config.angular.js +++ b/libs/shared/jest.config.angular.js @@ -1,5 +1,5 @@ /* eslint-env node */ -/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable @typescript-eslint/no-require-imports */ const { defaultTransformerOptions } = require("jest-preset-angular/presets"); /** @type {import('jest').Config} */ diff --git a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts index f63a974d487..62d171b8436 100644 --- a/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts +++ b/libs/vault/src/cipher-form/components/custom-fields/add-edit-custom-field-dialog/add-edit-custom-field-dialog.component.spec.ts @@ -38,6 +38,8 @@ describe("AddEditCustomFieldDialogComponent", () => { fixture = TestBed.createComponent(AddEditCustomFieldDialogComponent); component = fixture.componentInstance; + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-expressions fixture.detectChanges; }); diff --git a/libs/vault/src/components/download-attachment/download-attachment.component.ts b/libs/vault/src/components/download-attachment/download-attachment.component.ts index b22ced3d899..a47cdc8d1d5 100644 --- a/libs/vault/src/components/download-attachment/download-attachment.component.ts +++ b/libs/vault/src/components/download-attachment/download-attachment.component.ts @@ -98,6 +98,8 @@ export class DownloadAttachmentComponent { fileName: this.attachment.fileName, blobData: decBuf, }); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { this.toastService.showToast({ variant: "error", diff --git a/package-lock.json b/package-lock.json index 3d6ff011724..5366b26861b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -120,8 +120,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", @@ -10082,88 +10082,94 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.1.tgz", - "integrity": "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/type-utils": "7.16.1", - "@typescript-eslint/utils": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.1.tgz", - "integrity": "sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/utils": "7.16.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.1.tgz", - "integrity": "sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/experimental-utils": { @@ -10316,46 +10322,42 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.1.tgz", - "integrity": "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.1", - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/typescript-estree": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.1.tgz", - "integrity": "sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10363,13 +10365,13 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.1.tgz", - "integrity": "sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10377,32 +10379,43 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.1.tgz", - "integrity": "sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "@typescript-eslint/visitor-keys": "7.16.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, "node_modules/@typescript-eslint/utils": { @@ -10513,23 +10526,36 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.1.tgz", - "integrity": "sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.1", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", diff --git a/package.json b/package.json index e6c476d2dfd..1090d3efb33 100644 --- a/package.json +++ b/package.json @@ -80,8 +80,8 @@ "@types/proper-lockfile": "4.1.4", "@types/retry": "0.12.5", "@types/zxcvbn": "4.4.5", - "@typescript-eslint/eslint-plugin": "7.16.1", - "@typescript-eslint/parser": "7.16.1", + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", "@webcomponents/custom-elements": "1.6.0", "@yao-pkg/pkg": "5.16.1", "autoprefixer": "10.4.20", diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 80f70e47b09..941a612a30c 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -14,6 +14,7 @@ "declarationDir": "dist/types", "outDir": "dist", "baseUrl": ".", + "allowJs": true, "paths": { "@bitwarden/admin-console": ["./libs/admin-console/src"], "@bitwarden/angular/*": ["./libs/angular/src/*"], From 63a9c69f5a904bde34315fdff151e21eff59480f Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Tue, 14 Jan 2025 12:15:23 -0500 Subject: [PATCH 03/28] fix seat count to include invited and accepted members (#12848) --- .../components/member-dialog/member-dialog.component.ts | 6 +++--- .../organizations/members/members.component.ts | 7 ++++++- .../member-access-report/member-access-report.component.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) 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 514e7701e4b..4f43dacad32 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 @@ -68,7 +68,7 @@ export interface MemberDialogParams { usesKeyConnector: boolean; isOnSecretsManagerStandalone: boolean; initialTab?: MemberDialogTab; - numConfirmedMembers: number; + numSeatsUsed: number; managedByOrganization?: boolean; } @@ -263,7 +263,7 @@ export class MemberDialogComponent implements OnDestroy { }); this.remainingSeats$ = this.organization$.pipe( - map((organization) => organization.seats - this.params.numConfirmedMembers), + map((organization) => organization.seats - this.params.numSeatsUsed), ); } @@ -458,7 +458,7 @@ export class MemberDialogComponent implements OnDestroy { } if ( organization.hasReseller && - this.params.numConfirmedMembers + emails.length > organization.seats + this.params.numSeatsUsed + emails.length > organization.seats ) { this.formGroup.controls.emails.setErrors({ tooManyEmails: { message: this.i18nService.t("seatLimitReachedContactYourProvider") }, diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.ts b/apps/web/src/app/admin-console/organizations/members/members.component.ts index 3d10a4a07b3..703f187b223 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.ts +++ b/apps/web/src/app/admin-console/organizations/members/members.component.ts @@ -510,6 +510,11 @@ export class MembersComponent extends BaseMembersComponent return; } + const numSeatsUsed = + this.dataSource.confirmedUserCount + + this.dataSource.invitedUserCount + + this.dataSource.acceptedUserCount; + const dialog = openUserAddEditDialog(this.dialogService, { data: { name: this.userNamePipe.transform(user), @@ -519,7 +524,7 @@ export class MembersComponent extends BaseMembersComponent usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: initialTab, - numConfirmedMembers: this.dataSource.confirmedUserCount, + numSeatsUsed, managedByOrganization: user?.managedByOrganization, }, }); diff --git a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts index 6aaaf1a4066..321aae165c5 100644 --- a/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/reports/member-access-report/member-access-report.component.ts @@ -103,7 +103,7 @@ export class MemberAccessReportComponent implements OnInit { usesKeyConnector: user?.usesKeyConnector, isOnSecretsManagerStandalone: this.orgIsOnSecretsManagerStandalone, initialTab: MemberDialogTab.Role, - numConfirmedMembers: this.dataSource.data.length, + numSeatsUsed: this.dataSource.data.length, }, }); From 318a3ac6a9718e8cde7455095fb441fd7344031a Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Tue, 14 Jan 2025 18:29:52 +0100 Subject: [PATCH 04/28] [PM-17038] Fix biometrics autoprompt in firefox and chrome (#12853) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts * Add process reload logging * Fix autoprompt not working when no process reload happened * Fix instant reprompt on firefox lock * Fix biometrics enabling error on chrome * Update apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> * FIx build & linting --------- Co-authored-by: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> --- .../biometrics/foreground-browser-biometrics.ts | 3 ++- apps/browser/src/popup/app.component.ts | 6 ++++-- .../services/default-process-reload.service.ts | 11 +++++++++++ .../src/angular/lock/components/lock.component.ts | 8 +++++--- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts index 43cc25e4dad..d248a630cc6 100644 --- a/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts +++ b/apps/browser/src/key-management/biometrics/foreground-browser-biometrics.ts @@ -1,3 +1,4 @@ +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { UserId } from "@bitwarden/common/types/guid"; import { UserKey } from "@bitwarden/common/types/key"; import { BiometricsCommands, BiometricsService, BiometricsStatus } from "@bitwarden/key-management"; @@ -34,7 +35,7 @@ export class ForegroundBrowserBiometricsService extends BiometricsService { if (!response.result) { return null; } - return response.result; + return SymmetricCryptoKey.fromString(response.result.keyB64) as UserKey; } async getBiometricsStatusForUser(id: UserId): Promise { diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index e8a660620a9..7b6e402a90f 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -21,7 +21,7 @@ import { ToastOptions, ToastService, } from "@bitwarden/components"; -import { BiometricStateService } from "@bitwarden/key-management"; +import { BiometricsService, BiometricStateService } from "@bitwarden/key-management"; import { PopupCompactModeService } from "../platform/popup/layout/popup-compact-mode.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; @@ -66,6 +66,7 @@ export class AppComponent implements OnInit, OnDestroy { private accountService: AccountService, private animationControlService: AnimationControlService, private biometricStateService: BiometricStateService, + private biometricsService: BiometricsService, ) {} async ngOnInit() { @@ -102,7 +103,7 @@ export class AppComponent implements OnInit, OnDestroy { this.messageListener.allMessages$ .pipe( - tap((msg: any) => { + tap(async (msg: any) => { if (msg.command === "doneLoggingOut") { // TODO: PM-8544 - why do we call logout in the popup after receiving the doneLoggingOut message? Hasn't this already completeted logout? this.authService.logOut(async () => { @@ -119,6 +120,7 @@ export class AppComponent implements OnInit, OnDestroy { msg.command === "locked" && (msg.userId == null || msg.userId == this.activeUserId) ) { + await this.biometricsService.setShouldAutopromptNow(false); // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // eslint-disable-next-line @typescript-eslint/no-floating-promises this.router.navigate(["lock"]); diff --git a/libs/common/src/key-management/services/default-process-reload.service.ts b/libs/common/src/key-management/services/default-process-reload.service.ts index 3f099b77328..9f97e0a94c1 100644 --- a/libs/common/src/key-management/services/default-process-reload.service.ts +++ b/libs/common/src/key-management/services/default-process-reload.service.ts @@ -39,6 +39,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract let status = await firstValueFrom(authService.authStatusFor$(userId as UserId)); status = await authService.getAuthStatus(userId); if (status === AuthenticationStatus.Unlocked) { + this.logService.info( + "[Process Reload Service] User unlocked, preventing process reload", + ); return; } } @@ -55,6 +58,9 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract if (userId != null) { const ephemeralPin = await this.pinService.getPinKeyEncryptedUserKeyEphemeral(userId); if (ephemeralPin != null) { + this.logService.info( + "[Process Reload Service] Ephemeral pin active, preventing process reload", + ); return; } } @@ -97,7 +103,12 @@ export class DefaultProcessReloadService implements ProcessReloadServiceAbstract await this.reloadCallback(); } return; + } else { + this.logService.info( + "[Process Reload Service] Desktop ipc fingerprint validated, preventing process reload", + ); } + if (this.reloadInterval == null) { this.reloadInterval = setInterval(async () => await this.executeProcessReload(), 1000); } diff --git a/libs/key-management/src/angular/lock/components/lock.component.ts b/libs/key-management/src/angular/lock/components/lock.component.ts index 23f1a7a4330..6c912b0eaae 100644 --- a/libs/key-management/src/angular/lock/components/lock.component.ts +++ b/libs/key-management/src/angular/lock/components/lock.component.ts @@ -307,10 +307,12 @@ export class LockComponent implements OnInit, OnDestroy { (await this.biometricService.getShouldAutopromptNow()) ) { await this.biometricService.setShouldAutopromptNow(false); + + const lastProcessReload = await this.biometricStateService.getLastProcessReload(); if ( - (await this.biometricStateService.getLastProcessReload()) == null || - Date.now() - (await this.biometricStateService.getLastProcessReload()).getTime() > - AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY + lastProcessReload == null || + isNaN(lastProcessReload.getTime()) || + Date.now() - lastProcessReload.getTime() > AUTOPROMPT_BIOMETRICS_PROCESS_RELOAD_DELAY ) { await this.unlockViaBiometrics(); } From eedf9af2e79e8e53844f4a4dd35dbaf11f07e071 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Tue, 14 Jan 2025 18:39:43 +0100 Subject: [PATCH 05/28] in desktop "Allow browser integration" button fails on dev environment, but should pass fine. (#12797) Co-authored-by: aj-bw <81774843+aj-bw@users.noreply.github.com> --- apps/desktop/src/app/accounts/settings.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index 19748e797bb..f3440975cf2 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -650,7 +650,7 @@ export class SettingsComponent implements OnInit, OnDestroy { const skipSupportedPlatformCheck = ipc.platform.allowBrowserintegrationOverride || ipc.platform.isDev; - if (skipSupportedPlatformCheck) { + if (!skipSupportedPlatformCheck) { if ( ipc.platform.deviceType === DeviceType.MacOsDesktop && !this.platformUtilsService.isMacAppStore() From f44b36bdf62ff192ea3da59654f24c08bcd0f0bc Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 14 Jan 2025 18:48:50 +0100 Subject: [PATCH 06/28] Fix linting conflicts after merge (#12864) --- .../src/auth/popup/settings/account-security.component.ts | 2 ++ .../src/vault/popup/services/vault-popup-autofill.service.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.ts b/apps/browser/src/auth/popup/settings/account-security.component.ts index 1a64d860e45..7e094fe508b 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.ts +++ b/apps/browser/src/auth/popup/settings/account-security.component.ts @@ -514,6 +514,8 @@ export class AccountSecurityComponent implements OnInit, OnDestroy { try { const userKey = await this.biometricsService.unlockWithBiometricsForUser(userId); result = await this.keyService.validateUserKey(userKey, userId); + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { result = false; } diff --git a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts index c0ac9c91e18..ff282d7a6d0 100644 --- a/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts +++ b/apps/browser/src/vault/popup/services/vault-popup-autofill.service.ts @@ -127,6 +127,8 @@ export class VaultPopupAutofillService { [currentTabHostname as string]: { bannerIsDismissed: true }, }); } + // FIXME: Remove when updating file. Eslint update + // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (e) { throw new Error( "There was a problem dismissing the blocked interaction URI notification banner", From 8621e8aa6a2b6636a95359e13a32c8fdc1b19d73 Mon Sep 17 00:00:00 2001 From: Graham Walker Date: Tue, 14 Jan 2025 13:16:26 -0600 Subject: [PATCH 07/28] PM-16170 remove methods using deprecated send endpoints (#12751) --- .../services/send-api.service.abstraction.ts | 5 --- .../tools/send/services/send-api.service.ts | 45 +------------------ 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/libs/common/src/tools/send/services/send-api.service.abstraction.ts b/libs/common/src/tools/send/services/send-api.service.abstraction.ts index a6427824a64..570f3e746a0 100644 --- a/libs/common/src/tools/send/services/send-api.service.abstraction.ts +++ b/libs/common/src/tools/send/services/send-api.service.abstraction.ts @@ -22,11 +22,6 @@ export abstract class SendApiService { postSend: (request: SendRequest) => Promise; postFileTypeSend: (request: SendRequest) => Promise; postSendFile: (sendId: string, fileId: string, data: FormData) => Promise; - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - postSendFileLegacy: (data: FormData) => Promise; putSend: (id: string, request: SendRequest) => Promise; putSendRemovePassword: (id: string) => Promise; deleteSend: (id: string) => Promise; diff --git a/libs/common/src/tools/send/services/send-api.service.ts b/libs/common/src/tools/send/services/send-api.service.ts index ff71408bce3..f709553646f 100644 --- a/libs/common/src/tools/send/services/send-api.service.ts +++ b/libs/common/src/tools/send/services/send-api.service.ts @@ -5,7 +5,6 @@ import { FileUploadApiMethods, FileUploadService, } from "../../../platform/abstractions/file-upload/file-upload.service"; -import { Utils } from "../../../platform/misc/utils"; import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer"; import { SendType } from "../enums/send-type"; import { SendData } from "../models/data/send.data"; @@ -106,15 +105,6 @@ export class SendApiService implements SendApiServiceAbstraction { return this.apiService.send("POST", "/sends/" + sendId + "/file/" + fileId, data, true, false); } - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async postSendFileLegacy(data: FormData): Promise { - const r = await this.apiService.send("POST", "/sends/file", data, true, true); - return new SendResponse(r); - } - async putSend(id: string, request: SendRequest): Promise { const r = await this.apiService.send("PUT", "/sends/" + id, request, true, true); return new SendResponse(r); @@ -173,9 +163,7 @@ export class SendApiService implements SendApiServiceAbstraction { this.generateMethods(uploadDataResponse, response), ); } catch (e) { - if (e instanceof ErrorResponse && (e as ErrorResponse).statusCode === 404) { - response = await this.legacyServerSendFileUpload(sendData, request); - } else if (e instanceof ErrorResponse) { + if (e instanceof ErrorResponse) { throw new Error((e as ErrorResponse).getSingleMessage()); } else { throw e; @@ -219,35 +207,4 @@ export class SendApiService implements SendApiServiceAbstraction { return this.deleteSend(sendId); }; } - - /** - * @deprecated Mar 25 2021: This method has been deprecated in favor of direct uploads. - * This method still exists for backward compatibility with old server versions. - */ - async legacyServerSendFileUpload( - sendData: [Send, EncArrayBuffer], - request: SendRequest, - ): Promise { - const fd = new FormData(); - try { - const blob = new Blob([sendData[1].buffer], { type: "application/octet-stream" }); - fd.append("model", JSON.stringify(request)); - fd.append("data", blob, sendData[0].file.fileName.encryptedString); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append("model", JSON.stringify(request)); - fd.append( - "data", - Buffer.from(sendData[1].buffer) as any, - { - filepath: sendData[0].file.fileName.encryptedString, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - return await this.postSendFileLegacy(fd); - } } From 27e8a1f27c4477ad645259de90db4e1beb3b9eeb Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 14 Jan 2025 11:23:51 -0800 Subject: [PATCH 08/28] fix(auth) [PM-17047] Change clientType expression to assignment (#12865) Fixes a bug where we had an expression (`===`) that should be an assignment (`=`). Feature Flag: UnauthenticatedExtensionUIRefresh --- .../login-decryption-options.component.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 8f22f391b13..a3f5e062e4f 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 @@ -107,9 +107,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { private userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, private validationService: ValidationService, ) { - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - this.clientType === this.platformUtilsService.getClientType(); + this.clientType = this.platformUtilsService.getClientType(); } async ngOnInit() { From f2b6f05d3f007500a843ce1c737b1ceda9379606 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Tue, 14 Jan 2025 13:58:57 -0600 Subject: [PATCH 09/28] PM-16891 Applications at risk dialog (#12843) * Org at risk members click on the card * Fixing at risk member counts * At risk member text modification * Changing ok button to close * PM-16891 added a dialog for at risk apps * PM-16891 fixing order of imports (linting error) * PM-16891 updated PR comments --------- Co-authored-by: Tom Co-authored-by: Tom <144813356+ttalty@users.noreply.github.com> --- apps/web/src/locales/en/messages.json | 33 +++++++++++++++++++ .../risk-insights/models/password-health.ts | 9 +++++ .../services/risk-insights-report.service.ts | 25 ++++++++++++++ .../all-applications.component.html | 3 +- .../all-applications.component.ts | 7 ++++ .../org-at-risk-apps-dialog.component.html | 25 ++++++++++++++ .../org-at-risk-apps-dialog.component.ts | 24 ++++++++++++++ 7 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html create mode 100644 bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 2779c0470e7..eacba623ecd 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -122,6 +122,39 @@ } } }, + "atRiskApplicationsWithCount": { + "message": "At-risk applications ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, + "atRiskMembersDescription": { + "message": "These members are logging into applications with weak, exposed, or reused passwords." + }, + "atRiskApplicationsDescription": { + "message": "These applications have weak, exposed, or reused passwords." + }, + "atRiskMembersDescriptionWithApp": { + "message": "These members are logging into $APPNAME$ with weak, exposed, or reused passwords.", + "placeholders": { + "appname": { + "content": "$1", + "example": "Salesforce" + } + } + }, + "atRiskMembersWithCount": { + "message": "At-risk members ($COUNT$)", + "placeholders": { + "count": { + "content": "$1", + "example": "3" + } + } + }, "atRiskMembersDescription": { "message": "These members are logging into applications with weak, exposed, or reused passwords." }, diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index ad87f319e73..94dad65fdc9 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -100,3 +100,12 @@ export type AtRiskMemberDetail = { email: string; atRiskPasswordCount: number; }; + +/* + * A list of applications and the count of + * at risk passwords for each application + */ +export type AtRiskApplicationDetail = { + applicationName: string; + atRiskPasswordCount: number; +}; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts index d97550b5887..c3bcc59eca5 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/risk-insights-report.service.ts @@ -13,6 +13,7 @@ import { ApplicationHealthReportDetail, ApplicationHealthReportSummary, AtRiskMemberDetail, + AtRiskApplicationDetail, CipherHealthReportDetail, CipherHealthReportUriDetail, ExposedPasswordDetail, @@ -114,6 +115,30 @@ export class RiskInsightsReportService { })); } + generateAtRiskApplicationList( + cipherHealthReportDetails: ApplicationHealthReportDetail[], + ): AtRiskApplicationDetail[] { + const appsRiskMap = new Map(); + + cipherHealthReportDetails + .filter((app) => app.atRiskPasswordCount > 0) + .forEach((app) => { + if (appsRiskMap.has(app.applicationName)) { + appsRiskMap.set( + app.applicationName, + appsRiskMap.get(app.applicationName) + app.atRiskPasswordCount, + ); + } else { + appsRiskMap.set(app.applicationName, app.atRiskPasswordCount); + } + }); + + return Array.from(appsRiskMap.entries()).map(([applicationName, atRiskPasswordCount]) => ({ + applicationName, + atRiskPasswordCount, + })); + } + /** * Gets the summary from the application health report. Returns total members and applications as well * as the total at risk members and at risk applications diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index 0493f7e44b8..e17ac078687 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -35,10 +35,11 @@

{{ "allApplications" | i18n }}

> diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index 00708de282f..5fb12fed090 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -32,6 +32,7 @@ import { SharedModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; import { openAppAtRiskMembersDialog } from "./app-at-risk-members-dialog.component"; +import { OrgAtRiskAppsDialogComponent } from "./org-at-risk-apps-dialog.component"; import { OrgAtRiskMembersDialogComponent } from "./org-at-risk-members-dialog.component"; import { ApplicationsLoadingComponent } from "./risk-insights-loading.component"; @@ -154,6 +155,12 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; + showOrgAtRiskApps = async () => { + this.dialogService.open(OrgAtRiskAppsDialogComponent, { + data: this.reportService.generateAtRiskApplicationList(this.dataSource.data), + }); + }; + onCheckboxChange(id: number, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html new file mode 100644 index 00000000000..298011b2157 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.html @@ -0,0 +1,25 @@ + + + {{ "atRiskApplicationsWithCount" | i18n: atRiskApps.length }} + + +
+ {{ "atRiskApplicationsDescription" | i18n }} +
+
{{ "application" | i18n }}
+
{{ "atRiskPasswords" | i18n }}
+
+ +
+
{{ app.applicationName }}
+
{{ app.atRiskPasswordCount }}
+
+
+
+
+ + + +
diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts new file mode 100644 index 00000000000..0ae00f60874 --- /dev/null +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/org-at-risk-apps-dialog.component.ts @@ -0,0 +1,24 @@ +import { DIALOG_DATA } from "@angular/cdk/dialog"; +import { CommonModule } from "@angular/common"; +import { Component, Inject } from "@angular/core"; + +import { JslibModule } from "@bitwarden/angular/jslib.module"; +import { AtRiskApplicationDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; + +export const openOrgAtRiskMembersDialog = ( + dialogService: DialogService, + dialogConfig: AtRiskApplicationDetail[], +) => + dialogService.open(OrgAtRiskAppsDialogComponent, { + data: dialogConfig, + }); + +@Component({ + standalone: true, + templateUrl: "./org-at-risk-apps-dialog.component.html", + imports: [ButtonModule, CommonModule, DialogModule, JslibModule, TypographyModule], +}) +export class OrgAtRiskAppsDialogComponent { + constructor(@Inject(DIALOG_DATA) protected atRiskApps: AtRiskApplicationDetail[]) {} +} From 39a5addddc4a1a1d5d0696113663e8af1abd013a Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 14 Jan 2025 20:07:32 +0000 Subject: [PATCH 10/28] Bumped Desktop client to 2025.1.2 --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b8541aad2ec..8f6c6525a39 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.1", + "version": "2025.1.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d8705487d86..d878e1af2aa 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 95490ee34dd..08bdd745063 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.1", + "version": "2025.1.2", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 5366b26861b..6b8d35c6d43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -231,7 +231,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.1", + "version": "2025.1.2", "hasInstallScript": true, "license": "GPL-3.0" }, @@ -30884,6 +30884,7 @@ "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=16" }, From 04566488dc827b95cb0e058b9c107165f2dcedac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Tue, 14 Jan 2025 15:18:14 -0500 Subject: [PATCH 11/28] add close button to passoword history dialog (#12790) --- ...redential-generator-history-dialog.component.html | 6 ++++++ .../credential-generator-history-dialog.component.ts | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html index b07eb62ae98..ad629601c34 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.html +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.html @@ -14,5 +14,11 @@ > {{ "clearHistory" | i18n }} + +
diff --git a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts index cec818b1cd6..7bcffd92399 100644 --- a/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts +++ b/libs/tools/generator/components/src/credential-generator-history-dialog.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { DialogRef } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; @@ -34,6 +35,7 @@ export class CredentialGeneratorHistoryDialogComponent { private accountService: AccountService, private history: GeneratorHistoryService, private dialogService: DialogService, + private dialogRef: DialogRef, ) { this.accountService.activeAccount$ .pipe( @@ -52,7 +54,13 @@ export class CredentialGeneratorHistoryDialogComponent { .subscribe(this.hasHistory$); } - clear = async () => { + /** closes the dialog */ + protected close() { + this.dialogRef.close(); + } + + /** Launches clear history flow */ + protected async clear() { const confirmed = await this.dialogService.openSimpleDialog({ title: { key: "clearGeneratorHistoryTitle" }, content: { key: "cleargGeneratorHistoryDescription" }, @@ -64,5 +72,5 @@ export class CredentialGeneratorHistoryDialogComponent { if (confirmed) { await this.history.clear(await firstValueFrom(this.userId$)); } - }; + } } From 553d20f7a7aabf2ddf357efb1c4458a1071cad45 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:42:49 -0500 Subject: [PATCH 12/28] [deps] AC: Update bufferutil to v4.0.9 (#12700) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jimmy Vo --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6b8d35c6d43..87e0354f7ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", @@ -12728,9 +12728,9 @@ "license": "MIT" }, "node_modules/bufferutil": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz", - "integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1090d3efb33..641a49c4d04 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,7 @@ "bootstrap": "4.6.0", "braintree-web-drop-in": "1.43.0", "buffer": "6.0.3", - "bufferutil": "4.0.8", + "bufferutil": "4.0.9", "chalk": "4.1.2", "commander": "11.1.0", "core-js": "3.39.0", From 5fb383d7dc3b0b186e83533f8a3c44bf84c6ccd5 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Tue, 14 Jan 2025 16:10:57 -0600 Subject: [PATCH 13/28] [PM-16248] add notification refresh feature flag (#12859) * add notification refresh feature flag * update order --------- Co-authored-by: Evan Bassler --- libs/common/src/enums/feature-flag.enum.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index feffe2ca442..dde31acb9e3 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -13,6 +13,7 @@ export enum FeatureFlag { InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", InlineMenuTotp = "inline-menu-totp", NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements", + NotificationRefresh = "notification-refresh", UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", BrowserFilelessImport = "browser-fileless-import", @@ -70,6 +71,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.InlineMenuTotp]: FALSE, [FeatureFlag.NotificationBarAddLoginImprovements]: FALSE, + [FeatureFlag.NotificationRefresh]: FALSE, [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.BrowserFilelessImport]: FALSE, From 6f018e1b2e74f0bdf003a2187bccbee14ec4a7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Tue, 14 Jan 2025 22:11:29 +0000 Subject: [PATCH 14/28] Fix claimed domains page for manage SSO users by replacing policies server check with policy service (#12863) --- .../domain-verification.component.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts index 2a2ae73227a..1cbe57a7082 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/organizations/manage/domain-verification/domain-verification.component.ts @@ -16,7 +16,7 @@ import { import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction"; import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction"; import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; +import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyType } from "@bitwarden/common/admin-console/enums"; import { HttpStatusCode } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; @@ -54,7 +54,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { private validationService: ValidationService, private toastService: ToastService, private configService: ConfigService, - private policyApiService: PolicyApiServiceAbstraction, + private policyService: PolicyService, ) { this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$( FeatureFlag.AccountDeprovisioning, @@ -83,9 +83,14 @@ export class DomainVerificationComponent implements OnInit, OnDestroy { await this.orgDomainApiService.getAllByOrgId(this.organizationId); if (await this.configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning)) { - const singleOrgPolicy = await this.policyApiService.getPolicy( - this.organizationId, - PolicyType.SingleOrg, + const singleOrgPolicy = await firstValueFrom( + this.policyService.policies$.pipe( + map((policies) => + policies.find( + (p) => p.type === PolicyType.SingleOrg && p.organizationId === this.organizationId, + ), + ), + ), ); this.singleOrgPolicyEnabled = singleOrgPolicy?.enabled ?? false; } From 55e4b5ee09a8bfce868f614eca27ddc3f32ef74d Mon Sep 17 00:00:00 2001 From: Addison Beck Date: Wed, 15 Jan 2025 05:41:44 -0500 Subject: [PATCH 15/28] fix: Use `WeakMap` in `DerivedStateProvider` to separate user state caches (#12866) Bug fix for PM-15914 where switching users would incorrectly share cached derived states. The `DerivedStateProvider` now uses a `WeakMap` to maintain separate caches for each user's state `Observable`. - Modifies `DefaultDerivedStateProvider` to use `WeakMap` for caching - Each user's state `Observable` gets its own definition cache - Added test to verify correct behavior during user switching - Allows proper garbage collection of unused state caches This fixes issues where: - Users would see other users' cached states after switching accounts - Derived states weren't properly isolated between users - Cache keys didn't distinguish between different user states --- .../default-derived-state.provider.ts | 19 +++++++++++--- .../default-derived-state.spec.ts | 26 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts index 3c8c39e21e8..61f36fa0b75 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.provider.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.provider.ts @@ -8,7 +8,14 @@ import { DerivedStateProvider } from "../derived-state.provider"; import { DefaultDerivedState } from "./default-derived-state"; export class DefaultDerivedStateProvider implements DerivedStateProvider { - private cache: Record> = {}; + /** + * The cache uses a WeakMap to maintain separate derived states per user. + * Each user's state Observable acts as a unique key, without needing to + * pass around `userId`. Also, when a user's state Observable is cleaned up + * (like during an account swap) their cache is automatically garbage + * collected. + */ + private cache = new WeakMap, Record>>(); constructor() {} @@ -17,8 +24,14 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { deriveDefinition: DeriveDefinition, dependencies: TDeps, ): DerivedState { + let stateCache = this.cache.get(parentState$); + if (!stateCache) { + stateCache = {}; + this.cache.set(parentState$, stateCache); + } + const cacheKey = deriveDefinition.buildCacheKey(); - const existingDerivedState = this.cache[cacheKey]; + const existingDerivedState = stateCache[cacheKey]; if (existingDerivedState != null) { // I have to cast out of the unknown generic but this should be safe if rules // around domain token are made @@ -26,7 +39,7 @@ export class DefaultDerivedStateProvider implements DerivedStateProvider { } const newDerivedState = this.buildDerivedState(parentState$, deriveDefinition, dependencies); - this.cache[cacheKey] = newDerivedState; + stateCache[cacheKey] = newDerivedState; return newDerivedState; } diff --git a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts index 7e8d76bd203..6fcc1c408cb 100644 --- a/libs/common/src/platform/state/implementations/default-derived-state.spec.ts +++ b/libs/common/src/platform/state/implementations/default-derived-state.spec.ts @@ -9,6 +9,7 @@ import { DeriveDefinition } from "../derive-definition"; import { StateDefinition } from "../state-definition"; import { DefaultDerivedState } from "./default-derived-state"; +import { DefaultDerivedStateProvider } from "./default-derived-state.provider"; let callCount = 0; const cleanupDelayMs = 10; @@ -182,4 +183,29 @@ describe("DefaultDerivedState", () => { expect(await firstValueFrom(observable)).toEqual(new Date(newDate)); }); }); + + describe("account switching", () => { + let provider: DefaultDerivedStateProvider; + + beforeEach(() => { + provider = new DefaultDerivedStateProvider(); + }); + + it("should provide a dedicated cache for each account", async () => { + const user1State$ = new Subject(); + const user1Derived = provider.get(user1State$, deriveDefinition, deps); + const user1Emissions = trackEmissions(user1Derived.state$); + + const user2State$ = new Subject(); + const user2Derived = provider.get(user2State$, deriveDefinition, deps); + const user2Emissions = trackEmissions(user2Derived.state$); + + user1State$.next("2015-12-30"); + user2State$.next("2020-12-29"); + await awaitAsync(); + + expect(user1Emissions).toEqual([new Date("2015-12-30")]); + expect(user2Emissions).toEqual([new Date("2020-12-29")]); + }); + }); }); From ee6822c00d70c4eace5109b96bb78f2a9fe661ee Mon Sep 17 00:00:00 2001 From: Jonas Hendrickx Date: Wed, 15 Jan 2025 16:05:31 +0100 Subject: [PATCH 16/28] [PM-17064] Prevent error being thrown when taxInformation is undefined. (#12884) --- .../billing/organizations/change-plan-dialog.component.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 bc5c7da8db9..73577c7b002 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 @@ -1062,7 +1062,11 @@ export class ChangePlanDialogComponent implements OnInit, OnDestroy { } private refreshSalesTax(): void { - if (!this.taxInformation.country || !this.taxInformation.postalCode) { + if ( + this.taxInformation === undefined || + !this.taxInformation.country || + !this.taxInformation.postalCode + ) { return; } From bdab4aa939e2d41f37afef666e9877a5b102b3f4 Mon Sep 17 00:00:00 2001 From: Maciej Zieniuk <167752252+mzieniukbw@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:29:37 +0100 Subject: [PATCH 17/28] PM-17042: Biometrics auto prompt popup does not show up on safari (#12868) This is due to missing await before process reload, triggered by lock, effectively disabling the biometrics auto prompt on safari. This should be detected by eslint, but due to misconfiguration, nothing was reported. Also fixed two other missing awaits on biometrics unlock. --- .../biometrics/background-browser-biometrics.service.ts | 4 ++-- apps/browser/src/popup/app.component.ts | 4 ++-- tsconfig.eslint.json | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts index 4c4753c3f7f..e943f241f77 100644 --- a/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts +++ b/apps/browser/src/key-management/biometrics/background-browser-biometrics.service.ts @@ -97,7 +97,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); await this.biometricStateService.setFingerprintValidated(true); - this.keyService.setUserKey(userKey, userId); + await this.keyService.setUserKey(userKey, userId); return userKey; } } else { @@ -115,7 +115,7 @@ export class BackgroundBrowserBiometricsService extends BiometricsService { if (await this.keyService.validateUserKey(userKey, userId)) { await this.biometricStateService.setBiometricUnlockEnabled(true); await this.biometricStateService.setFingerprintValidated(true); - this.keyService.setUserKey(userKey, userId); + await this.keyService.setUserKey(userKey, userId); return userKey; } } else { diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 7b6e402a90f..9d4835889b9 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -137,8 +137,8 @@ export class AppComponent implements OnInit, OnDestroy { this.toastService._showToast(msg); } else if (msg.command === "reloadProcess") { if (this.platformUtilsService.isSafari()) { - window.setTimeout(() => { - this.biometricStateService.updateLastProcessReload(); + window.setTimeout(async () => { + await this.biometricStateService.updateLastProcessReload(); window.location.reload(); }, 2000); } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 941a612a30c..a69452389f5 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -36,6 +36,8 @@ "@bitwarden/platform": ["./libs/platform/src"], "@bitwarden/node/*": ["./libs/node/src/*"], "@bitwarden/vault": ["./libs/vault/src"], + "@bitwarden/key-management": ["./libs/key-management/src"], + "@bitwarden/key-management/angular": ["./libs/key-management/src/angular"], "@bitwarden/bit-common/*": ["./bitwarden_license/bit-common/src/*"] }, "plugins": [ From 8c13ea894ba9d88d511aa533c071c71b92051bf2 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:43:26 +1000 Subject: [PATCH 18/28] [PM-16917] Remove jest-extended dependency (#12798) * add toContainPartialObjects matcher (replacing toIncludeAllPartialMembers from jest-extended) * replace jest-extended matchers with equivalent default matchers --- .github/renovate.json | 2 +- .../vault-header-v2.component.spec.ts | 2 +- .../view-v2/view-v2.component.spec.ts | 12 +-- .../vault-popup-list-filters.service.spec.ts | 4 +- .../default-vnext-collection.service.spec.ts | 10 +-- libs/admin-console/test.setup.ts | 4 + libs/admin-console/tsconfig.json | 2 +- libs/common/spec/matchers/index.ts | 12 +-- .../to-contain-partial-objects.spec.ts | 77 +++++++++++++++++++ .../matchers/to-contain-partial-objects.ts | 31 ++++++++ ...-service-legacy-encryptor-provider.spec.ts | 12 +-- libs/common/src/tools/rx.spec.ts | 2 +- .../services/folder/folder.service.spec.ts | 4 +- package-lock.json | 24 +----- package.json | 2 +- 15 files changed, 146 insertions(+), 54 deletions(-) create mode 100644 libs/common/spec/matchers/to-contain-partial-objects.spec.ts create mode 100644 libs/common/spec/matchers/to-contain-partial-objects.ts diff --git a/.github/renovate.json b/.github/renovate.json index 776c66af68e..a1987ca038d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -210,10 +210,10 @@ "eslint-plugin-storybook", "eslint-plugin-tailwindcss", "husky", - "jest-extended", "jest-junit", "jest-mock-extended", "jest-preset-angular", + "jest-diff", "lint-staged", "ts-jest" ], diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts index 38ec6056d19..1f67dd51c21 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-header/vault-header-v2.component.spec.ts @@ -152,7 +152,7 @@ describe("VaultHeaderV2Component", () => { it("defaults the initial state to true", (done) => { // The initial value of the `state$` variable above is undefined component["initialDisclosureVisibility$"].subscribe((initialVisibility) => { - expect(initialVisibility).toBeTrue(); + expect(initialVisibility).toBe(true); done(); }); diff --git a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts index 7ee15aa833b..526ab2e2579 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/view-v2/view-v2.component.spec.ts @@ -179,7 +179,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-username"', fakeAsync(() => { @@ -187,7 +187,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-password"', fakeAsync(() => { @@ -195,7 +195,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it('invokes `copy` when action="copy-totp"', fakeAsync(() => { @@ -203,7 +203,7 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(copy).toHaveBeenCalledOnce(); + expect(copy).toHaveBeenCalledTimes(1); })); it("closes the popout after a load action", fakeAsync(() => { @@ -218,9 +218,9 @@ describe("ViewV2Component", () => { flush(); // Resolve all promises - expect(doAutofill).toHaveBeenCalledOnce(); + expect(doAutofill).toHaveBeenCalledTimes(1); expect(focusSpy).toHaveBeenCalledWith(99); - expect(closeSpy).toHaveBeenCalledOnce(); + expect(closeSpy).toHaveBeenCalledTimes(1); })); }); }); 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 0eb91c6cbe2..e1236be08f9 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 @@ -488,7 +488,7 @@ describe("VaultPopupListFiltersService", () => { state$.next(true); service.filterVisibilityState$.subscribe((filterVisibility) => { - expect(filterVisibility).toBeTrue(); + expect(filterVisibility).toBe(true); done(); }); }); @@ -496,7 +496,7 @@ describe("VaultPopupListFiltersService", () => { it("updates stored filter state", async () => { await service.updateFilterVisibility(false); - expect(update).toHaveBeenCalledOnce(); + expect(update).toHaveBeenCalledTimes(1); // Get callback passed to `update` const updateCallback = update.mock.calls[0][0]; expect(updateCallback()).toBe(false); 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 4aa54429aad..048a4733948 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 @@ -91,7 +91,7 @@ describe("DefaultvNextCollectionService", () => { // Assert emitted values expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: "DEC_NAME_" + collection1.id, @@ -167,7 +167,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -205,7 +205,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("UPDATED_ENC_NAME_" + collection1.id), @@ -230,7 +230,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: collection1.id, name: makeEncString("ENC_NAME_" + collection1.id), @@ -253,7 +253,7 @@ describe("DefaultvNextCollectionService", () => { const result = await firstValueFrom(collectionService.encryptedCollections$(userId)); expect(result.length).toBe(1); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: newCollection3.id, name: makeEncString("ENC_NAME_" + newCollection3.id), diff --git a/libs/admin-console/test.setup.ts b/libs/admin-console/test.setup.ts index 6be6e7b8dd1..8ab102f2cf4 100644 --- a/libs/admin-console/test.setup.ts +++ b/libs/admin-console/test.setup.ts @@ -1,6 +1,10 @@ import { webcrypto } from "crypto"; + +import { addCustomMatchers } from "@bitwarden/common/spec"; import "jest-preset-angular/setup-jest"; +addCustomMatchers(); + Object.defineProperty(window, "CSS", { value: null }); Object.defineProperty(window, "getComputedStyle", { value: () => { diff --git a/libs/admin-console/tsconfig.json b/libs/admin-console/tsconfig.json index 3d22cb2ec51..4f057fd6af0 100644 --- a/libs/admin-console/tsconfig.json +++ b/libs/admin-console/tsconfig.json @@ -8,6 +8,6 @@ "@bitwarden/key-management": ["../key-management/src"] } }, - "include": ["src", "spec"], + "include": ["src", "spec", "../../libs/common/custom-matchers.d.ts"], "exclude": ["node_modules", "dist"] } diff --git a/libs/common/spec/matchers/index.ts b/libs/common/spec/matchers/index.ts index 44440be5b54..b2e09cc8e92 100644 --- a/libs/common/spec/matchers/index.ts +++ b/libs/common/spec/matchers/index.ts @@ -1,16 +1,12 @@ -import * as matchers from "jest-extended"; - import { toBeFulfilled, toBeResolved, toBeRejected } from "./promise-fulfilled"; import { toAlmostEqual } from "./to-almost-equal"; +import { toContainPartialObjects } from "./to-contain-partial-objects"; import { toEqualBuffer } from "./to-equal-buffer"; export * from "./to-equal-buffer"; export * from "./to-almost-equal"; export * from "./promise-fulfilled"; -// add all jest-extended matchers -expect.extend(matchers); - export function addCustomMatchers() { expect.extend({ toEqualBuffer: toEqualBuffer, @@ -18,6 +14,7 @@ export function addCustomMatchers() { toBeFulfilled: toBeFulfilled, toBeResolved: toBeResolved, toBeRejected: toBeRejected, + toContainPartialObjects, }); } @@ -59,4 +56,9 @@ export interface CustomMatchers { * @returns CustomMatcherResult indicating whether or not the test passed */ toBeRejected(withinMs?: number): Promise; + /** + * Matches if the received array contains all the expected objects using partial matching (expect.objectContaining). + * @param expected An array of partial objects that should be contained in the received array. + */ + toContainPartialObjects(expected: Array): R; } diff --git a/libs/common/spec/matchers/to-contain-partial-objects.spec.ts b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts new file mode 100644 index 00000000000..ab6f90adf17 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.spec.ts @@ -0,0 +1,77 @@ +describe("toContainPartialObjects", () => { + describe("matches", () => { + it("if the array only contains the partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + + it("if the array contains the partial objects and other objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + { + id: 3, + name: "baz", + }, + ]; + + const expected = [{ id: 1 }, { id: 2 }]; + + expect(actual).toContainPartialObjects(expected); + }); + }); + + describe("doesn't match", () => { + it("if the array does not contain any partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 1, name: "Foo" }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + + it("if the array contains some but not all partial objects", () => { + const actual = [ + { + id: 1, + name: "foo", + }, + { + id: 2, + name: "bar", + }, + ]; + + const expected = [{ id: 2 }, { id: 3 }]; + + expect(actual).not.toContainPartialObjects(expected); + }); + }); +}); diff --git a/libs/common/spec/matchers/to-contain-partial-objects.ts b/libs/common/spec/matchers/to-contain-partial-objects.ts new file mode 100644 index 00000000000..f072ca6fba6 --- /dev/null +++ b/libs/common/spec/matchers/to-contain-partial-objects.ts @@ -0,0 +1,31 @@ +import { EOL } from "os"; + +import { diff } from "jest-diff"; + +export const toContainPartialObjects: jest.CustomMatcher = function ( + received: Array, + expected: Array, +) { + const matched = this.equals( + received, + expect.arrayContaining(expected.map((e) => expect.objectContaining(e))), + ); + + if (matched) { + return { + message: () => + "Expected the received array NOT to include partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: true, + }; + } + + return { + message: () => + "Expected the received array to contain partial matches for all expected objects." + + EOL + + diff(expected, received), + pass: false, + }; +}; diff --git a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts index 831cad74155..0b60aef4917 100644 --- a/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts +++ b/libs/common/src/tools/cryptography/key-service-legacy-encryptor-provider.spec.ts @@ -184,7 +184,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleUserId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `userKey$` emits a falsy value after emitting a truthy value", () => { @@ -199,7 +199,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleUserId$` emits and `userKey$` completes", () => { @@ -214,7 +214,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { userKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); @@ -445,7 +445,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { singleOrganizationId$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes when `orgKeys$` emits a falsy value after emitting a truthy value", () => { @@ -466,7 +466,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.next(OrgRecords); orgKey$.next(null); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); it("completes once `dependencies.singleOrganizationId$` emits and `userKey$` completes", () => { @@ -486,7 +486,7 @@ describe("KeyServiceLegacyEncryptorProvider", () => { orgKey$.complete(); - expect(completed).toBeTrue(); + expect(completed).toBe(true); }); }); }); diff --git a/libs/common/src/tools/rx.spec.ts b/libs/common/src/tools/rx.spec.ts index 9ce147a3ff4..2c433fef93b 100644 --- a/libs/common/src/tools/rx.spec.ts +++ b/libs/common/src/tools/rx.spec.ts @@ -56,7 +56,7 @@ describe("errorOnChange", () => { source$.complete(); - expect(complete).toBeTrue(); + expect(complete).toBe(true); }); it("errors when the input changes", async () => { diff --git a/libs/common/src/vault/services/folder/folder.service.spec.ts b/libs/common/src/vault/services/folder/folder.service.spec.ts index 9fdb4327b98..cc3aa1946ca 100644 --- a/libs/common/src/vault/services/folder/folder.service.spec.ts +++ b/libs/common/src/vault/services/folder/folder.service.spec.ts @@ -77,7 +77,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folders$(mockUserId)); expect(result.length).toBe(2); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: makeEncString("ENC_STRING_1") }, { id: "2", name: makeEncString("ENC_STRING_2") }, ]); @@ -98,7 +98,7 @@ describe("Folder Service", () => { const result = await firstValueFrom(folderService.folderViews$(mockUserId)); expect(result.length).toBe(3); - expect(result).toIncludeAllPartialMembers([ + expect(result).toContainPartialObjects([ { id: "1", name: "DEC" }, { id: "2", name: "DEC" }, { name: "No Folder" }, diff --git a/package-lock.json b/package-lock.json index 87e0354f7ae..a8f82952517 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", @@ -20729,28 +20729,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-extended": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", - "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-diff": "^29.0.0", - "jest-get-type": "^29.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "jest": ">=27.2.5" - }, - "peerDependenciesMeta": { - "jest": { - "optional": true - } - } - }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", diff --git a/package.json b/package.json index 641a49c4d04..0af1445a8ae 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", "husky": "9.1.4", - "jest-extended": "4.0.2", + "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", From bbf128767172d5869c4e132b928d77406bc588a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:44:16 -0500 Subject: [PATCH 19/28] clean up classes that aren't tailwind-compatible (#12871) --- .../generator/components/src/catchall-settings.component.html | 2 +- .../components/src/credential-generator.component.html | 4 ++-- .../components/src/forwarder-settings.component.html | 2 +- .../components/src/passphrase-settings.component.html | 2 +- .../generator/components/src/password-settings.component.html | 2 +- .../components/src/subaddress-settings.component.html | 2 +- .../components/src/username-generator.component.html | 4 ++-- .../generator/components/src/username-settings.component.html | 2 +- .../send-list-items-container.component.html | 4 ++-- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libs/tools/generator/components/src/catchall-settings.component.html b/libs/tools/generator/components/src/catchall-settings.component.html index 61037c91a73..4afa145c055 100644 --- a/libs/tools/generator/components/src/catchall-settings.component.html +++ b/libs/tools/generator/components/src/catchall-settings.component.html @@ -1,4 +1,4 @@ -
+ {{ "domainName" | i18n }} {{ "options" | i18n }}
- + {{ "type" | i18n }} {{ "options" | i18n }} }} -
+ {{ "service" | i18n }} + {{ "forwarderDomainName" | i18n }}
{{ "options" | i18n }}
- +
diff --git a/libs/tools/generator/components/src/password-settings.component.html b/libs/tools/generator/components/src/password-settings.component.html index 9f8e00921fb..5e4d1079725 100644 --- a/libs/tools/generator/components/src/password-settings.component.html +++ b/libs/tools/generator/components/src/password-settings.component.html @@ -2,7 +2,7 @@

{{ "options" | i18n }}

- +
diff --git a/libs/tools/generator/components/src/subaddress-settings.component.html b/libs/tools/generator/components/src/subaddress-settings.component.html index 1dfb5e3460d..b7f71b12b2a 100644 --- a/libs/tools/generator/components/src/subaddress-settings.component.html +++ b/libs/tools/generator/components/src/subaddress-settings.component.html @@ -1,4 +1,4 @@ - + {{ "email" | i18n }} {{ "options" | i18n }}
- + {{ "type" | i18n }} {{ "options" | i18n }} }} -
+ {{ "service" | i18n }} + {{ send.name }} From f6f4bc9d4b68aeb25ba95b169e1066a97289b800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:45:14 -0500 Subject: [PATCH 20/28] remove circular reference to generator-core (#12869) --- libs/tools/generator/core/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/tools/generator/core/tsconfig.json b/libs/tools/generator/core/tsconfig.json index 7c703686b20..a95b588686f 100644 --- a/libs/tools/generator/core/tsconfig.json +++ b/libs/tools/generator/core/tsconfig.json @@ -5,7 +5,6 @@ "@bitwarden/admin-console/common": ["../../../admin-console/src/common"], "@bitwarden/auth/common": ["../../../auth/src/common"], "@bitwarden/common/*": ["../../../common/src/*"], - "@bitwarden/generator-core": ["../../../tools/generator/core/src"], "@bitwarden/key-management": ["../../../key-management/src"] } }, From e79dab868956a21e1886f38ae8d1dbc05246ead9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 10:47:02 -0500 Subject: [PATCH 21/28] [PM-16789] introduce extension metadata (#12717) --- libs/common/src/tools/extension/data.ts | 26 + .../extension-registry.abstraction.ts | 104 ++ .../src/tools/extension/extension-site.ts | 20 + libs/common/src/tools/extension/factory.ts | 24 + libs/common/src/tools/extension/index.ts | 12 + libs/common/src/tools/extension/metadata.ts | 17 + .../runtime-extension-registry.spec.ts | 923 ++++++++++++++++++ .../extension/runtime-extension-registry.ts | 286 ++++++ libs/common/src/tools/extension/type.ts | 109 +++ .../src/tools/extension/vendor/addyio.ts | 25 + .../src/tools/extension/vendor/bitwarden.ts | 8 + .../common/src/tools/extension/vendor/data.ts | 11 + .../src/tools/extension/vendor/duckduckgo.ts | 25 + .../src/tools/extension/vendor/fastmail.ts | 25 + .../tools/extension/vendor/forwardemail.ts | 25 + .../src/tools/extension/vendor/index.ts | 30 + .../src/tools/extension/vendor/mozilla.ts | 26 + .../src/tools/extension/vendor/readme.md | 33 + .../src/tools/extension/vendor/simplelogin.ts | 25 + libs/common/src/tools/util.ts | 19 + 20 files changed, 1773 insertions(+) create mode 100644 libs/common/src/tools/extension/data.ts create mode 100644 libs/common/src/tools/extension/extension-registry.abstraction.ts create mode 100644 libs/common/src/tools/extension/extension-site.ts create mode 100644 libs/common/src/tools/extension/factory.ts create mode 100644 libs/common/src/tools/extension/index.ts create mode 100644 libs/common/src/tools/extension/metadata.ts create mode 100644 libs/common/src/tools/extension/runtime-extension-registry.spec.ts create mode 100644 libs/common/src/tools/extension/runtime-extension-registry.ts create mode 100644 libs/common/src/tools/extension/type.ts create mode 100644 libs/common/src/tools/extension/vendor/addyio.ts create mode 100644 libs/common/src/tools/extension/vendor/bitwarden.ts create mode 100644 libs/common/src/tools/extension/vendor/data.ts create mode 100644 libs/common/src/tools/extension/vendor/duckduckgo.ts create mode 100644 libs/common/src/tools/extension/vendor/fastmail.ts create mode 100644 libs/common/src/tools/extension/vendor/forwardemail.ts create mode 100644 libs/common/src/tools/extension/vendor/index.ts create mode 100644 libs/common/src/tools/extension/vendor/mozilla.ts create mode 100644 libs/common/src/tools/extension/vendor/readme.md create mode 100644 libs/common/src/tools/extension/vendor/simplelogin.ts create mode 100644 libs/common/src/tools/util.ts diff --git a/libs/common/src/tools/extension/data.ts b/libs/common/src/tools/extension/data.ts new file mode 100644 index 00000000000..cab6272a068 --- /dev/null +++ b/libs/common/src/tools/extension/data.ts @@ -0,0 +1,26 @@ +/** well-known name for a feature extensible through an extension. */ +export const Site = Object.freeze({ + forwarder: "forwarder", +} as const); + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export const Field = Object.freeze({ + token: "token", + baseUrl: "baseUrl", + domain: "domain", + prefix: "prefix", +} as const); + +/** Permission levels for metadata. */ +export const Permission = Object.freeze({ + /** unless a rule denies access, allow it. If a permission is `null` + * or `undefined` it should be treated as `Permission.default`. + */ + default: "default", + /** unless a rule allows access, deny it. */ + none: "none", + /** access is explicitly granted to use an extension. */ + allow: "allow", + /** access is explicitly prohibited for this extension. This rule overrides allow rules. */ + deny: "deny", +} as const); diff --git a/libs/common/src/tools/extension/extension-registry.abstraction.ts b/libs/common/src/tools/extension/extension-registry.abstraction.ts new file mode 100644 index 00000000000..7734c01ea50 --- /dev/null +++ b/libs/common/src/tools/extension/extension-registry.abstraction.ts @@ -0,0 +1,104 @@ +import { ExtensionSite } from "./extension-site"; +import { + ExtensionMetadata, + ExtensionSet, + ExtensionPermission, + SiteId, + SiteMetadata, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them. */ +export abstract class ExtensionRegistry { + /** Registers a site supporting extensibility. + * Each site may only be registered once. Calls after the first for + * the same SiteId have no effect. + * @param site identifies the site being extended + * @param meta configures the extension site + * @return self for method chaining. + * @remarks The registry initializes with a set of allowed sites and fields. + * `registerSite` drops a registration and trims its allowed fields to only + * those indicated in the allow list. + */ + abstract registerSite: (meta: SiteMetadata) => this; + + /** List all registered extension sites with their extension permission, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract sites: () => { site: SiteMetadata; permission?: ExtensionPermission }[]; + + /** Get a site's metadata + * @param site identifies a site registration + * @return the site's metadata or `undefined` if the site isn't registered. + */ + abstract site: (site: SiteId) => SiteMetadata | undefined; + + /** Registers a vendor providing an extension. + * Each vendor may only be registered once. Calls after the first for + * the same VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerVendor: (meta: VendorMetadata) => this; + + /** List all registered vendors with their permissions, if any. + * @returns a list of all extension sites. `permission` is defined when the site + * is associated with an extension permission. + */ + abstract vendors: () => { vendor: VendorMetadata; permission?: ExtensionPermission }[]; + + /** Get a vendor's metadata + * @param site identifies a vendor registration + * @return the vendor's metadata or `undefined` if the vendor isn't registered. + */ + abstract vendor: (vendor: VendorId) => VendorMetadata | undefined; + + /** Registers an extension provided by a vendor to an extension site. + * The vendor and site MUST be registered before the extension. + * Each extension may only be registered once. Calls after the first for + * the same SiteId and VendorId have no effect. + * @param site - identifies the site being extended + * @param meta - configures the extension site + * @return self for method chaining. + */ + abstract registerExtension: (meta: ExtensionMetadata) => this; + + /** Get an extensions metadata + * @param site identifies the extension's site + * @param vendor identifies the extension's vendor + * @return the extension's metadata or `undefined` if the extension isn't registered. + */ + abstract extension: (site: SiteId, vendor: VendorId) => ExtensionMetadata | undefined; + + /** List all registered extensions and their permissions */ + abstract extensions: () => ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }>; + + /** Registers a permission. Only 1 permission can be registered for each extension set. + * Calls after the first *replace* the registered permission. + * @param set the collection of extensions affected by the permission + * @param permission the permission for the collection + * @return self for method chaining. + */ + abstract setPermission: (set: ExtensionSet, permission: ExtensionPermission) => this; + + /** Retrieves the current permission for the given extension set or `undefined` if + * a permission doesn't exist. + */ + abstract permission: (set: ExtensionSet) => ExtensionPermission | undefined; + + /** Returns all registered extension rules. */ + abstract permissions: () => { set: ExtensionSet; permission: ExtensionPermission }[]; + + /** Creates a point-in-time snapshot of the registry's contents with extension + * permissions applied for the provided SiteId. + * @param id identifies the extension site to create. + * @returns the extension site, or `undefined` if the site is not registered. + */ + abstract build: (id: SiteId) => ExtensionSite | undefined; +} diff --git a/libs/common/src/tools/extension/extension-site.ts b/libs/common/src/tools/extension/extension-site.ts new file mode 100644 index 00000000000..e8aba008493 --- /dev/null +++ b/libs/common/src/tools/extension/extension-site.ts @@ -0,0 +1,20 @@ +import { deepFreeze } from "../util"; + +import { ExtensionMetadata, SiteMetadata, VendorId } from "./type"; + +/** Describes the capabilities of an extension site. + * This type is immutable. + */ +export class ExtensionSite { + /** instantiate the extension site + * @param site describes the extension site + * @param vendors describes the available vendors + * @param extensions describes the available extensions + */ + constructor( + readonly site: Readonly, + readonly extensions: ReadonlyMap>, + ) { + deepFreeze(this); + } +} diff --git a/libs/common/src/tools/extension/factory.ts b/libs/common/src/tools/extension/factory.ts new file mode 100644 index 00000000000..10ebc77804a --- /dev/null +++ b/libs/common/src/tools/extension/factory.ts @@ -0,0 +1,24 @@ +import { DefaultFields, DefaultSites, Extension } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { VendorExtensions, Vendors } from "./vendor"; + +// FIXME: find a better way to build the registry than a hard-coded factory function + +/** Constructs the extension registry */ +export function buildExtensionRegistry() { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + for (const site of Reflect.ownKeys(Extension) as string[]) { + registry.registerSite(Extension[site]); + } + + for (const vendor of Vendors) { + registry.registerVendor(vendor); + } + + for (const extension of VendorExtensions) { + registry.registerExtension(extension); + } + + return registry; +} diff --git a/libs/common/src/tools/extension/index.ts b/libs/common/src/tools/extension/index.ts new file mode 100644 index 00000000000..e786dde4f59 --- /dev/null +++ b/libs/common/src/tools/extension/index.ts @@ -0,0 +1,12 @@ +export { Site, Field, Permission } from "./data"; +export { + SiteId, + FieldId, + VendorId, + ExtensionId, + ExtensionPermission, + SiteMetadata, + ExtensionMetadata, + VendorMetadata, +} from "./type"; +export { ExtensionSite } from "./extension-site"; diff --git a/libs/common/src/tools/extension/metadata.ts b/libs/common/src/tools/extension/metadata.ts new file mode 100644 index 00000000000..895b1d1b31f --- /dev/null +++ b/libs/common/src/tools/extension/metadata.ts @@ -0,0 +1,17 @@ +import { Field, Site, Permission } from "./data"; +import { FieldId, SiteId, SiteMetadata } from "./type"; + +export const DefaultSites: SiteId[] = Object.freeze(Object.keys(Site) as any); + +export const DefaultFields: FieldId[] = Object.freeze(Object.keys(Field) as any); + +export const Extension: Record = { + [Site.forwarder]: { + id: Site.forwarder, + availableFields: [Field.baseUrl, Field.domain, Field.prefix, Field.token], + }, +}; + +export const AllowedPermissions: ReadonlyArray = Object.freeze( + Object.values(Permission), +); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts new file mode 100644 index 00000000000..f4fe0e0ec05 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -0,0 +1,923 @@ +import { deepFreeze } from "../util"; + +import { Field, Site, Permission } from "./data"; +import { ExtensionSite } from "./extension-site"; +import { DefaultFields, DefaultSites } from "./metadata"; +import { RuntimeExtensionRegistry } from "./runtime-extension-registry"; +import { ExtensionMetadata, SiteId, SiteMetadata, VendorMetadata } from "./type"; +import { Bitwarden } from "./vendor/bitwarden"; + +// arbitrary test entities +const SomeSiteId: SiteId = Site.forwarder; + +const SomeSite: SiteMetadata = Object.freeze({ + id: SomeSiteId, + availableFields: [], +}); + +const SomeVendor = Bitwarden; +const SomeVendorId = SomeVendor.id; +const SomeExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: SomeVendor, name: "Some Product" }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://vault.bitwarden.com" }, + requestedFields: [], +}); + +const JustTrustUs: VendorMetadata = Object.freeze({ + id: "justrustus" as any, + name: "JustTrust.Us", +}); +const JustTrustUsExtension: ExtensionMetadata = deepFreeze({ + site: SomeSite, + product: { vendor: JustTrustUs }, + host: { authorization: "bearer", selfHost: "maybe", baseUrl: "https://justrust.us" }, + requestedFields: [], +}); + +// In the following tests, not-null assertions (`!`) indicate that +// the returned object should never be null or undefined given +// the conditions defined within the test case +describe("RuntimeExtensionRegistry", () => { + describe("registerSite", () => { + it("registers an extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("interns the site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + const result = registry.registerSite(SomeSite).site(SomeSiteId); + + expect(result).not.toBe(SomeSite); + }); + + it("registers an extension site with fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const site: SiteMetadata = { + ...SomeSite, + availableFields: [Field.baseUrl], + }; + + const result = registry.registerSite(site).site(SomeSiteId); + + expect(result).toEqual(site); + }); + + it("ignores unavailable sites", () => { + const registry = new RuntimeExtensionRegistry([], []); + const ignored: SiteMetadata = { + id: "an-unavailable-site" as any, + availableFields: [], + }; + + const result = registry.registerSite(ignored).sites(); + + expect(result).toEqual([]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).site(SomeSiteId); + + expect(result).toEqual(SomeSite); + }); + + it("ignores unknown available fields", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [SomeSite.availableFields, "ignored" as any], + }; + + const { availableFields } = registry.registerSite(ignored).site(SomeSiteId)!; + + expect(availableFields).toEqual(SomeSite.availableFields); + }); + + it("freezes the site definition", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const site = registry.registerSite(SomeSite).site(SomeSiteId)!; + + // reassigning `availableFields` throws b/c the object is frozen + expect(() => (site.availableFields = [Field.domain])).toThrow(); + }); + }); + + describe("site", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.site(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + + const first = registry.site(SomeSiteId); + const second = registry.site(SomeSiteId); + + expect(first).toBe(second); + }); + }); + + describe("sites", () => { + it("lists registered sites", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + const barSite: SiteMetadata = { + id: "bar" as any, + availableFields: [], + }; + + const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); + + expect(result.some(({ site }) => site.id === SomeSiteId)).toBeTrue(); + expect(result.some(({ site }) => site.id === barSite.id)).toBeTrue(); + }); + + it("includes permissions for a site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + const ignored: SiteMetadata = { + ...SomeSite, + availableFields: [Field.token], + }; + + const result = registry.registerSite(SomeSite).registerSite(ignored).sites(); + + expect(result).toEqual([{ site: SomeSite }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry + .registerSite(SomeSite) + .setPermission({ site: SomeSite.id }, Permission.allow) + .setPermission({ site: "bar" as any }, Permission.deny) + .sites(); + + expect(result).toEqual([{ site: SomeSite, permission: Permission.allow }]); + }); + }); + + describe("registerVendor", () => { + it("registers a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + const result = registry.registerVendor(SomeVendor).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor }]); + }); + + it("freezes the vendor definition", () => { + const registry = new RuntimeExtensionRegistry([], []); + // copy `SomeVendor` because it is already frozen + const original: VendorMetadata = { ...SomeVendor }; + + const [{ vendor }] = registry.registerVendor(original).vendors(); + + // reassigning `name` throws b/c the object is frozen + expect(() => (vendor.name = "Bytewarden")).toThrow(); + }); + }); + + describe("vendor", () => { + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry.vendor(SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("returns the same result when called repeatedly", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor); + + const first = registry.vendor(SomeVendorId); + const second = registry.vendor(SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("vendors", () => { + it("lists registered vendors", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.registerVendor(SomeVendor).registerVendor(JustTrustUs); + + const result = registry.vendors(); + + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBeTrue(); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBeTrue(); + }); + + it("includes permissions for a vendor", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const result = registry + .registerVendor(SomeVendor) + .setPermission({ vendor: SomeVendorId }, Permission.allow) + .vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + + it("ignores duplicate registrations", () => { + const registry = new RuntimeExtensionRegistry([], []); + const vendor: VendorMetadata = SomeVendor; + const ignored: VendorMetadata = { + ...SomeVendor, + name: "Duplicate", + }; + + const result = registry.registerVendor(vendor).registerVendor(ignored).vendors(); + + expect(result).toEqual([{ vendor }]); + }); + + it("ignores permissions for other sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.setPermission({ vendor: JustTrustUs.id }, Permission.deny).vendors(); + + expect(result).toEqual([{ vendor: SomeVendor, permission: Permission.allow }]); + }); + }); + + describe("setPermission", () => { + it("sets the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("sets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("ignores a site permission unless it is in the allowed sites list", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { site: SomeSiteId }; + + const permission = registry.setPermission(target, Permission.allow).permission(target); + + expect(permission).toBeUndefined(); + }); + + it("throws when a permission is invalid", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + + expect(() => registry.setPermission({ all: true }, "invalid" as any)).toThrow(); + }); + + it("throws when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + expect(() => registry.setPermission(target, Permission.allow)).toThrow(); + }); + }); + + describe("permission", () => { + it("gets the default all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.default); + }); + + it("gets an all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { all: true } as const; + registry.setPermission(target, Permission.none); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.none); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a site permission", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + const target = { site: SomeSiteId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("gets a vendor permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { vendor: SomeVendorId }; + registry.setPermission(target, Permission.allow); + + const permission = registry.permission(target); + + expect(permission).toEqual(Permission.allow); + }); + + it("returns undefined when the extension set is the wrong type", () => { + const registry = new RuntimeExtensionRegistry([], []); + const target = { invalid: "invalid" } as any; + + const permission = registry.permission(target); + + expect(permission).toBeUndefined(); + }); + }); + + describe("permissions", () => { + it("returns a default all permission by default", () => { + const registry = new RuntimeExtensionRegistry([], []); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.default }]); + }); + + it("returns the all permission", () => { + const registry = new RuntimeExtensionRegistry([], []); + registry.setPermission({ all: true }, Permission.none); + + const permission = registry.permissions(); + + expect(permission).toEqual([{ set: { all: true }, permission: Permission.none }]); + }); + + it("includes site permissions", () => { + const registry = new RuntimeExtensionRegistry([SomeSiteId, "bar"] as any[], DefaultFields); + registry.registerSite(SomeSite).setPermission({ site: SomeSiteId }, Permission.allow); + registry + .registerSite({ + id: "bar" as any, + availableFields: [], + }) + .setPermission({ site: "bar" as any }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), + ).toBeTrue(); + }); + + it("includes vendor permissions", () => { + const registry = new RuntimeExtensionRegistry([], DefaultFields); + registry.registerVendor(SomeVendor).setPermission({ vendor: SomeVendorId }, Permission.allow); + registry + .registerVendor(JustTrustUs) + .setPermission({ vendor: JustTrustUs.id }, Permission.deny); + + const result = registry.permissions(); + + expect( + result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), + ).toBeTrue(); + expect( + result.some( + (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, + ), + ).toBeTrue(); + }); + }); + + describe("registerExtension", () => { + it("registers an extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("ignores extensions with nonregistered sites", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerVendor(SomeVendor); + + // precondition: the site is not registered + expect(registry.site(SomeSiteId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite); + + // precondition: the vendor is not registered + expect(registry.vendor(SomeVendorId)).toBeUndefined(); + + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("ignores repeated extensions with nonregistered vendors", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + // precondition: the vendor is already registered + expect(registry.extension(SomeSiteId, SomeVendorId)).toBeDefined(); + + const result = registry + .registerExtension({ + ...SomeExtension, + requestedFields: [Field.domain], + }) + .extension(SomeSiteId, SomeVendorId); + + expect(result).toEqual(SomeExtension); + }); + + it("interns site metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedSite = registry.site(SomeSiteId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.site).toBe(internedSite); + }); + + it("interns vendor metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const internedVendor = registry.vendor(SomeVendorId); + const result = registry.registerExtension(SomeExtension).extension(SomeSiteId, SomeVendorId)!; + + expect(result.product.vendor).toBe(internedVendor); + }); + + it("freezes the extension metadata", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const extension = registry.extension(SomeSiteId, SomeVendorId)!; + + // field assignments & mutation functions throw b/c the object is frozen + expect(() => ((extension.site as any) = SomeSite)).toThrow(); + expect(() => ((extension.product.vendor as any) = SomeVendor)).toThrow(); + expect(() => ((extension.product.name as any) = "SomeVendor")).toThrow(); + expect(() => ((extension.host as any) = {})).toThrow(); + expect(() => ((extension.host.selfHost as any) = {})).toThrow(); + expect(() => ((extension.host as any).authorization = "basic")).toThrow(); + expect(() => ((extension.host as any).baseUrl = "https://www.example.com")).toThrow(); + expect(() => ((extension.requestedFields as any) = [Field.baseUrl])).toThrow(); + expect(() => (extension.requestedFields as any).push(Field.baseUrl)).toThrow(); + }); + }); + + describe("extension", () => { + describe("extension", () => { + it("returns `undefined` for an unknown extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.extension(SomeSiteId, SomeVendorId); + + expect(result).toBeUndefined(); + }); + + it("interns the extension", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + + const first = registry.extension(SomeSiteId, SomeVendorId); + const second = registry.extension(SomeSiteId, SomeVendorId); + + expect(first).toBe(second); + }); + }); + + describe("extensions", () => { + it("lists registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry.registerSite(SomeSite); + registry.registerVendor(SomeVendor).registerExtension(SomeExtension); + registry.registerVendor(JustTrustUs).registerExtension(JustTrustUsExtension); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, + ), + ).toBeTrue(); + expect( + result.some( + ({ extension }) => + extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, + ), + ).toBeTrue(); + }); + + it("includes permissions for extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ vendor: SomeVendorId }, Permission.allow); + + const result = registry.extensions(); + + expect( + result.some( + ({ extension, permissions }) => + extension.site.id === SomeSiteId && + extension.product.vendor.id === SomeVendorId && + permissions.includes(Permission.allow), + ), + ).toBeTrue(); + }); + }); + + describe("build", () => { + it("builds an empty extension site when no extensions are registered", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry.registerSite(SomeSite).registerVendor(SomeVendor).registerExtension(SomeExtension); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it("returns `undefined` for an unknown site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + + const result = registry.build(SomeSiteId); + + expect(result).toBeUndefined(); + }); + + describe("when the all permission is `default`", () => { + const allPermission = Permission.default; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `none`", () => { + const allPermission = Permission.none; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.none); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.allow]])("includes sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.allow]])("includes vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }); + + it.each([[Permission.default], [Permission.none], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + + describe("when the all permission is `allow`", () => { + const allPermission = Permission.allow; + + it("builds an extension site with all registered extensions", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.default); + const expected = registry.extension(SomeSiteId, SomeVendorId); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.get(SomeVendorId)).toBe(expected); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores sites with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow]])( + "includes vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.get(SomeVendorId)).toEqual(SomeExtension); + }, + ); + + it.each([[Permission.deny]])("ignores vendors with `%p` permission", (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }); + }); + + describe("when the all permission is `deny`", () => { + const allPermission = Permission.deny; + + it("builds an empty extension site", () => { + const registry = new RuntimeExtensionRegistry(DefaultSites, DefaultFields); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension) + .setPermission({ all: true }, Permission.deny); + + const result = registry.build(SomeSiteId)!; + + expect(result).toBeInstanceOf(ExtensionSite); + expect(result.extensions.size).toBe(0); + }); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores sites with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ site: SomeSiteId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + + it.each([[Permission.default], [Permission.none], [Permission.allow], [Permission.deny]])( + "ignores vendors with `%p` permission", + (permission) => { + const registry = new RuntimeExtensionRegistry(DefaultSites, []); + registry + .registerSite(SomeSite) + .registerVendor(SomeVendor) + .registerExtension(SomeExtension); + registry.setPermission({ all: true }, allPermission); + registry.setPermission({ vendor: SomeVendorId }, permission); + + const result = registry.build(SomeSiteId)!; + + expect(result.extensions.size).toBe(0); + }, + ); + }); + }); + }); +}); diff --git a/libs/common/src/tools/extension/runtime-extension-registry.ts b/libs/common/src/tools/extension/runtime-extension-registry.ts new file mode 100644 index 00000000000..1c630dcc915 --- /dev/null +++ b/libs/common/src/tools/extension/runtime-extension-registry.ts @@ -0,0 +1,286 @@ +import { deepFreeze } from "../util"; + +import { ExtensionRegistry } from "./extension-registry.abstraction"; +import { ExtensionSite } from "./extension-site"; +import { AllowedPermissions } from "./metadata"; +import { + ExtensionMetadata, + ExtensionPermission, + ExtensionSet, + FieldId, + ProductMetadata, + SiteMetadata, + SiteId, + VendorId, + VendorMetadata, +} from "./type"; + +/** Tracks extension sites and the vendors that extend them in application memory. */ +export class RuntimeExtensionRegistry implements ExtensionRegistry { + /** Instantiates the extension registry + * @param allowedSites sites that are valid for use by any extension; + * this is most useful to disable an extension site that is only + * available on a specific client. + * @param allowedFields fields that are valid for use by any extension; + * this is most useful to prohibit access to a field via policy. + */ + constructor( + private readonly allowedSites: SiteId[], + private readonly allowedFields: FieldId[], + ) { + Object.freeze(this.allowedFields); + Object.freeze(this.allowedSites); + } + + private allPermission: ExtensionPermission = "default"; + + private siteRegistrations = new Map(); + private sitePermissions = new Map(); + + private vendorRegistrations = new Map(); + private vendorPermissions = new Map(); + + private extensionRegistrations = new Array(); + private extensionsBySiteByVendor = new Map>(); + + registerSite(site: SiteMetadata): this { + if (!this.allowedSites.includes(site.id)) { + return this; + } + + // verify requested fields are on the list of valid fields to expose to + // an extension + const availableFields = site.availableFields.filter((field) => + this.allowedFields.includes(field), + ); + const validated: SiteMetadata = deepFreeze({ id: site.id, availableFields }); + + if (!this.siteRegistrations.has(site.id)) { + this.siteRegistrations.set(site.id, validated); + } + + return this; + } + + site(site: SiteId): SiteMetadata | undefined { + const result = this.siteRegistrations.get(site); + return result; + } + + sites() { + const sites: { site: SiteMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, site] of this.siteRegistrations.entries()) { + const s: (typeof sites)[number] = { site }; + const permission = this.sitePermissions.get(k); + if (permission) { + s.permission = permission; + } + + sites.push(s); + } + + return sites; + } + + registerVendor(vendor: VendorMetadata): this { + if (!this.vendorRegistrations.has(vendor.id)) { + const frozen = deepFreeze(vendor); + this.vendorRegistrations.set(vendor.id, frozen); + } + + return this; + } + + vendor(vendor: VendorId): VendorMetadata | undefined { + const result = this.vendorRegistrations.get(vendor); + return result; + } + + vendors() { + const vendors: { vendor: VendorMetadata; permission?: ExtensionPermission }[] = []; + + for (const [k, vendor] of this.vendorRegistrations.entries()) { + const s: (typeof vendors)[number] = { vendor }; + const permission = this.vendorPermissions.get(k); + if (permission) { + s.permission = permission; + } + + vendors.push(s); + } + + return vendors; + } + + setPermission(set: ExtensionSet, permission: ExtensionPermission): this { + if (!AllowedPermissions.includes(permission)) { + throw new Error(`invalid extension permission: ${permission}`); + } + + if ("all" in set && set.all) { + this.allPermission = permission; + } else if ("vendor" in set) { + this.vendorPermissions.set(set.vendor, permission); + } else if ("site" in set) { + if (this.allowedSites.includes(set.site)) { + this.sitePermissions.set(set.site, permission); + } + } else { + throw new Error(`Unrecognized extension set received: ${JSON.stringify(set)}.`); + } + + return this; + } + + permission(set: ExtensionSet) { + if ("all" in set && set.all) { + return this.allPermission; + } else if ("vendor" in set) { + return this.vendorPermissions.get(set.vendor); + } else if ("site" in set) { + return this.sitePermissions.get(set.site); + } else { + return undefined; + } + } + + permissions() { + const rules: { set: ExtensionSet; permission: ExtensionPermission }[] = []; + rules.push({ set: { all: true }, permission: this.allPermission }); + + for (const [site, permission] of this.sitePermissions.entries()) { + rules.push({ set: { site }, permission }); + } + + for (const [vendor, permission] of this.vendorPermissions.entries()) { + rules.push({ set: { vendor }, permission }); + } + + return rules; + } + + registerExtension(meta: ExtensionMetadata): this { + const site = this.siteRegistrations.get(meta.site.id); + const vendor = this.vendorRegistrations.get(meta.product.vendor.id); + if (!site || !vendor) { + return this; + } + + // exit early if the extension is already registered + const extensionsByVendor = + this.extensionsBySiteByVendor.get(meta.site.id) ?? new Map(); + if (extensionsByVendor.has(meta.product.vendor.id)) { + return this; + } + + // create immutable copy; this updates the vendor and site with + // their internalized representation to provide reference equality + // across registrations + const product: ProductMetadata = { vendor }; + if (meta.product.name) { + product.name = meta.product.name; + } + const extension: ExtensionMetadata = Object.freeze({ + site, + product: Object.freeze(product), + host: Object.freeze({ ...meta.host }), + requestedFields: Object.freeze([...meta.requestedFields]), + }); + + // register it + const index = this.extensionRegistrations.push(extension) - 1; + extensionsByVendor.set(vendor.id, index); + this.extensionsBySiteByVendor.set(site.id, extensionsByVendor); + + return this; + } + + extension(site: SiteId, vendor: VendorId): ExtensionMetadata | undefined { + const index = this.extensionsBySiteByVendor.get(site)?.get(vendor) ?? -1; + if (index < 0) { + return undefined; + } else { + return this.extensionRegistrations[index]; + } + } + + private getPermissions(site: SiteId, vendor: VendorId): ExtensionPermission[] { + const permissions = [ + this.sitePermissions.get(site), + this.vendorPermissions.get(vendor), + this.allPermission, + // Need to cast away `undefined` because typescript isn't + // aware that the filter eliminates undefined elements + ].filter((p) => !!p) as ExtensionPermission[]; + + return permissions; + } + + extensions(): ReadonlyArray<{ + extension: ExtensionMetadata; + permissions: ExtensionPermission[]; + }> { + const extensions = []; + for (const extension of this.extensionRegistrations) { + const permissions = this.getPermissions(extension.site.id, extension.product.vendor.id); + + extensions.push({ extension, permissions }); + } + + return extensions; + } + + build(id: SiteId): ExtensionSite | undefined { + const site = this.siteRegistrations.get(id); + if (!site) { + return undefined; + } + + if (this.allPermission === "deny") { + return new ExtensionSite(site, new Map()); + } + + const extensions = new Map(); + const entries = this.extensionsBySiteByVendor.get(id)?.entries() ?? ([] as const); + for (const [vendor, index] of entries) { + const permissions = this.getPermissions(id, vendor); + + const extension = evaluate(permissions, this.extensionRegistrations[index]); + if (extension) { + extensions.set(vendor, extension); + } + } + + const extensionSite = new ExtensionSite(site, extensions); + return extensionSite; + } +} + +function evaluate( + permissions: ExtensionPermission[], + value: ExtensionMetadata, +): ExtensionMetadata | undefined { + // deny always wins + if (permissions.includes("deny")) { + return undefined; + } + + // allow overrides implicit permissions + if (permissions.includes("allow")) { + return value; + } + + // none permission becomes a deny + if (permissions.includes("none")) { + return undefined; + } + + // default permission becomes an allow + if (permissions.includes("default")) { + return value; + } + + // if no permission is recognized, throw. This code is unreachable. + throw new Error("failed to recognize any permissions"); +} diff --git a/libs/common/src/tools/extension/type.ts b/libs/common/src/tools/extension/type.ts new file mode 100644 index 00000000000..f37d4ff8e53 --- /dev/null +++ b/libs/common/src/tools/extension/type.ts @@ -0,0 +1,109 @@ +import { Opaque } from "type-fest"; + +import { Site, Field, Permission } from "./data"; + +/** well-known name for a feature extensible through an extension. */ +export type SiteId = keyof typeof Site; + +/** well-known name for a field surfaced from an extension site to a vendor. */ +export type FieldId = keyof typeof Field; + +/** Identifies a vendor extending bitwarden */ +export type VendorId = Opaque<"vendor", string>; + +/** uniquely identifies an extension. */ +export type ExtensionId = { site: SiteId; vendor: VendorId }; + +/** Permission levels for metadata. */ +export type ExtensionPermission = keyof typeof Permission; + +/** The capabilities and descriptive content for an extension */ +export type SiteMetadata = { + /** Uniquely identifies the extension site. */ + id: SiteId; + + /** Lists the fields disclosed by the extension to the vendor */ + availableFields: FieldId[]; +}; + +/** The capabilities and descriptive content for an extension */ +export type VendorMetadata = { + /** Uniquely identifies the vendor. */ + id: VendorId; + + /** Brand name of the service providing the extension. */ + name: string; +}; + +type TokenHeader = + | { + /** Transmit the token as the value of an `Authentication` header */ + authentication: true; + } + | { + /** Transmit the token as an `Authorization` header and a formatted value + * * `bearer` uses OAUTH-2.0 bearer token format + * * `token` prefixes the token with "Token" + * * `basic-username` uses HTTP Basic authentication format, encoding the + * token as the username. + */ + authorization: "bearer" | "token" | "basic-username"; + }; + +/** Catalogues an extension's hosting status. + * selfHost: "never" always uses the service's base URL + * selfHost: "maybe" allows the user to override the service's + * base URL with their own. + * selfHost: "always" requires a base URL. + */ +export type ApiHost = TokenHeader & + ( + | { selfHost: "never"; baseUrl: string } + | { selfHost: "maybe"; baseUrl: string } + | { selfHost: "always" } + ); + +/** Describes a branded product */ +export type ProductMetadata = { + /** The vendor providing the extension */ + vendor: VendorMetadata; + + /** The branded name of the product, if it varies from the Vendor name */ + name?: string; +}; + +/** Describes an extension provided by a vendor */ +export type ExtensionMetadata = { + /** The part of Bitwarden extended by the vendor's services */ + readonly site: Readonly; + + /** Product description */ + readonly product: Readonly; + + /** Hosting provider capabilities required by the extension */ + readonly host: Readonly; + + /** Lists the fields disclosed by the extension to the vendor. + * This should be a subset of the `availableFields` listed in + * the extension. + */ + readonly requestedFields: ReadonlyArray>; +}; + +/** Identifies a collection of extensions. + */ +export type ExtensionSet = + | { + /** A set of extensions sharing an extension point */ + site: SiteId; + } + | { + /** A set of extensions sharing a vendor */ + vendor: VendorId; + } + | { + /** The total set of extensions. This is used to set a categorical + * rule affecting all extensions. + */ + all: true; + }; diff --git a/libs/common/src/tools/extension/vendor/addyio.ts b/libs/common/src/tools/extension/vendor/addyio.ts new file mode 100644 index 00000000000..c33abd570ad --- /dev/null +++ b/libs/common/src/tools/extension/vendor/addyio.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const AddyIo: VendorMetadata = { + id: Vendor.addyio, + name: "Addy.io", +}; + +export const AddyIoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: AddyIo, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://app.addy.io", + }, + requestedFields: [Field.token, Field.baseUrl, Field.domain], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/bitwarden.ts b/libs/common/src/tools/extension/vendor/bitwarden.ts new file mode 100644 index 00000000000..7f659c2d07f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/bitwarden.ts @@ -0,0 +1,8 @@ +import { VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Bitwarden: VendorMetadata = Object.freeze({ + id: Vendor.bitwarden, + name: "Bitwarden", +}); diff --git a/libs/common/src/tools/extension/vendor/data.ts b/libs/common/src/tools/extension/vendor/data.ts new file mode 100644 index 00000000000..7f0802ef82f --- /dev/null +++ b/libs/common/src/tools/extension/vendor/data.ts @@ -0,0 +1,11 @@ +import { VendorId } from "../type"; + +export const Vendor = Object.freeze({ + addyio: "addyio" as VendorId, + bitwarden: "bitwarden" as VendorId, // RESERVED + duckduckgo: "duckduckgo" as VendorId, + fastmail: "fastmail" as VendorId, + forwardemail: "forwardemail" as VendorId, + mozilla: "mozilla" as VendorId, + simplelogin: "simplelogin" as VendorId, +} as const); diff --git a/libs/common/src/tools/extension/vendor/duckduckgo.ts b/libs/common/src/tools/extension/vendor/duckduckgo.ts new file mode 100644 index 00000000000..ca4634192f5 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/duckduckgo.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const DuckDuckGo: VendorMetadata = { + id: Vendor.duckduckgo, + name: "DuckDuckGo", +}; + +export const DuckDuckGoExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: DuckDuckGo, + }, + host: { + authorization: "bearer", + selfHost: "never", + baseUrl: "https://quack.duckduckgo.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/fastmail.ts b/libs/common/src/tools/extension/vendor/fastmail.ts new file mode 100644 index 00000000000..e6fb9ec16be --- /dev/null +++ b/libs/common/src/tools/extension/vendor/fastmail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Fastmail: VendorMetadata = { + id: Vendor.fastmail, + name: "Fastmail", +}; + +export const FastmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Fastmail, + }, + host: { + authorization: "bearer", + selfHost: "maybe", + baseUrl: "https://api.fastmail.com", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/forwardemail.ts b/libs/common/src/tools/extension/vendor/forwardemail.ts new file mode 100644 index 00000000000..4fbc8c139b1 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/forwardemail.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const ForwardEmail: VendorMetadata = { + id: Vendor.forwardemail, + name: "Forward Email", +}; + +export const ForwardEmailExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: ForwardEmail, + }, + host: { + authorization: "basic-username", + selfHost: "never", + baseUrl: "https://api.forwardemail.net", + }, + requestedFields: [Field.domain, Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/index.ts b/libs/common/src/tools/extension/vendor/index.ts new file mode 100644 index 00000000000..3bac78c80db --- /dev/null +++ b/libs/common/src/tools/extension/vendor/index.ts @@ -0,0 +1,30 @@ +import { deepFreeze } from "../../util"; + +import { AddyIo, AddyIoExtensions } from "./addyio"; +import { Bitwarden } from "./bitwarden"; +import { DuckDuckGo, DuckDuckGoExtensions } from "./duckduckgo"; +import { Fastmail, FastmailExtensions } from "./fastmail"; +import { ForwardEmail, ForwardEmailExtensions } from "./forwardemail"; +import { Mozilla, MozillaExtensions } from "./mozilla"; +import { SimpleLogin, SimpleLoginExtensions } from "./simplelogin"; + +export const Vendors = deepFreeze([ + AddyIo, + Bitwarden, + DuckDuckGo, + Fastmail, + ForwardEmail, + Mozilla, + SimpleLogin, +]); + +export const VendorExtensions = deepFreeze( + [ + AddyIoExtensions, + DuckDuckGoExtensions, + FastmailExtensions, + ForwardEmailExtensions, + MozillaExtensions, + SimpleLoginExtensions, + ].flat(), +); diff --git a/libs/common/src/tools/extension/vendor/mozilla.ts b/libs/common/src/tools/extension/vendor/mozilla.ts new file mode 100644 index 00000000000..b02b97d8777 --- /dev/null +++ b/libs/common/src/tools/extension/vendor/mozilla.ts @@ -0,0 +1,26 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const Mozilla: VendorMetadata = { + id: Vendor.mozilla, + name: "Mozilla", +}; + +export const MozillaExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: Mozilla, + name: "Firefox Relay", + }, + host: { + authorization: "token", + selfHost: "never", + baseUrl: "https://relay.firefox.com/api", + }, + requestedFields: [Field.token], + }, +]; diff --git a/libs/common/src/tools/extension/vendor/readme.md b/libs/common/src/tools/extension/vendor/readme.md new file mode 100644 index 00000000000..507769edd4e --- /dev/null +++ b/libs/common/src/tools/extension/vendor/readme.md @@ -0,0 +1,33 @@ +# Vendors + +This folder contains vendor-specific logic that extends the +Bitwarden password manager. + +## Vendor IDs + +A vendor's ID is used to identify and trace the code provided by +a vendor across Bitwarden. There are a few rules that vendor ids +must follow: + +1. They should be human-readable. (No UUIDs.) +2. They may only contain lowercase ASCII characters and numbers. +3. They must retain backwards compatibility with prior versions. + +As such, any given ID may not not match the vendor's present +brand identity. Said branding may be stored in `VendorMetadata.name`. + +## Core files + +There are 4 vendor-independent files in this directory. + +- `data.ts` - core metadata used for system initialization +- `index.ts` - exports vendor metadata +- `README.md` - this file + +## Vendor definitions + +Each vendor should have one and only one definition, whose name +MUST match their `VendorId`. The vendor is free to use either a +single file (e.g. `bitwarden.ts`) or a folder containing multiple +files (e.g. `bitwarden/extension.ts`, `bitwarden/forwarder.ts`) to +host their files. diff --git a/libs/common/src/tools/extension/vendor/simplelogin.ts b/libs/common/src/tools/extension/vendor/simplelogin.ts new file mode 100644 index 00000000000..21ee969cebb --- /dev/null +++ b/libs/common/src/tools/extension/vendor/simplelogin.ts @@ -0,0 +1,25 @@ +import { Field } from "../data"; +import { Extension } from "../metadata"; +import { ExtensionMetadata, VendorMetadata } from "../type"; + +import { Vendor } from "./data"; + +export const SimpleLogin: VendorMetadata = { + id: Vendor.simplelogin, + name: "SimpleLogin", +}; + +export const SimpleLoginExtensions: ExtensionMetadata[] = [ + { + site: Extension.forwarder, + product: { + vendor: SimpleLogin, + }, + host: { + authentication: true, + selfHost: "maybe", + baseUrl: "https://app.simplelogin.io", + }, + requestedFields: [Field.baseUrl, Field.token, Field.domain], + }, +]; diff --git a/libs/common/src/tools/util.ts b/libs/common/src/tools/util.ts new file mode 100644 index 00000000000..9a3a14c1c83 --- /dev/null +++ b/libs/common/src/tools/util.ts @@ -0,0 +1,19 @@ +/** Recursively freeze an object's own keys + * @param value the value to freeze + * @returns `value` + * @remarks this function is derived from MDN's `deepFreeze`, which + * has been committed to the public domain. + */ +export function deepFreeze(value: T): Readonly { + const keys = Reflect.ownKeys(value) as (keyof T)[]; + + for (const key of keys) { + const own = value[key]; + + if ((own && typeof own === "object") || typeof own === "function") { + deepFreeze(own); + } + } + + return Object.freeze(value); +} From 1721552294548fce688548100f40eb4d63e67ad3 Mon Sep 17 00:00:00 2001 From: Conner Turnbull <133619638+cturnbull-bitwarden@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:53:30 -0500 Subject: [PATCH 22/28] Updated org plans component to not call for tax info on self hosted instances (#12888) --- .../app/billing/organizations/organization-plans.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 4592f8de894..edc29b16049 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -193,7 +193,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { this.billing = await this.organizationApiService.getBilling(this.organizationId); this.sub = await this.organizationApiService.getSubscription(this.organizationId); this.taxInformation = await this.organizationApiService.getTaxInfo(this.organizationId); - } else { + } else if (!this.selfHosted) { this.taxInformation = await this.apiService.getTaxInfo(); } From b26ad6a173da33bfcdfce7f2edd2b65c31957953 Mon Sep 17 00:00:00 2001 From: Jonathan Prusik Date: Wed, 15 Jan 2025 11:22:24 -0500 Subject: [PATCH 23/28] update the suggested items section header copy based on the blocked state of the current page (#12860) --- apps/browser/src/_locales/en/messages.json | 3 +++ .../autofill-vault-list-items.component.html | 2 +- .../autofill-vault-list-items.component.ts | 6 ++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 51fb3a0a770..9848be6d5fa 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4007,6 +4007,9 @@ "passkeyRemoved": { "message": "Passkey removed" }, + "autofillSuggestions": { + "message": "Autofill suggestions" + }, "itemSuggestions": { "message": "Suggested items" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html index 047d168ecbb..eae8e2cc980 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/autofill-vault-list-items/autofill-vault-list-items.component.html @@ -1,7 +1,7 @@ = + this.vaultPopupAutofillService.currentTabIsOnBlocklist$; + constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupAutofillService: VaultPopupAutofillService, From 58bd44fa2fe8124944f31b1d474f27ebc2147489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Wed, 15 Jan 2025 11:49:26 -0500 Subject: [PATCH 24/28] replace toBeTrue() with toBe(true) (#12893) --- .../runtime-extension-registry.spec.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts index f4fe0e0ec05..6aa7382db57 100644 --- a/libs/common/src/tools/extension/runtime-extension-registry.spec.ts +++ b/libs/common/src/tools/extension/runtime-extension-registry.spec.ts @@ -143,8 +143,8 @@ describe("RuntimeExtensionRegistry", () => { const result = registry.registerSite(SomeSite).registerSite(barSite).sites(); - expect(result.some(({ site }) => site.id === SomeSiteId)).toBeTrue(); - expect(result.some(({ site }) => site.id === barSite.id)).toBeTrue(); + expect(result.some(({ site }) => site.id === SomeSiteId)).toBe(true); + expect(result.some(({ site }) => site.id === barSite.id)).toBe(true); }); it("includes permissions for a site", () => { @@ -230,8 +230,8 @@ describe("RuntimeExtensionRegistry", () => { const result = registry.vendors(); - expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBeTrue(); - expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBeTrue(); + expect(result.some(({ vendor }) => vendor.id === SomeVendorId)).toBe(true); + expect(result.some(({ vendor }) => vendor.id === JustTrustUs.id)).toBe(true); }); it("includes permissions for a vendor", () => { @@ -411,10 +411,10 @@ describe("RuntimeExtensionRegistry", () => { expect( result.some((p: any) => p.set.site === SomeSiteId && p.permission === Permission.allow), - ).toBeTrue(); + ).toBe(true); expect( result.some((p: any) => p.set.site === "bar" && p.permission === Permission.deny), - ).toBeTrue(); + ).toBe(true); }); it("includes vendor permissions", () => { @@ -428,12 +428,12 @@ describe("RuntimeExtensionRegistry", () => { expect( result.some((p: any) => p.set.vendor === SomeVendorId && p.permission === Permission.allow), - ).toBeTrue(); + ).toBe(true); expect( result.some( (p: any) => p.set.vendor === JustTrustUs.id && p.permission === Permission.deny, ), - ).toBeTrue(); + ).toBe(true); }); }); @@ -561,13 +561,13 @@ describe("RuntimeExtensionRegistry", () => { ({ extension }) => extension.site.id === SomeSiteId && extension.product.vendor.id === SomeVendorId, ), - ).toBeTrue(); + ).toBe(true); expect( result.some( ({ extension }) => extension.site.id === SomeSiteId && extension.product.vendor.id === JustTrustUs.id, ), - ).toBeTrue(); + ).toBe(true); }); it("includes permissions for extensions", () => { @@ -587,7 +587,7 @@ describe("RuntimeExtensionRegistry", () => { extension.product.vendor.id === SomeVendorId && permissions.includes(Permission.allow), ), - ).toBeTrue(); + ).toBe(true); }); }); From a5dce0535433c5ff5afff3b025260be9b9cee883 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Wed, 15 Jan 2025 17:59:39 +0100 Subject: [PATCH 25/28] [PM-17035] Fix biometric unlock badge in mv2 (#12854) * Fix biometrics not working in firefox or windows * Remove logs * Update badge after biometric unlock * Add removal todo note * Remove debug logging * Fix type warnings * Fix userkey typing in background biometrics service * Simplify types for userkey in foreground-browser-biometrics and runtime.background.ts * Add process reload logging * Fix autoprompt not working when no process reload happened * Fix biometric unlock badge in mv2 * Fix instant reprompt on firefox lock * Remove biometrics autoprompt on firefox (#12856) --- .../auth/popup/settings/account-security.component.html | 2 +- .../src/auth/popup/settings/account-security.component.ts | 7 +++++++ apps/browser/src/background/main.background.ts | 1 + .../biometrics/background-browser-biometrics.service.ts | 6 ++++++ .../src/angular/lock/components/lock.component.ts | 7 ++++++- 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/auth/popup/settings/account-security.component.html b/apps/browser/src/auth/popup/settings/account-security.component.html index 0f2754b2bf2..8bc28c9754d 100644 --- a/apps/browser/src/auth/popup/settings/account-security.component.html +++ b/apps/browser/src/auth/popup/settings/account-security.component.html @@ -20,7 +20,7 @@

{{ "unlockMethods" | i18n }}

{{ biometricUnavailabilityReason }}
- + Date: Wed, 15 Jan 2025 18:14:31 +0100 Subject: [PATCH 26/28] Remove unused lifetime (#12889) --- apps/desktop/desktop_native/core/src/password/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/desktop_native/core/src/password/windows.rs b/apps/desktop/desktop_native/core/src/password/windows.rs index 32300b9f81f..8b297fc33b7 100644 --- a/apps/desktop/desktop_native/core/src/password/windows.rs +++ b/apps/desktop/desktop_native/core/src/password/windows.rs @@ -13,7 +13,7 @@ use windows::{ const CRED_FLAGS_NONE: u32 = 0; -pub async fn get_password<'a>(service: &str, account: &str) -> Result { +pub async fn get_password(service: &str, account: &str) -> Result { let target_name = U16CString::from_str(target_name(service, account))?; let mut credential: *mut CREDENTIALW = std::ptr::null_mut(); From 1916fdc87f0f0d30e0f49edd208765b373ab1109 Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:23:49 -0500 Subject: [PATCH 27/28] Adjust handling of GH action dependencies for CI/CD partnership (#12818) --- .github/renovate.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index a1987ca038d..5de11388039 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,13 +4,9 @@ "enabledManagers": ["cargo", "github-actions", "npm"], "packageRules": [ { - "groupName": "gh minor", + "groupName": "github action dependencies", "matchManagers": ["github-actions"], - "matchUpdateTypes": ["minor", "patch"] - }, - { - "matchManagers": ["github-actions"], - "commitMessagePrefix": "[deps] BRE:" + "matchUpdateTypes": ["minor"] }, { "matchManagers": ["cargo"], From 334dd4cebe1ad20fa7f1b65c56f08266b67d5ed4 Mon Sep 17 00:00:00 2001 From: Merissa Weinstein Date: Wed, 15 Jan 2025 11:53:03 -0600 Subject: [PATCH 28/28] [PM-10429] remove onboarding module for the browser refresh (#12759) * remove onboarding module * revert package.json commit --- apps/browser/src/_locales/en/messages.json | 6 -- .../vault-ui-onboarding.component.ts | 79 ---------------- .../components/vault-v2/vault-v2.component.ts | 5 -- .../services/vault-ui-onboarding.service.ts | 89 ------------------- .../src/platform/state/state-definitions.ts | 1 - 5 files changed, 180 deletions(-) delete mode 100644 apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts delete mode 100644 apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 9848be6d5fa..51e1203673b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -4589,12 +4589,6 @@ "textSends": { "message": "Text Sends" }, - "bitwardenNewLook": { - "message": "Bitwarden has a new look!" - }, - "bitwardenNewLookDesc": { - "message": "It's easier and more intuitive than ever to autofill and search from the Vault tab. Take a look around!" - }, "accountActions": { "message": "Account actions" }, diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts deleted file mode 100644 index 20b39c5a88d..00000000000 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { - ButtonModule, - DialogModule, - DialogService, - IconModule, - svgIcon, -} from "@bitwarden/components"; - -const announcementIcon = svgIcon` - - - - - - - - - - - - - - - - -`; - -@Component({ - standalone: true, - selector: "app-vault-ui-onboarding", - template: ` - -
- -
- - {{ "bitwardenNewLook" | i18n }} - - - {{ "bitwardenNewLookDesc" | i18n }} - - - - - - -
- `, - imports: [CommonModule, DialogModule, ButtonModule, JslibModule, IconModule], -}) -export class VaultUiOnboardingComponent { - icon = announcementIcon; - - static open(dialogService: DialogService) { - return dialogService.open(VaultUiOnboardingComponent); - } - - navigateToLink = async () => { - window.open( - "https://bitwarden.com/blog/bringing-intuitive-workflows-and-visual-updates-to-the-bitwarden-browser/", - "_blank", - ); - }; -} 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 a0c54987357..7c21c7e6a0c 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 @@ -19,7 +19,6 @@ import { PopupHeaderComponent } from "../../../../platform/popup/layout/popup-he import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page.component"; import { VaultPopupItemsService } from "../../services/vault-popup-items.service"; import { VaultPopupListFiltersService } from "../../services/vault-popup-list-filters.service"; -import { VaultUiOnboardingService } from "../../services/vault-ui-onboarding.service"; import { BlockedInjectionBanner } from "./blocked-injection-banner/blocked-injection-banner.component"; import { @@ -58,7 +57,6 @@ enum VaultState { VaultHeaderV2Component, DecryptionFailureDialogComponent, ], - providers: [VaultUiOnboardingService], }) export class VaultV2Component implements OnInit, OnDestroy { cipherType = CipherType; @@ -93,7 +91,6 @@ export class VaultV2Component implements OnInit, OnDestroy { constructor( private vaultPopupItemsService: VaultPopupItemsService, private vaultPopupListFiltersService: VaultPopupListFiltersService, - private vaultUiOnboardingService: VaultUiOnboardingService, private destroyRef: DestroyRef, private cipherService: CipherService, private dialogService: DialogService, @@ -123,8 +120,6 @@ export class VaultV2Component implements OnInit, OnDestroy { } async ngOnInit() { - await this.vaultUiOnboardingService.showOnboardingDialog(); - this.cipherService.failedToDecryptCiphers$ .pipe( map((ciphers) => ciphers.filter((c) => !c.isDeleted)), diff --git a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts b/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts deleted file mode 100644 index f50d6ebc236..00000000000 --- a/apps/browser/src/vault/popup/services/vault-ui-onboarding.service.ts +++ /dev/null @@ -1,89 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Injectable } from "@angular/core"; -import { firstValueFrom, map } from "rxjs"; - -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { - GlobalState, - KeyDefinition, - StateProvider, - VAULT_BROWSER_UI_ONBOARDING, -} from "@bitwarden/common/platform/state"; -import { DialogService } from "@bitwarden/components"; - -import { VaultUiOnboardingComponent } from "../components/vault-v2/vault-ui-onboarding/vault-ui-onboarding.component"; - -// Key definition for the Vault UI onboarding state. -// This key is used to store the state of the new UI information dialog. -export const GLOBAL_VAULT_UI_ONBOARDING = new KeyDefinition( - VAULT_BROWSER_UI_ONBOARDING, - "dialogState", - { - deserializer: (obj) => obj, - }, -); - -@Injectable() -export class VaultUiOnboardingService { - private onboardingUiReleaseDate = new Date("2024-12-10"); - - private vaultUiOnboardingState: GlobalState = this.stateProvider.getGlobal( - GLOBAL_VAULT_UI_ONBOARDING, - ); - - private readonly vaultUiOnboardingState$ = this.vaultUiOnboardingState.state$.pipe( - map((x) => x ?? false), - ); - - constructor( - private stateProvider: StateProvider, - private dialogService: DialogService, - private apiService: ApiService, - ) {} - - /** - * Checks whether the onboarding dialog should be shown and opens it if necessary. - * The dialog is shown if the user has not previously viewed it and is not a new account. - */ - async showOnboardingDialog(): Promise { - const hasViewedDialog = await this.getVaultUiOnboardingState(); - - if (!hasViewedDialog && !(await this.isNewAccount())) { - await this.openVaultUiOnboardingDialog(); - } - } - - private async openVaultUiOnboardingDialog(): Promise { - const dialogRef = VaultUiOnboardingComponent.open(this.dialogService); - - const result = firstValueFrom(dialogRef.closed); - - // Update the onboarding state when the dialog is closed - await this.setVaultUiOnboardingState(true); - - return result; - } - - private async isNewAccount(): Promise { - const userProfile = await this.apiService.getProfile(); - const profileCreationDate = new Date(userProfile.creationDate); - return profileCreationDate > this.onboardingUiReleaseDate; - } - - /** - * Updates and saves the state indicating whether the user has viewed - * the new UI onboarding information dialog. - */ - private async setVaultUiOnboardingState(value: boolean): Promise { - await this.vaultUiOnboardingState.update(() => value); - } - - /** - * Retrieves the current state indicating whether the user has viewed - * the new UI onboarding information dialog.s - */ - private async getVaultUiOnboardingState(): Promise { - return await firstValueFrom(this.vaultUiOnboardingState$); - } -} diff --git a/libs/common/src/platform/state/state-definitions.ts b/libs/common/src/platform/state/state-definitions.ts index 1ed5227cb13..483a8c050d3 100644 --- a/libs/common/src/platform/state/state-definitions.ts +++ b/libs/common/src/platform/state/state-definitions.ts @@ -179,7 +179,6 @@ export const PREMIUM_BANNER_DISK_LOCAL = new StateDefinition("premiumBannerRepro web: "disk-local", }); export const BANNERS_DISMISSED_DISK = new StateDefinition("bannersDismissed", "disk"); -export const VAULT_BROWSER_UI_ONBOARDING = new StateDefinition("vaultBrowserUiOnboarding", "disk"); export const NEW_DEVICE_VERIFICATION_NOTICE = new StateDefinition( "newDeviceVerificationNotice", "disk",