diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 37591ae5c36..6c7c43c4dac 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -84,7 +84,7 @@ libs/common/spec @bitwarden/team-platform-dev libs/common/src/state-migrations @bitwarden/team-platform-dev libs/platform @bitwarden/team-platform-dev # Node-specifc platform files -libs/node @bitwarden/team-platform-dev +libs/node @bitwarden/team-key-management-dev # Web utils used across app and connectors apps/web/src/utils/ @bitwarden/team-platform-dev # Web core and shared files diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml index 75a07431942..f436f1b3760 100644 --- a/.github/workflows/chromatic.yml +++ b/.github/workflows/chromatic.yml @@ -28,9 +28,22 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 + + - name: Get changed files + id: get-changed-files-for-chromatic + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + with: + filters: | + storyFiles: + - "apps/!(cli)/**" + - "bitwarden_license/bit-web/src/app/**" + - "libs/!(eslint)/**" + - "package.json" + - ".storybook/**" - name: Get Node version id: retrieve-node-version + if: steps.get-changed-files-for-chromatic.outputs.storyFiles == 'true' run: | NODE_NVMRC=$(cat .nvmrc) NODE_VERSION=${NODE_NVMRC/v/''} @@ -40,6 +53,7 @@ jobs: uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 with: node-version: ${{ steps.retrieve-node-version.outputs.node_version }} + if: steps.get-changed-files-for-chromatic.outputs.storyFiles == 'true' - name: Cache NPM id: npm-cache @@ -47,12 +61,15 @@ jobs: with: path: "~/.npm" key: ${{ runner.os }}-npm-chromatic-${{ hashFiles('**/package-lock.json') }} + if: steps.get-changed-files-for-chromatic.outputs.storyFiles == 'true' - name: Install Node dependencies + if: steps.get-changed-files-for-chromatic.outputs.storyFiles == 'true' run: npm ci # Manually build the Storybook to resolve a bug related to TurboSnap - name: Build Storybook + if: steps.get-changed-files-for-chromatic.outputs.storyFiles == 'true' run: npm run build-storybook:ci - name: Publish to Chromatic @@ -64,3 +81,5 @@ jobs: exitOnceUploaded: true onlyChanged: true externals: "[\"libs/components/**/*.scss\", \"libs/components/**/*.css\", \"libs/components/tailwind.config*.js\"]" + # Rather than use an `if` check on the whole publish step, we need to tell Chromatic to skip so that any Chromatic-spawned actions are properly skipped + skip: ${{ steps.get-changed-files-for-chromatic.outputs.storyFiles == 'false' }} diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts index ee940952852..6d1f0571ae7 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.spec.ts @@ -6,7 +6,7 @@ import { DefaultLoginComponentService } from "@bitwarden/auth/angular"; import { SsoUrlService } from "@bitwarden/auth/common"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { Environment, EnvironmentService, diff --git a/apps/browser/src/auth/popup/login/extension-login-component.service.ts b/apps/browser/src/auth/popup/login/extension-login-component.service.ts index 9ed727ade33..49ed0635b7a 100644 --- a/apps/browser/src/auth/popup/login/extension-login-component.service.ts +++ b/apps/browser/src/auth/popup/login/extension-login-component.service.ts @@ -6,7 +6,7 @@ import { firstValueFrom } from "rxjs"; import { DefaultLoginComponentService, LoginComponentService } from "@bitwarden/auth/angular"; import { SsoUrlService } from "@bitwarden/auth/common"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; 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 6757972e839..55260cc1149 100644 --- a/apps/browser/src/autofill/services/autofill-overlay-content.service.ts +++ b/apps/browser/src/autofill/services/autofill-overlay-content.service.ts @@ -840,7 +840,7 @@ export class AutofillOverlayContentService implements AutofillOverlayContentServ return; } - const clonedNode = formFieldElement.cloneNode() as FillableFormFieldElement; + const clonedNode = formFieldElement.cloneNode(true) as FillableFormFieldElement; const identityLoginFields: AutofillFieldQualifierType[] = [ AutofillFieldQualifier.identityUsername, AutofillFieldQualifier.identityEmail, diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index aeee58c6fdf..709d64f2094 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -71,11 +71,13 @@ import { DefaultBillingAccountProfileStateService } from "@bitwarden/common/bill import { ClientType } from "@bitwarden/common/enums"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { BulkEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/bulk-encrypt.service.implementation"; import { EncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/encrypt.service.implementation"; import { FallbackBulkEncryptService } from "@bitwarden/common/key-management/crypto/services/fallback-bulk-encrypt.service"; import { MultithreadEncryptServiceImplementation } from "@bitwarden/common/key-management/crypto/services/multithread-encrypt.service.implementation"; +import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { DeviceTrustService } from "@bitwarden/common/key-management/device-trust/services/device-trust.service.implementation"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; @@ -91,7 +93,6 @@ import { import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigApiServiceAbstraction } from "@bitwarden/common/platform/abstractions/config/config-api.service.abstraction"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { Fido2ActiveRequestManager as Fido2ActiveRequestManagerAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-active-request-manager.abstraction"; import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; @@ -146,7 +147,6 @@ import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/no import { StateService } from "@bitwarden/common/platform/services/state.service"; import { SystemService } from "@bitwarden/common/platform/services/system.service"; import { UserAutoUnlockKeyService } from "@bitwarden/common/platform/services/user-auto-unlock-key.service"; -import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { ActiveUserStateProvider, DerivedStateProvider, diff --git a/apps/browser/src/background/nativeMessaging.background.ts b/apps/browser/src/background/nativeMessaging.background.ts index 69521228bc5..d92826765db 100644 --- a/apps/browser/src/background/nativeMessaging.background.ts +++ b/apps/browser/src/background/nativeMessaging.background.ts @@ -2,9 +2,9 @@ import { delay, filter, firstValueFrom, from, map, race, timer } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/browser/src/platform/browser/browser-api.spec.ts b/apps/browser/src/platform/browser/browser-api.spec.ts index f6bf7d4069b..49d3e8e1cec 100644 --- a/apps/browser/src/platform/browser/browser-api.spec.ts +++ b/apps/browser/src/platform/browser/browser-api.spec.ts @@ -589,4 +589,113 @@ describe("BrowserApi", () => { ); }); }); + + /* + * Safari sometimes returns >1 tabs unexpectedly even when + * specificing a `windowId` or `currentWindow: true` query option. + * + * For example, when there are >=2 windows with an active pinned tab, + * the pinned tab will always be included as the first entry in the array, + * while the correct tab is included as the second entry. + * + * These tests can remain as verification when Safari fixes this bug. + */ + describe.each([{ isSafariApi: true }, { isSafariApi: false }])( + "SafariTabsQuery %p", + ({ isSafariApi }) => { + let originalIsSafariApi = BrowserApi.isSafariApi; + const expectedWindowId = 10; + const wrongWindowId = expectedWindowId + 1; + const raceConditionWindowId = expectedWindowId + 2; + const mismatchedWindowId = expectedWindowId + 3; + + const resolvedTabsQueryResult = [ + mock({ + title: "tab[0] is a pinned tab from another window", + pinned: true, + windowId: wrongWindowId, + }), + mock({ + title: "tab[1] is the tab with the correct foreground window", + windowId: expectedWindowId, + }), + ]; + + function mockCurrentWindowId(id: number | null) { + jest + .spyOn(BrowserApi, "getCurrentWindow") + .mockResolvedValue(mock({ id })); + } + + beforeEach(() => { + originalIsSafariApi = BrowserApi.isSafariApi; + BrowserApi.isSafariApi = isSafariApi; + mockCurrentWindowId(expectedWindowId); + jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue(resolvedTabsQueryResult); + }); + + afterEach(() => { + BrowserApi.isSafariApi = originalIsSafariApi; + jest.restoreAllMocks(); + }); + + describe.each([BrowserApi.getTabFromCurrentWindow, BrowserApi.getTabFromCurrentWindowId])( + "%p", + (getCurrTabFn) => { + it("returns the first tab when the query result has one tab", async () => { + const expectedSingleTab = resolvedTabsQueryResult[0]; + jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([expectedSingleTab]); + const actualTab = await getCurrTabFn(); + expect(actualTab).toBe(expectedSingleTab); + }); + + it("returns the first tab when the current window ID is mismatched", async () => { + mockCurrentWindowId(mismatchedWindowId); + const actualTab = await getCurrTabFn(); + expect(actualTab).toBe(resolvedTabsQueryResult[0]); + }); + + it("returns the first tab when the current window ID is unavailable", async () => { + mockCurrentWindowId(null); + const actualTab = await getCurrTabFn(); + expect(actualTab).toBe(resolvedTabsQueryResult[0]); + }); + + if (isSafariApi) { + it("returns the tab with the current window ID", async () => { + const actualTab = await getCurrTabFn(); + expect(actualTab.windowId).toBe(expectedWindowId); + }); + + it(`returns the tab with the current window ID at the time of calling [Function ${getCurrTabFn.name}]`, async () => { + jest.spyOn(BrowserApi, "tabsQuery").mockImplementation(() => { + /* + * Simulate rapid clicking/switching between windows, e.g. + * 1. From Window A, call `getCurrTabFn()` + * 2. getCurrTabFn() calls `await BrowserApi.tabsQuery()` + * 3. Users switches to Window B before the `await` returns + * 4. getCurrTabFn() calls `await BrowserApi.getCurrentWindow()` + * ^ This now returns Window B and filters the results erroneously + */ + mockCurrentWindowId(raceConditionWindowId); + + return Promise.resolve(resolvedTabsQueryResult); + }); + + const actualTab = await getCurrTabFn(); + expect(actualTab.windowId).toBe(expectedWindowId); + }); + } /* !isSafariApi */ else { + it("falls back to tabsQueryFirst", async () => { + const tabsQueryFirstSpy = jest.spyOn(BrowserApi, "tabsQueryFirst"); + const actualTab = await getCurrTabFn(); + + expect(tabsQueryFirstSpy).toHaveBeenCalled(); + expect(actualTab).toBe(resolvedTabsQueryResult[0]); + }); + } + }, + ); + }, + ); }); diff --git a/apps/browser/src/platform/browser/browser-api.ts b/apps/browser/src/platform/browser/browser-api.ts index ec16c883bfb..4b4cec7e7da 100644 --- a/apps/browser/src/platform/browser/browser-api.ts +++ b/apps/browser/src/platform/browser/browser-api.ts @@ -125,7 +125,7 @@ export class BrowserApi { } static async getTabFromCurrentWindowId(): Promise | null { - return await BrowserApi.tabsQueryFirst({ + return await BrowserApi.tabsQueryFirstCurrentWindowForSafari({ active: true, windowId: chrome.windows.WINDOW_ID_CURRENT, }); @@ -153,7 +153,7 @@ export class BrowserApi { } static async getTabFromCurrentWindow(): Promise | null { - return await BrowserApi.tabsQueryFirst({ + return await BrowserApi.tabsQueryFirstCurrentWindowForSafari({ active: true, currentWindow: true, }); @@ -197,6 +197,51 @@ export class BrowserApi { return null; } + /** + * Drop-in replacement for {@link BrowserApi.tabsQueryFirst}. + * + * Safari sometimes returns >1 tabs unexpectedly even when + * specificing a `windowId` or `currentWindow: true` query option. + * + * For all of these calls, + * ``` + * await chrome.tabs.query({active: true, currentWindow: true}) + * await chrome.tabs.query({active: true, windowId: chrome.windows.WINDOW_ID_CURRENT}) + * await chrome.tabs.query({active: true, windowId: 10}) + * ``` + * + * Safari could return: + * ``` + * [ + * {windowId: 2, pinned: true, title: "Incorrect tab in another window", …}, + * {windowId: 10, title: "Correct tab in foreground", …}, + * ] + * ``` + * + * This function captures the current window ID manually before running the query, + * then finds and returns the tab with the matching window ID. + * + * See the `SafariTabsQuery` tests in `browser-api.spec.ts`. + * + * This workaround can be removed when Safari fixes this bug. + */ + static async tabsQueryFirstCurrentWindowForSafari( + options: chrome.tabs.QueryInfo, + ): Promise | null { + if (!BrowserApi.isSafariApi) { + return await BrowserApi.tabsQueryFirst(options); + } + + const currentWindowId = (await BrowserApi.getCurrentWindow()).id; + const tabs = await BrowserApi.tabsQuery(options); + + if (tabs.length <= 1 || currentWindowId == null) { + return tabs[0]; + } + + return tabs.find((t) => t.windowId === currentWindowId) ?? tabs[0]; + } + static tabSendMessageData( tab: chrome.tabs.Tab, command: string, diff --git a/apps/browser/src/popup/images/two-factor/rc-w.png b/apps/browser/src/popup/images/two-factor/rc-w.png deleted file mode 100644 index e83b8db1324..00000000000 Binary files a/apps/browser/src/popup/images/two-factor/rc-w.png and /dev/null differ diff --git a/apps/browser/src/popup/images/two-factor/rc.png b/apps/browser/src/popup/images/two-factor/rc.png deleted file mode 100644 index 4bebdf936cc..00000000000 Binary files a/apps/browser/src/popup/images/two-factor/rc.png and /dev/null differ diff --git a/apps/browser/src/popup/scss/plugins.scss b/apps/browser/src/popup/scss/plugins.scss index b8ac8697b7f..591e8a1bd0c 100644 --- a/apps/browser/src/popup/scss/plugins.scss +++ b/apps/browser/src/popup/scss/plugins.scss @@ -21,11 +21,3 @@ max-width: 100px; } } - -.recovery-code-img { - @include themify($themes) { - content: url("../images/two-factor/rc" + themed("mfaLogoSuffix")); - max-width: 100px; - max-height: 45px; - } -} diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index f603f82a5cf..00b8ae81cf9 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -63,7 +63,9 @@ import { } from "@bitwarden/common/autofill/services/user-notification-settings.service"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { ClientType } from "@bitwarden/common/enums"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; +import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { VaultTimeoutService, @@ -74,7 +76,6 @@ import { DefaultAnimationControlService, } from "@bitwarden/common/platform/abstractions/animation-control.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -101,7 +102,6 @@ import { ContainerService } from "@bitwarden/common/platform/services/container. import { DefaultSdkClientFactory } from "@bitwarden/common/platform/services/sdk/default-sdk-client-factory"; import { NoopSdkClientFactory } from "@bitwarden/common/platform/services/sdk/noop-sdk-client-factory"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; -import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; import { DerivedStateProvider, GlobalStateProvider, diff --git a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts index f986bdfca31..30fd57a6bc6 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/add-edit/add-edit-v2.component.ts @@ -488,5 +488,79 @@ const mapAddEditCipherInfoToInitialValues = ( initialValues.username = cipher.identity.username; } + if (cipher.type == CipherType.Identity) { + const identity = cipher.identity; + + if (identity != null) { + if (identity.title != null) { + initialValues.title = identity.title; + } + + if (identity.firstName != null) { + initialValues.firstName = identity.firstName; + } + + if (identity.middleName != null) { + initialValues.middleName = identity.middleName; + } + + if (identity.lastName != null) { + initialValues.lastName = identity.lastName; + } + + if (identity.company != null) { + initialValues.company = identity.company; + } + + if (identity.ssn != null) { + initialValues.ssn = identity.ssn; + } + + if (identity.passportNumber != null) { + initialValues.passportNumber = identity.passportNumber; + } + + if (identity.licenseNumber != null) { + initialValues.licenseNumber = identity.licenseNumber; + } + + if (identity.email != null) { + initialValues.email = identity.email; + } + + if (identity.phone != null) { + initialValues.phone = identity.phone; + } + + if (identity.address1 != null) { + initialValues.address1 = identity.address1; + } + + if (identity.address2 != null) { + initialValues.address2 = identity.address2; + } + + if (identity.address3 != null) { + initialValues.address3 = identity.address3; + } + + if (identity.city != null) { + initialValues.city = identity.city; + } + + if (identity.state != null) { + initialValues.state = identity.state; + } + + if (identity.postalCode != null) { + initialValues.postalCode = identity.postalCode; + } + + if (identity.country != null) { + initialValues.country = identity.country; + } + } + } + return initialValues; }; diff --git a/apps/cli/src/auth/commands/login.command.ts b/apps/cli/src/auth/commands/login.command.ts index 107afc6dc8d..8d66a566038 100644 --- a/apps/cli/src/auth/commands/login.command.ts +++ b/apps/cli/src/auth/commands/login.command.ts @@ -31,9 +31,9 @@ import { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two import { UpdateTempPasswordRequest } from "@bitwarden/common/auth/models/request/update-temp-password.request"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; import { ClientType } from "@bitwarden/common/enums"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/cli/src/auth/commands/unlock.command.ts b/apps/cli/src/auth/commands/unlock.command.ts index 34546f3d56f..6d524759dd6 100644 --- a/apps/cli/src/auth/commands/unlock.command.ts +++ b/apps/cli/src/auth/commands/unlock.command.ts @@ -7,9 +7,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service"; diff --git a/apps/cli/src/tools/send/commands/receive.command.ts b/apps/cli/src/tools/send/commands/receive.command.ts index 879d03f6dae..c67b4213d97 100644 --- a/apps/cli/src/tools/send/commands/receive.command.ts +++ b/apps/cli/src/tools/send/commands/receive.command.ts @@ -5,9 +5,9 @@ import * as inquirer from "inquirer"; import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 82259155b37..04bc4887ff7 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -2965,9 +2965,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "492a604e2fd7f814268a378409e6c92b5525d747d10db9a229723f55a417958c" dependencies = [ "backtrace", "bytes", diff --git a/apps/desktop/desktop_native/Cargo.toml b/apps/desktop/desktop_native/Cargo.toml index 9b1e5c2666c..01838359147 100644 --- a/apps/desktop/desktop_native/Cargo.toml +++ b/apps/desktop/desktop_native/Cargo.toml @@ -51,7 +51,7 @@ ssh-encoding = "=0.2.0" ssh-key = {version = "=0.6.7", default-features = false } sysinfo = "0.33.1" thiserror = "=1.0.69" -tokio = "=1.43.0" +tokio = "=1.43.1" tokio-stream = "=0.1.15" tokio-util = "=0.7.13" typenum = "=1.17.0" diff --git a/apps/desktop/package.json b/apps/desktop/package.json index a47d6fb88be..a4365b4c06a 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.4.0", + "version": "2025.4.1", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/app/services/renderer-crypto-function.service.ts b/apps/desktop/src/app/services/renderer-crypto-function.service.ts index ee80dc25933..8f109e0613f 100644 --- a/apps/desktop/src/app/services/renderer-crypto-function.service.ts +++ b/apps/desktop/src/app/services/renderer-crypto-function.service.ts @@ -1,5 +1,5 @@ -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { WebCryptoFunctionService } from "@bitwarden/common/platform/services/web-crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; +import { WebCryptoFunctionService } from "@bitwarden/common/key-management/crypto/services/web-crypto-function.service"; export class RendererCryptoFunctionService extends WebCryptoFunctionService diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index db28f01f27a..cfab600505e 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -52,6 +52,7 @@ import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; +import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service"; @@ -60,7 +61,6 @@ import { VaultTimeoutStringType, } from "@bitwarden/common/key-management/vault-timeout"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-authenticator.service.abstraction"; import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/platform/abstractions/fido2/fido2-user-interface.service.abstraction"; diff --git a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts index 914dc73bfd2..c88627250c9 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.spec.ts @@ -6,7 +6,7 @@ import { DefaultLoginComponentService } from "@bitwarden/auth/angular"; import { SsoUrlService } from "@bitwarden/auth/common"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { ClientType } from "@bitwarden/common/enums"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { Environment, EnvironmentService, @@ -107,22 +107,17 @@ describe("DesktopLoginComponentService", () => { }); describe("redirectToSso", () => { - // Array of all permutations of isAppImage, isSnapStore, and isDev + // Array of all permutations of isAppImage and isDev const permutations = [ - [true, false, false], // Case 1: isAppImage true - [false, true, false], // Case 2: isSnapStore true - [false, false, true], // Case 3: isDev true - [true, true, false], // Case 4: isAppImage and isSnapStore true - [true, false, true], // Case 5: isAppImage and isDev true - [false, true, true], // Case 6: isSnapStore and isDev true - [true, true, true], // Case 7: all true - [false, false, false], // Case 8: all false + [true, false], // Case 1: isAppImage true + [false, true], // Case 2: isDev true + [true, true], // Case 3: all true + [false, false], // Case 4: all false ]; - permutations.forEach(([isAppImage, isSnapStore, isDev]) => { - it(`executes correct logic for isAppImage=${isAppImage}, isSnapStore=${isSnapStore}, isDev=${isDev}`, async () => { + permutations.forEach(([isAppImage, isDev]) => { + it(`executes correct logic for isAppImage=${isAppImage}, isDev=${isDev}`, async () => { (global as any).ipc.platform.isAppImage = isAppImage; - (global as any).ipc.platform.isSnapStore = isSnapStore; (global as any).ipc.platform.isDev = isDev; const email = "test@bitwarden.com"; @@ -136,7 +131,7 @@ describe("DesktopLoginComponentService", () => { await service.redirectToSsoLogin(email); - if (isAppImage || isSnapStore || isDev) { + if (isAppImage || isDev) { expect(ipc.platform.localhostCallbackService.openSsoPrompt).toHaveBeenCalledWith( codeChallenge, state, 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 89407b0d4bc..60e7791b384 100644 --- a/apps/desktop/src/auth/login/desktop-login-component.service.ts +++ b/apps/desktop/src/auth/login/desktop-login-component.service.ts @@ -6,7 +6,7 @@ import { firstValueFrom } from "rxjs"; import { DefaultLoginComponentService, LoginComponentService } from "@bitwarden/auth/angular"; import { DESKTOP_SSO_CALLBACK, SsoUrlService } from "@bitwarden/auth/common"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -51,7 +51,7 @@ export class DesktopLoginComponentService ): Promise { // For platforms that cannot support a protocol-based (e.g. bitwarden://) callback, we use a localhost callback // Otherwise, we launch the SSO component in a browser window and wait for the callback - if (ipc.platform.isAppImage || ipc.platform.isSnapStore || ipc.platform.isDev) { + if (ipc.platform.isAppImage || ipc.platform.isDev) { await this.initiateSsoThroughLocalhostCallback(email, state, codeChallenge); } else { const env = await firstValueFrom(this.environmentService.environment$); diff --git a/apps/desktop/src/images/two-factor/rc-w.png b/apps/desktop/src/images/two-factor/rc-w.png deleted file mode 100644 index e83b8db1324..00000000000 Binary files a/apps/desktop/src/images/two-factor/rc-w.png and /dev/null differ diff --git a/apps/desktop/src/images/two-factor/rc.png b/apps/desktop/src/images/two-factor/rc.png deleted file mode 100644 index 4bebdf936cc..00000000000 Binary files a/apps/desktop/src/images/two-factor/rc.png and /dev/null differ diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 6c6a724560e..a720dff7257 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.4.0", + "version": "2025.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.4.0", + "version": "2025.4.1", "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 b52172019f9..e288f5f5a79 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.4.0", + "version": "2025.4.1", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/apps/desktop/src/platform/main/main-crypto-function.service.ts b/apps/desktop/src/platform/main/main-crypto-function.service.ts index 2fc3fde1db2..0751f6b3a85 100644 --- a/apps/desktop/src/platform/main/main-crypto-function.service.ts +++ b/apps/desktop/src/platform/main/main-crypto-function.service.ts @@ -1,6 +1,6 @@ import { ipcMain } from "electron"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { crypto } from "@bitwarden/desktop-napi"; import { NodeCryptoFunctionService } from "@bitwarden/node/services/node-crypto-function.service"; diff --git a/apps/desktop/src/platform/services/electron-key.service.ts b/apps/desktop/src/platform/services/electron-key.service.ts index e378f3cf374..d272a9a9bd3 100644 --- a/apps/desktop/src/platform/services/electron-key.service.ts +++ b/apps/desktop/src/platform/services/electron-key.service.ts @@ -1,8 +1,8 @@ import { PinServiceAbstraction } from "@bitwarden/auth/common"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/desktop/src/scss/plugins.scss b/apps/desktop/src/scss/plugins.scss index b8ac8697b7f..591e8a1bd0c 100644 --- a/apps/desktop/src/scss/plugins.scss +++ b/apps/desktop/src/scss/plugins.scss @@ -21,11 +21,3 @@ max-width: 100px; } } - -.recovery-code-img { - @include themify($themes) { - content: url("../images/two-factor/rc" + themed("mfaLogoSuffix")); - max-width: 100px; - max-height: 45px; - } -} diff --git a/apps/desktop/src/services/biometric-message-handler.service.spec.ts b/apps/desktop/src/services/biometric-message-handler.service.spec.ts index cc82c165219..0e92e3839fe 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.spec.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.spec.ts @@ -5,8 +5,8 @@ import { of } from "rxjs"; import { AccountInfo, AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/desktop/src/services/biometric-message-handler.service.ts b/apps/desktop/src/services/biometric-message-handler.service.ts index 7f4adce29d9..48026bca388 100644 --- a/apps/desktop/src/services/biometric-message-handler.service.ts +++ b/apps/desktop/src/services/biometric-message-handler.service.ts @@ -4,8 +4,8 @@ import { combineLatest, concatMap, firstValueFrom, map } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; diff --git a/apps/desktop/src/services/duckduckgo-message-handler.service.ts b/apps/desktop/src/services/duckduckgo-message-handler.service.ts index e4474b60741..e82444be993 100644 --- a/apps/desktop/src/services/duckduckgo-message-handler.service.ts +++ b/apps/desktop/src/services/duckduckgo-message-handler.service.ts @@ -4,8 +4,8 @@ import { Injectable } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { NativeMessagingVersion } from "@bitwarden/common/enums"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/web/src/app/admin-console/icons/admin-console-logo.ts b/apps/web/src/app/admin-console/icons/admin-console-logo.ts index 32b2b7a13a5..25b463217eb 100644 --- a/apps/web/src/app/admin-console/icons/admin-console-logo.ts +++ b/apps/web/src/app/admin-console/icons/admin-console-logo.ts @@ -1,5 +1,27 @@ import { svgIcon } from "@bitwarden/components"; export const AdminConsoleLogo = svgIcon` - + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/apps/web/src/app/admin-console/icons/business-unit-portal-logo.icon.ts b/apps/web/src/app/admin-console/icons/business-unit-portal-logo.icon.ts new file mode 100644 index 00000000000..f913ee68ef0 --- /dev/null +++ b/apps/web/src/app/admin-console/icons/business-unit-portal-logo.icon.ts @@ -0,0 +1,33 @@ +import { svgIcon } from "@bitwarden/components"; + +export const BusinessUnitPortalLogo = svgIcon` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/apps/web/src/app/admin-console/icons/provider-portal-logo.ts b/apps/web/src/app/admin-console/icons/provider-portal-logo.ts index 16f7b05d671..bc77c3f7902 100644 --- a/apps/web/src/app/admin-console/icons/provider-portal-logo.ts +++ b/apps/web/src/app/admin-console/icons/provider-portal-logo.ts @@ -1,5 +1,29 @@ import { svgIcon } from "@bitwarden/components"; export const ProviderPortalLogo = svgIcon` - + + + + + + + + + + + + + + + + + + + + + + + + + `; diff --git a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts index 10f2d483386..264e37c6bd3 100644 --- a/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts +++ b/apps/web/src/app/admin-console/organizations/core/views/organization-user-admin-view.ts @@ -17,6 +17,7 @@ export class OrganizationUserAdminView { type: OrganizationUserType; status: OrganizationUserStatusType; externalId: string; + ssoExternalId: string; permissions: PermissionsApi; resetPasswordEnrolled: boolean; hasMasterPassword: boolean; @@ -39,6 +40,7 @@ export class OrganizationUserAdminView { view.type = response.type; view.status = response.status; view.externalId = response.externalId; + view.ssoExternalId = response.ssoExternalId; view.permissions = response.permissions; view.resetPasswordEnrolled = response.resetPasswordEnrolled; view.collections = response.collections.map((c) => ({ diff --git a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html index db7422bd34b..8a6534c6c5a 100644 --- a/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html +++ b/apps/web/src/app/admin-console/organizations/members/components/member-dialog/member-dialog.component.html @@ -177,11 +177,17 @@ - + {{ "externalId" | i18n }} {{ "externalIdDesc" | i18n }} + + + {{ "ssoExternalId" | i18n }} + + {{ "ssoExternalIdDesc" | i18n }} +
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 fac5d606946..bc7dff3e70b 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 @@ -125,6 +125,7 @@ export class MemberDialogComponent implements OnDestroy { emails: [""], type: OrganizationUserType.User, externalId: this.formBuilder.control({ value: "", disabled: true }), + ssoExternalId: this.formBuilder.control({ value: "", disabled: true }), accessSecretsManager: false, access: [[] as AccessItemValue[]], groups: [[] as AccessItemValue[]], @@ -155,6 +156,22 @@ export class MemberDialogComponent implements OnDestroy { FeatureFlag.AccountDeprovisioning, ); + protected isExternalIdVisible$ = this.configService + .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) + .pipe( + map((isEnabled) => { + return !isEnabled || !!this.formGroup.get("externalId")?.value; + }), + ); + + protected isSsoExternalIdVisible$ = this.configService + .getFeatureFlag$(FeatureFlag.SsoExternalIdVisibility) + .pipe( + map((isEnabled) => { + return isEnabled && !!this.formGroup.get("ssoExternalId")?.value; + }), + ); + private destroy$ = new Subject(); get customUserTypeSelected(): boolean { @@ -402,6 +419,7 @@ export class MemberDialogComponent implements OnDestroy { this.formGroup.patchValue({ type: userDetails.type, externalId: userDetails.externalId, + ssoExternalId: userDetails.ssoExternalId, access: accessSelections, accessSecretsManager: userDetails.accessSecretsManager, groups: groupAccessSelections, diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts index 2a2068d581f..bfff3b2aa2e 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings.module.ts @@ -1,5 +1,7 @@ import { NgModule } from "@angular/core"; +import { ItemModule } from "@bitwarden/components"; + import { LooseComponentsModule, SharedModule } from "../../../shared"; import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component"; import { PoliciesModule } from "../../organizations/policies"; @@ -15,6 +17,7 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component"; PoliciesModule, OrganizationSettingsRoutingModule, AccountFingerprintComponent, + ItemModule, ], declarations: [AccountComponent, TwoFactorSetupComponent], }) diff --git a/apps/web/src/app/auth/core/services/link-sso.service.spec.ts b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts index 70b52999875..a9f2a50ba57 100644 --- a/apps/web/src/app/auth/core/services/link-sso.service.spec.ts +++ b/apps/web/src/app/auth/core/services/link-sso.service.spec.ts @@ -4,7 +4,7 @@ import { BehaviorSubject } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoPreValidateResponse } from "@bitwarden/common/auth/models/response/sso-pre-validate.response"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/web/src/app/auth/core/services/link-sso.service.ts b/apps/web/src/app/auth/core/services/link-sso.service.ts index 3d51525add1..41de783efa2 100644 --- a/apps/web/src/app/auth/core/services/link-sso.service.ts +++ b/apps/web/src/app/auth/core/services/link-sso.service.ts @@ -2,7 +2,7 @@ import { firstValueFrom } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { Utils } from "@bitwarden/common/platform/misc/utils"; diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts index 57ea5e8ee05..95ddc74c3c5 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.spec.ts @@ -10,7 +10,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { ResetPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/reset-password-policy-options"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts index 8d4c3bd84f0..c644f26dd90 100644 --- a/apps/web/src/app/auth/core/services/login/web-login-component.service.ts +++ b/apps/web/src/app/auth/core/services/login/web-login-component.service.ts @@ -15,7 +15,7 @@ import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; +import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts index 19e01af4b0b..75a97661311 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-recovery.component.ts @@ -1,36 +1,50 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, Inject } from "@angular/core"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components"; +import { + ButtonModule, + DIALOG_DATA, + DialogConfig, + DialogModule, + DialogRef, + DialogService, + TypographyModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; @Component({ selector: "app-two-factor-recovery", templateUrl: "two-factor-recovery.component.html", + standalone: true, + imports: [CommonModule, DialogModule, ButtonModule, TypographyModule, I18nPipe], }) export class TwoFactorRecoveryComponent { type = -1; - code: string; - authed: boolean; + code: string = ""; + authed: boolean = false; twoFactorProviderType = TwoFactorProviderType; constructor( - @Inject(DIALOG_DATA) protected data: any, + @Inject(DIALOG_DATA) protected data: { response: { response: TwoFactorRecoverResponse } }, private i18nService: I18nService, ) { this.auth(data.response); } - auth(authResponse: any) { + auth(authResponse: { response: TwoFactorRecoverResponse }) { this.authed = true; this.processResponse(authResponse.response); } print() { const w = window.open(); + if (!w) { + // return early if the window is not open + return; + } w.document.write( '
' + "

" + @@ -47,9 +61,9 @@ export class TwoFactorRecoveryComponent { w.print(); } - private formatString(s: string) { + private formatString(s: string): string { if (s == null) { - return null; + return ""; } return s .replace(/(.{4})/g, "$1 ") @@ -61,7 +75,13 @@ export class TwoFactorRecoveryComponent { this.code = this.formatString(response.code); } - static open(dialogService: DialogService, config: DialogConfig) { + static open( + dialogService: DialogService, + config: DialogConfig< + { response: { response: TwoFactorRecoverResponse } }, + DialogRef + >, + ) { return dialogService.open(TwoFactorRecoveryComponent, config); } } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html index a31d4c33458..1595c0350d0 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-authenticator.component.html @@ -41,7 +41,7 @@ {{ "twoStepAuthenticatorInstructionSuffix" | i18n }}

-

+

- + {{ "twoStepLoginProviderEnabled" | i18n }} - + Duo logo {{ "twoFactorDuoClientId" | i18n }}: {{ clientId }}
diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts index 833ce5c1cb8..bada3301a97 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-duo.component.ts @@ -1,7 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; @@ -13,18 +12,41 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { + AsyncActionsModule, + ButtonModule, + CalloutModule, DIALOG_DATA, DialogConfig, + DialogModule, DialogRef, DialogService, + FormFieldModule, + IconModule, + InputModule, ToastService, + TypographyModule, } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; @Component({ selector: "app-two-factor-setup-duo", templateUrl: "two-factor-setup-duo.component.html", + standalone: true, + imports: [ + CommonModule, + DialogModule, + FormFieldModule, + InputModule, + TypographyModule, + ButtonModule, + IconModule, + I18nPipe, + ReactiveFormsModule, + AsyncActionsModule, + CalloutModule, + ], }) export class TwoFactorSetupDuoComponent extends TwoFactorSetupMethodBaseComponent @@ -63,23 +85,23 @@ export class TwoFactorSetupDuoComponent ); } - get clientId() { - return this.formGroup.get("clientId").value; + get clientId(): string { + return this.formGroup.get("clientId")?.value || ""; } - get clientSecret() { - return this.formGroup.get("clientSecret").value; + get clientSecret(): string { + return this.formGroup.get("clientSecret")?.value || ""; } - get host() { - return this.formGroup.get("host").value; + get host(): string { + return this.formGroup.get("host")?.value || ""; } set clientId(value: string) { - this.formGroup.get("clientId").setValue(value); + this.formGroup.get("clientId")?.setValue(value); } set clientSecret(value: string) { - this.formGroup.get("clientSecret").setValue(value); + this.formGroup.get("clientSecret")?.setValue(value); } set host(value: string) { - this.formGroup.get("host").setValue(value); + this.formGroup.get("host")?.setValue(value); } async ngOnInit() { @@ -147,7 +169,10 @@ export class TwoFactorSetupDuoComponent dialogService: DialogService, config: DialogConfig, ) => { - return dialogService.open(TwoFactorSetupDuoComponent, config); + return dialogService.open( + TwoFactorSetupDuoComponent, + config as DialogConfig>, + ); }; } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts index 40f50a33937..c5692c3f080 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-email.component.ts @@ -1,7 +1,6 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; +import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms"; import { firstValueFrom, map } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -16,19 +15,41 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { + AsyncActionsModule, + ButtonModule, + CalloutModule, DIALOG_DATA, DialogConfig, + DialogModule, DialogRef, DialogService, + FormFieldModule, + IconModule, + InputModule, ToastService, + TypographyModule, } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component"; @Component({ selector: "app-two-factor-setup-email", templateUrl: "two-factor-setup-email.component.html", - outputs: ["onUpdated"], + standalone: true, + imports: [ + AsyncActionsModule, + ButtonModule, + CalloutModule, + CommonModule, + DialogModule, + FormFieldModule, + IconModule, + I18nPipe, + InputModule, + ReactiveFormsModule, + TypographyModule, + ], }) export class TwoFactorSetupEmailComponent extends TwoFactorSetupMethodBaseComponent @@ -36,8 +57,8 @@ export class TwoFactorSetupEmailComponent { @Output() onChangeStatus: EventEmitter = new EventEmitter(); type = TwoFactorProviderType.Email; - sentEmail: string; - emailPromise: Promise; + sentEmail: string = ""; + emailPromise: Promise | undefined; override componentName = "app-two-factor-email"; formGroup = this.formBuilder.group({ token: ["", [Validators.required]], @@ -67,17 +88,17 @@ export class TwoFactorSetupEmailComponent toastService, ); } - get token() { - return this.formGroup.get("token").value; + get token(): string { + return this.formGroup.get("token")?.value || ""; } - set token(value: string) { - this.formGroup.get("token").setValue(value); + set token(value: string | null) { + this.formGroup.get("token")?.setValue(value || ""); } - get email() { - return this.formGroup.get("email").value; + get email(): string { + return this.formGroup.get("email")?.value || ""; } - set email(value: string) { - this.formGroup.get("email").setValue(value); + set email(value: string | null | undefined) { + this.formGroup.get("email")?.setValue(value || ""); } async ngOnInit() { @@ -149,6 +170,9 @@ export class TwoFactorSetupEmailComponent dialogService: DialogService, config: DialogConfig>, ) { - return dialogService.open(TwoFactorSetupEmailComponent, config); + return dialogService.open>( + TwoFactorSetupEmailComponent, + config as DialogConfig, DialogRef>, + ); } } diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts index b87b92a965c..0654ad126e2 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-method-base.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { Directive, EventEmitter, Output } from "@angular/core"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -17,18 +15,20 @@ import { DialogService, ToastService } from "@bitwarden/components"; /** * Base class for two-factor setup components (ex: email, yubikey, webauthn, duo). */ -@Directive() +@Directive({ + standalone: true, +}) export abstract class TwoFactorSetupMethodBaseComponent { @Output() onUpdated = new EventEmitter(); - type: TwoFactorProviderType; - organizationId: string; + type: TwoFactorProviderType | undefined; + organizationId: string | null = null; twoFactorProviderType = TwoFactorProviderType; enabled = false; authed = false; - protected hashedSecret: string; - protected verificationType: VerificationType; + protected hashedSecret: string | undefined; + protected verificationType: VerificationType | undefined; protected componentName = ""; constructor( @@ -74,6 +74,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { try { const request = await this.buildRequestModel(TwoFactorProviderRequest); + if (this.type === undefined) { + throw new Error("Two-factor provider type is required"); + } request.type = this.type; if (this.organizationId != null) { promise = this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request); @@ -84,7 +87,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.enabled = false; this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("twoStepDisabled"), }); this.onUpdated.emit(false); @@ -105,6 +108,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { } const request = await this.buildRequestModel(TwoFactorProviderRequest); + if (this.type === undefined) { + throw new Error("Two-factor provider type is required"); + } request.type = this.type; if (this.organizationId != null) { await this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request); @@ -114,7 +120,7 @@ export abstract class TwoFactorSetupMethodBaseComponent { this.enabled = false; this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("twoStepDisabled"), }); this.onUpdated.emit(false); @@ -123,6 +129,9 @@ export abstract class TwoFactorSetupMethodBaseComponent { protected async buildRequestModel( requestClass: new () => T, ) { + if (this.hashedSecret === undefined || this.verificationType === undefined) { + throw new Error("User verification data is missing"); + } return this.userVerificationService.buildRequest( { secret: this.hashedSecret, diff --git a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html index 4d505161631..767934cf3da 100644 --- a/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html +++ b/apps/web/src/app/auth/settings/two-factor/two-factor-setup-webauthn.component.html @@ -5,23 +5,23 @@ [subtitle]="'webAuthnTitle' | i18n" > - {{ "twoStepLoginProviderEnabled" | i18n }} - - + +

{{ "twoFactorWebAuthnWarning1" | i18n }}

- + FIDO2 WebAuthn logo