From acd3ab05f6b2335b5dd284b0da8f079fb9a3b87c Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:39:37 +0100 Subject: [PATCH 01/13] [PM-7100] Remove conditional routing for extension refresh (#12485) * Remove conditional routing for new vault page (header/footer) Redirect tabs/current to tabs/vault (new home) * Remove unused TabsComponent --------- Co-authored-by: Daniel James Smith --- apps/browser/src/popup/app-routing.module.ts | 19 +++---- apps/browser/src/popup/app.module.ts | 2 - apps/browser/src/popup/tabs.component.html | 57 -------------------- apps/browser/src/popup/tabs.component.ts | 15 ------ 4 files changed, 7 insertions(+), 86 deletions(-) delete mode 100644 apps/browser/src/popup/tabs.component.html delete mode 100644 apps/browser/src/popup/tabs.component.ts diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index ad839bbd7ce..73147cace23 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -105,10 +105,8 @@ import { clearVaultStateGuard } from "../vault/guards/clear-vault-state.guard"; import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; import { CollectionsComponent } from "../vault/popup/components/vault/collections.component"; -import { CurrentTabComponent } from "../vault/popup/components/vault/current-tab.component"; import { PasswordHistoryComponent } from "../vault/popup/components/vault/password-history.component"; import { ShareComponent } from "../vault/popup/components/vault/share.component"; -import { VaultFilterComponent } from "../vault/popup/components/vault/vault-filter.component"; import { VaultItemsComponent } from "../vault/popup/components/vault/vault-items.component"; import { VaultV2Component } from "../vault/popup/components/vault/vault-v2.component"; import { ViewComponent } from "../vault/popup/components/vault/view.component"; @@ -130,7 +128,6 @@ import { VaultSettingsComponent } from "../vault/popup/settings/vault-settings.c import { RouteElevation } from "./app-routing.animations"; import { debounceNavigationGuard } from "./services/debounce-navigation.service"; import { TabsV2Component } from "./tabs-v2.component"; -import { TabsComponent } from "./tabs.component"; /** * Data properties acceptable for use in extension route objects @@ -748,8 +745,9 @@ const routes: Routes = [ }, ], }, - ...extensionRefreshSwap(TabsComponent, TabsV2Component, { + { path: "tabs", + component: TabsV2Component, data: { elevation: 0 } satisfies RouteDataProperties, children: [ { @@ -759,18 +757,15 @@ const routes: Routes = [ }, { path: "current", - component: CurrentTabComponent, - canActivate: [authGuard], - canMatch: [extensionRefreshRedirect("/tabs/vault")], - data: { elevation: 0 } satisfies RouteDataProperties, - runGuardsAndResolvers: "always", + redirectTo: "/tabs/vault", }, - ...extensionRefreshSwap(VaultFilterComponent, VaultV2Component, { + { path: "vault", + component: VaultV2Component, canActivate: [authGuard, NewDeviceVerificationNoticeGuard], canDeactivate: [clearVaultStateGuard], data: { elevation: 0 } satisfies RouteDataProperties, - }), + }, ...extensionRefreshSwap(GeneratorComponent, CredentialGeneratorComponent, { path: "generator", canActivate: [authGuard], @@ -788,7 +783,7 @@ const routes: Routes = [ data: { elevation: 0 } satisfies RouteDataProperties, }), ], - }), + }, { path: "account-switcher", component: AccountSwitcherComponent, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 76bd06565c7..4695f0820b2 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -88,7 +88,6 @@ import { AppComponent } from "./app.component"; import { UserVerificationComponent } from "./components/user-verification.component"; import { ServicesModule } from "./services/services.module"; import { TabsV2Component } from "./tabs-v2.component"; -import { TabsComponent } from "./tabs.component"; // Register the locales for the application import "../platform/popup/locales"; @@ -177,7 +176,6 @@ import "../platform/popup/locales"; ShareComponent, SsoComponentV1, SyncComponent, - TabsComponent, TabsV2Component, TwoFactorComponent, TwoFactorOptionsComponent, diff --git a/apps/browser/src/popup/tabs.component.html b/apps/browser/src/popup/tabs.component.html deleted file mode 100644 index fd04967b914..00000000000 --- a/apps/browser/src/popup/tabs.component.html +++ /dev/null @@ -1,57 +0,0 @@ -
- - -
diff --git a/apps/browser/src/popup/tabs.component.ts b/apps/browser/src/popup/tabs.component.ts deleted file mode 100644 index 7546c9ca13b..00000000000 --- a/apps/browser/src/popup/tabs.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from "@angular/core"; - -import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; - -@Component({ - selector: "app-tabs", - templateUrl: "tabs.component.html", -}) -export class TabsComponent implements OnInit { - showCurrentTab = true; - - ngOnInit() { - this.showCurrentTab = !BrowserPopupUtils.inPopout(window); - } -} From b27a1a5337f765ee3fa9c07f51083c29c69a68da Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:44:36 -0600 Subject: [PATCH 02/13] [PM-12998] View Cipher: Color Password (#12354) * show color password for visible passwords in vault view - The password input will be visually hidden - Adds tests for the login credentials component * formatting --- .../login-credentials-view.component.html | 23 +- .../login-credentials-view.component.spec.ts | 198 ++++++++++++++++++ 2 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.spec.ts diff --git a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html index 5b6b995d095..8503604bf7c 100644 --- a/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html +++ b/libs/vault/src/cipher-view/login-credentials/login-credentials-view.component.html @@ -28,17 +28,34 @@

{{ "loginCredentials" | i18n }}

> - {{ "password" | i18n }} + + {{ "password" | i18n }} + + + + + + + + +
  • +
    + + +
    +
  • + + +`; + exports[`AutofillInlineMenuList initAutofillInlineMenuList the locked inline menu for an unauthenticated user creates the views for the locked inline menu 1`] = `
    { expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); }); + it("renders correctly when there are multiple TOTP elements with username displayed", async () => { + const totpCipher1 = createAutofillOverlayCipherDataMock(1, { + type: CipherType.Login, + login: { + totp: "123456", + totpField: true, + username: "user1", + }, + }); + + const totpCipher2 = createAutofillOverlayCipherDataMock(2, { + type: CipherType.Login, + login: { + totp: "654321", + totpField: true, + username: "user2", + }, + }); + + postWindowMessage( + createInitAutofillInlineMenuListMessageMock({ + inlineMenuFillType: CipherType.Login, + ciphers: [totpCipher1, totpCipher2], + }), + ); + + await flushPromises(); + const checkSubtitleElement = (username: string) => { + const subtitleElement = autofillInlineMenuList["inlineMenuListContainer"].querySelector( + `span.cipher-subtitle[title="${username}"]`, + ); + expect(subtitleElement).not.toBeNull(); + expect(subtitleElement.textContent).toBe(username); + }; + + checkSubtitleElement("user1"); + checkSubtitleElement("user2"); + + expect(autofillInlineMenuList["inlineMenuListContainer"]).toMatchSnapshot(); + }); + it("creates the view for a totp field", () => { postWindowMessage( createInitAutofillInlineMenuListMessageMock({ diff --git a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts index 6cf390d0a29..b7837505d41 100644 --- a/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts +++ b/apps/browser/src/autofill/overlay/inline-menu/pages/list/autofill-inline-menu-list.ts @@ -1163,7 +1163,7 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { } if (cipher.login?.totpField && cipher.login?.totp) { - return this.buildTotpElement(cipher.login?.totp); + return this.buildTotpElement(cipher.login?.totp, cipher.login?.username); } const subTitleText = this.getSubTitleText(cipher); const cipherSubtitleElement = this.buildCipherSubtitleElement(subTitleText); @@ -1174,13 +1174,24 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { return cipherDetailsElement; } + /** + * Checks if there is more than one TOTP element being displayed. + * + * @returns {boolean} - Returns true if more than one TOTP element is displayed, otherwise false. + */ + private multipleTotpElements(): boolean { + return ( + this.ciphers.filter((cipher) => cipher.login?.totpField && cipher.login?.totp).length > 1 + ); + } + /** * Builds a TOTP element for a given TOTP code. * * @param totp - The TOTP code to display. */ - private buildTotpElement(totpCode: string): HTMLDivElement | null { + private buildTotpElement(totpCode: string, username?: string): HTMLDivElement | null { if (!totpCode) { return null; } @@ -1196,12 +1207,17 @@ export class AutofillInlineMenuList extends AutofillInlineMenuPageElement { containerElement.appendChild(totpHeading); - const subtitleElement = document.createElement("span"); - subtitleElement.classList.add("cipher-subtitle"); - subtitleElement.textContent = formattedTotpCode; - subtitleElement.setAttribute("aria-label", this.getTranslation("totpCodeAria")); - subtitleElement.setAttribute("data-testid", "totp-code"); - containerElement.appendChild(subtitleElement); + if (this.multipleTotpElements() && username) { + const usernameSubtitle = this.buildCipherSubtitleElement(username); + containerElement.appendChild(usernameSubtitle); + } + + const totpCodeSpan = document.createElement("span"); + totpCodeSpan.classList.add("cipher-subtitle"); + totpCodeSpan.textContent = formattedTotpCode; + totpCodeSpan.setAttribute("aria-label", this.getTranslation("totpCodeAria")); + totpCodeSpan.setAttribute("data-testid", "totp-code"); + containerElement.appendChild(totpCodeSpan); return containerElement; } From 2e6031eee90631274864f9b9ba65e935132ed32f Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Fri, 20 Dec 2024 17:36:04 +0100 Subject: [PATCH 05/13] Increase heap size for builds (#12483) * Increase heap size for builds * Add cross-env to cli * Add cross-env to desktop * Update apps/web/package.json Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --------- Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> --- apps/browser/package.json | 10 +++++----- apps/cli/package.json | 4 ++-- apps/desktop/package.json | 2 +- apps/web/package.json | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/browser/package.json b/apps/browser/package.json index 2b3edd96b6e..7c839ecc3d9 100644 --- a/apps/browser/package.json +++ b/apps/browser/package.json @@ -14,11 +14,11 @@ "build:watch:firefox": "npm run build:firefox -- --watch", "build:watch:opera": "npm run build:opera -- --watch", "build:watch:safari": "npm run build:safari -- --watch", - "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:chrome", - "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:edge", - "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:firefox", - "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:opera", - "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build:safari", + "build:prod:chrome": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:chrome", + "build:prod:edge": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:edge", + "build:prod:firefox": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:firefox", + "build:prod:opera": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:opera", + "build:prod:safari": "cross-env NODE_ENV=production NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build:safari", "dist:chrome": "npm run build:prod:chrome && mkdir -p dist && ./scripts/compress.ps1 dist-chrome.zip", "dist:edge": "npm run build:prod:edge && mkdir -p dist && ./scripts/compress.ps1 dist-edge.zip", "dist:firefox": "npm run build:prod:firefox && mkdir -p dist && ./scripts/compress.ps1 dist-firefox.zip", diff --git a/apps/cli/package.json b/apps/cli/package.json index b7d54e78e1d..b25f327c42a 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -18,14 +18,14 @@ "license": "SEE LICENSE IN LICENSE.txt", "scripts": { "clean": "rimraf dist", - "build:oss": "webpack", + "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", "build:oss:debug": "npm run build:oss && node --inspect ./build/bw.js", "build:oss:watch": "webpack --watch", "build:oss:prod": "cross-env NODE_ENV=production webpack", "build:oss:prod:watch": "cross-env NODE_ENV=production webpack --watch", "debug": "node --inspect ./build/bw.js", "publish:npm": "npm run build:oss:prod && npm publish --access public", - "build:bit": "webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", + "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:debug": "npm run build:bit && node --inspect ./build/bw.js", "build:bit:watch": "webpack --watch -c ../../bitwarden_license/bit-cli/webpack.config.js", "build:bit:prod": "cross-env NODE_ENV=production npm run build:bit", diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 10d5ded3448..7c6d1f37692 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -19,7 +19,7 @@ "postinstall": "electron-rebuild", "start": "cross-env ELECTRON_IS_DEV=0 ELECTRON_NO_UPDATER=1 electron ./build", "build-native": "cd desktop_native && node build.js", - "build": "concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", + "build": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" concurrently -n Main,Rend,Prel -c yellow,cyan \"npm run build:main\" \"npm run build:renderer\" \"npm run build:preload\"", "build:dev": "concurrently -n Main,Rend -c yellow,cyan \"npm run build:main:dev\" \"npm run build:renderer:dev\"", "build:preload": "cross-env NODE_ENV=production webpack --config webpack.preload.js", "build:preload:watch": "cross-env NODE_ENV=production webpack --config webpack.preload.js --watch", diff --git a/apps/web/package.json b/apps/web/package.json index dee8e7722c9..f2697e1b68d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,8 +2,8 @@ "name": "@bitwarden/web-vault", "version": "2024.12.1", "scripts": { - "build:oss": "webpack", - "build:bit": "webpack -c ../../bitwarden_license/bit-web/webpack.config.js", + "build:oss": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack", + "build:bit": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" webpack -c ../../bitwarden_license/bit-web/webpack.config.js", "build:oss:watch": "webpack serve", "build:bit:watch": "webpack serve -c ../../bitwarden_license/bit-web/webpack.config.js", "build:bit:dev": "cross-env ENV=development npm run build:bit", From d209da4c945b9fddbc480c779150989596bfd90f Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Fri, 20 Dec 2024 10:23:03 -0800 Subject: [PATCH 06/13] feat(auth): [PM-9674] Remove Deprecated LockComponents (#12453) This PR deletes the legacy lock components from the Angular clients and also removes feature flag control from the routing. The lock component will now be based entirely on the new, recently refreshed LockComponent in libs/auth/angular. --- .../src/auth/popup/lock.component.html | 100 ---- apps/browser/src/auth/popup/lock.component.ts | 185 ------- apps/browser/src/popup/app-routing.module.ts | 21 +- apps/browser/src/popup/app.module.ts | 2 - apps/desktop/src/app/app-routing.module.ts | 16 +- apps/desktop/src/app/app.module.ts | 2 - apps/desktop/src/auth/lock.component.html | 80 --- apps/desktop/src/auth/lock.component.spec.ts | 478 ------------------ apps/desktop/src/auth/lock.component.ts | 235 --------- apps/web/src/app/auth/lock.component.html | 27 - apps/web/src/app/auth/lock.component.ts | 51 -- apps/web/src/app/oss-routing.module.ts | 59 +-- .../src/auth/components/lock.component.ts | 398 --------------- libs/auth/src/angular/lock/lock.component.ts | 6 +- 14 files changed, 31 insertions(+), 1629 deletions(-) delete mode 100644 apps/browser/src/auth/popup/lock.component.html delete mode 100644 apps/browser/src/auth/popup/lock.component.ts delete mode 100644 apps/desktop/src/auth/lock.component.html delete mode 100644 apps/desktop/src/auth/lock.component.spec.ts delete mode 100644 apps/desktop/src/auth/lock.component.ts delete mode 100644 apps/web/src/app/auth/lock.component.html delete mode 100644 apps/web/src/app/auth/lock.component.ts delete mode 100644 libs/angular/src/auth/components/lock.component.ts diff --git a/apps/browser/src/auth/popup/lock.component.html b/apps/browser/src/auth/popup/lock.component.html deleted file mode 100644 index fb1b09de49c..00000000000 --- a/apps/browser/src/auth/popup/lock.component.html +++ /dev/null @@ -1,100 +0,0 @@ -
    - -
    -

    - {{ "verifyIdentity" | i18n }} -

    -
    - -
    -
    -
    - -
    -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    - -
    -

    - -

    - {{ biometricError }} -

    - {{ "awaitDesktop" | i18n }} -

    - - -
    -
    -
    diff --git a/apps/browser/src/auth/popup/lock.component.ts b/apps/browser/src/auth/popup/lock.component.ts deleted file mode 100644 index 66de3fb89d2..00000000000 --- a/apps/browser/src/auth/popup/lock.component.ts +++ /dev/null @@ -1,185 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - KdfConfigService, - KeyService, - BiometricsService, - BiometricStateService, -} from "@bitwarden/key-management"; - -import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors"; -import { BrowserRouterService } from "../../platform/popup/services/browser-router.service"; -import { fido2PopoutSessionData$ } from "../../vault/popup/utils/fido2-popout-session-data"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", -}) -export class LockComponent extends BaseLockComponent implements OnInit { - private isInitialLockScreen: boolean; - - biometricError: string; - pendingBiometric = false; - fido2PopoutSessionData$ = fido2PopoutSessionData$(); - - constructor( - masterPasswordService: InternalMasterPasswordServiceAbstraction, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - messagingService: MessagingService, - keyService: KeyService, - vaultTimeoutService: VaultTimeoutService, - vaultTimeoutSettingsService: VaultTimeoutSettingsService, - environmentService: EnvironmentService, - stateService: StateService, - apiService: ApiService, - logService: LogService, - ngZone: NgZone, - policyApiService: PolicyApiServiceAbstraction, - policyService: InternalPolicyService, - passwordStrengthService: PasswordStrengthServiceAbstraction, - authService: AuthService, - dialogService: DialogService, - deviceTrustService: DeviceTrustServiceAbstraction, - userVerificationService: UserVerificationService, - pinService: PinServiceAbstraction, - private routerService: BrowserRouterService, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, - accountService: AccountService, - kdfConfigService: KdfConfigService, - syncService: SyncService, - toastService: ToastService, - ) { - super( - masterPasswordService, - router, - i18nService, - platformUtilsService, - messagingService, - keyService, - vaultTimeoutService, - vaultTimeoutSettingsService, - environmentService, - stateService, - apiService, - logService, - ngZone, - policyApiService, - policyService, - passwordStrengthService, - dialogService, - deviceTrustService, - userVerificationService, - pinService, - biometricStateService, - biometricsService, - accountService, - authService, - kdfConfigService, - syncService, - toastService, - ); - this.successRoute = "/tabs/current"; - this.isInitialLockScreen = (window as any).previousPopupUrl == null; - - this.onSuccessfulSubmit = async () => { - const previousUrl = this.routerService.getPreviousUrl(); - if (previousUrl) { - // 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.navigateByUrl(previousUrl); - } else { - // 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([this.successRoute]); - } - }; - } - - async ngOnInit() { - await super.ngOnInit(); - const autoBiometricsPrompt = await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ); - - window.setTimeout(async () => { - document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus(); - if ( - this.biometricLock && - autoBiometricsPrompt && - this.isInitialLockScreen && - (await this.authService.getAuthStatus()) === AuthenticationStatus.Locked - ) { - await this.unlockBiometric(true); - } - }, 100); - } - - override async unlockBiometric(automaticPrompt: boolean = false): Promise { - if (!this.biometricLock) { - return; - } - - this.biometricError = null; - - let success; - try { - const available = await super.isBiometricUnlockAvailable(); - if (!available) { - if (!automaticPrompt) { - await this.dialogService.openSimpleDialog({ - type: "warning", - title: { key: "biometricsNotAvailableTitle" }, - content: { key: "biometricsNotAvailableDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - }); - } - } else { - this.pendingBiometric = true; - success = await super.unlockBiometric(); - } - } catch (e) { - const error = BiometricErrors[e?.message as BiometricErrorTypes]; - - if (error == null) { - this.logService.error("Unknown error: " + e); - return false; - } - - this.biometricError = this.i18nService.t(error.description); - } finally { - this.pendingBiometric = false; - } - - return success; - } -} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 73147cace23..549a677857c 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -17,7 +17,6 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { extensionRefreshRedirect } from "@bitwarden/angular/utils/extension-refresh-redirect"; import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { @@ -26,7 +25,7 @@ import { LoginComponent, LoginSecondaryContentComponent, LockIcon, - LockV2Component, + LockComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, @@ -60,7 +59,6 @@ import { } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; import { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; -import { LockComponent } from "../auth/popup/lock.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; @@ -173,13 +171,6 @@ const routes: Routes = [ canActivate: [fido2AuthGuard], data: { elevation: 1 } satisfies RouteDataProperties, }), - { - path: "lock", - component: LockComponent, - canActivate: [lockGuard()], - canMatch: [extensionRefreshRedirect("/lockV2")], - data: { elevation: 1, doNotSaveUrl: true } satisfies RouteDataProperties, - }, ...twofactorRefactorSwap( TwoFactorComponent, AnonLayoutWrapperComponent, @@ -650,8 +641,8 @@ const routes: Routes = [ ], }, { - path: "lockV2", - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()], + path: "lock", + canActivate: [lockGuard()], data: { pageIcon: LockIcon, pageTitle: { @@ -662,19 +653,19 @@ const routes: Routes = [ elevation: 1, /** * This ensures that in a passkey flow the `/fido2?` URL does not get - * overwritten in the `BrowserRouterService` by the `/lockV2` route. This way, after + * overwritten in the `BrowserRouterService` by the `/lock` route. This way, after * unlocking, the user can be redirected back to the `/fido2?` URL. * * Also, this prevents a routing loop when using biometrics to unlock the vault in MV2 (Firefox), * locking up the browser (https://bitwarden.atlassian.net/browse/PM-16116). This involves the - * `popup-router-cache.service` pushing the `lockV2` route to the history. + * `popup-router-cache.service` pushing the `lock` route to the history. */ doNotSaveUrl: true, } satisfies ExtensionAnonLayoutWrapperData & RouteDataProperties, children: [ { path: "", - component: LockV2Component, + component: LockComponent, }, ], }, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 4695f0820b2..3d8cb267798 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -23,7 +23,6 @@ import { EnvironmentComponent } from "../auth/popup/environment.component"; import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; import { HintComponent } from "../auth/popup/hint.component"; import { HomeComponent } from "../auth/popup/home.component"; -import { LockComponent } from "../auth/popup/lock.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/popup/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; @@ -156,7 +155,6 @@ import "../platform/popup/locales"; VaultFilterComponent, HintComponent, HomeComponent, - LockComponent, LoginViaAuthRequestComponentV1, LoginComponentV1, LoginDecryptionOptionsComponentV1, diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index c7642638dc3..7e82bb004fa 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -15,7 +15,6 @@ import { unauthGuardFn, } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { extensionRefreshRedirect } from "@bitwarden/angular/utils/extension-refresh-redirect"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, @@ -23,7 +22,7 @@ import { LoginComponent, LoginSecondaryContentComponent, LockIcon, - LockV2Component, + LockComponent, LoginViaAuthRequestComponent, PasswordHintComponent, RegistrationFinishComponent, @@ -51,7 +50,6 @@ import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-fa import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { HintComponent } from "../auth/hint.component"; -import { LockComponent } from "../auth/lock.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/login/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "../auth/login/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/login/login-via-auth-request-v1.component"; @@ -81,12 +79,6 @@ const routes: Routes = [ children: [], // Children lets us have an empty component. canActivate: [redirectGuard({ loggedIn: "/vault", loggedOut: "/login", locked: "/lock" })], }, - { - path: "lock", - component: LockComponent, - canActivate: [lockGuard()], - canMatch: [extensionRefreshRedirect("/lockV2")], - }, ...twofactorRefactorSwap( TwoFactorComponent, AnonLayoutWrapperComponent, @@ -373,8 +365,8 @@ const routes: Routes = [ ], }, { - path: "lockV2", - canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh), lockGuard()], + path: "lock", + canActivate: [lockGuard()], data: { pageIcon: LockIcon, pageTitle: { @@ -385,7 +377,7 @@ const routes: Routes = [ children: [ { path: "", - component: LockV2Component, + component: LockComponent, }, ], }, diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 5bd1c66b87c..5b9a1e3539d 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -13,7 +13,6 @@ import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.compo import { DeleteAccountComponent } from "../auth/delete-account.component"; import { EnvironmentComponent } from "../auth/environment.component"; import { HintComponent } from "../auth/hint.component"; -import { LockComponent } from "../auth/lock.component"; import { LoginModule } from "../auth/login/login.module"; import { RegisterComponent } from "../auth/register.component"; import { RemovePasswordComponent } from "../auth/remove-password.component"; @@ -78,7 +77,6 @@ import { SendComponent } from "./tools/send/send.component"; FolderAddEditComponent, HeaderComponent, HintComponent, - LockComponent, NavComponent, GeneratorComponent, PasswordGeneratorHistoryComponent, diff --git a/apps/desktop/src/auth/lock.component.html b/apps/desktop/src/auth/lock.component.html deleted file mode 100644 index 895eda91e83..00000000000 --- a/apps/desktop/src/auth/lock.component.html +++ /dev/null @@ -1,80 +0,0 @@ -
    -
    - -

    {{ "yourVaultIsLocked" | i18n }}

    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - -
    -
    -
    - -
    -
    -
    - -
    -
    - - -
    -
    -
    -
    diff --git a/apps/desktop/src/auth/lock.component.spec.ts b/apps/desktop/src/auth/lock.component.spec.ts deleted file mode 100644 index 4e59acf89c1..00000000000 --- a/apps/desktop/src/auth/lock.component.spec.ts +++ /dev/null @@ -1,478 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing"; -import { ActivatedRoute } from "@angular/router"; -import { MockProxy, mock } from "jest-mock-extended"; -import { of } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { UserId } from "@bitwarden/common/types/guid"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - KdfConfigService, - KeyService, - BiometricsService as AbstractBiometricService, - BiometricStateService, -} from "@bitwarden/key-management"; - -import { BiometricsService } from "../key-management/biometrics/biometrics.service"; - -import { LockComponent } from "./lock.component"; - -// ipc mock global -const isWindowVisibleMock = jest.fn(); -(global as any).ipc = { - platform: { - isWindowVisible: isWindowVisibleMock, - }, - keyManagement: { - biometric: { - enabled: jest.fn(), - }, - }, -}; - -describe("LockComponent", () => { - let component: LockComponent; - let fixture: ComponentFixture; - let stateServiceMock: MockProxy; - let biometricStateService: MockProxy; - let biometricsService: MockProxy; - let messagingServiceMock: MockProxy; - let broadcasterServiceMock: MockProxy; - let platformUtilsServiceMock: MockProxy; - let activatedRouteMock: MockProxy; - let mockMasterPasswordService: FakeMasterPasswordService; - let mockToastService: MockProxy; - - const mockUserId = Utils.newGuid() as UserId; - const accountService: FakeAccountService = mockAccountServiceWith(mockUserId); - - beforeEach(async () => { - stateServiceMock = mock(); - - messagingServiceMock = mock(); - broadcasterServiceMock = mock(); - platformUtilsServiceMock = mock(); - mockToastService = mock(); - - activatedRouteMock = mock(); - activatedRouteMock.queryParams = mock(); - - mockMasterPasswordService = new FakeMasterPasswordService(); - - biometricStateService = mock(); - biometricStateService.dismissedRequirePasswordOnStartCallout$ = of(false); - biometricStateService.promptAutomatically$ = of(false); - biometricStateService.promptCancelled$ = of(false); - - await TestBed.configureTestingModule({ - declarations: [LockComponent, I18nPipe], - providers: [ - { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService }, - { - provide: I18nService, - useValue: mock(), - }, - { - provide: PlatformUtilsService, - useValue: platformUtilsServiceMock, - }, - { - provide: MessagingService, - useValue: messagingServiceMock, - }, - { - provide: KeyService, - useValue: mock(), - }, - { - provide: VaultTimeoutService, - useValue: mock(), - }, - { - provide: VaultTimeoutSettingsService, - useValue: mock(), - }, - { - provide: EnvironmentService, - useValue: mock(), - }, - { - provide: StateService, - useValue: stateServiceMock, - }, - { - provide: ApiService, - useValue: mock(), - }, - { - provide: ActivatedRoute, - useValue: activatedRouteMock, - }, - { - provide: BroadcasterService, - useValue: broadcasterServiceMock, - }, - { - provide: PolicyApiServiceAbstraction, - useValue: mock(), - }, - { - provide: InternalPolicyService, - useValue: mock(), - }, - { - provide: PasswordStrengthServiceAbstraction, - useValue: mock(), - }, - { - provide: LogService, - useValue: mock(), - }, - { - provide: DialogService, - useValue: mock(), - }, - { - provide: DeviceTrustServiceAbstraction, - useValue: mock(), - }, - { - provide: UserVerificationService, - useValue: mock(), - }, - { - provide: PinServiceAbstraction, - useValue: mock(), - }, - { - provide: BiometricStateService, - useValue: biometricStateService, - }, - { - provide: AbstractBiometricService, - useValue: biometricsService, - }, - { - provide: AccountService, - useValue: accountService, - }, - { - provide: AuthService, - useValue: mock(), - }, - { - provide: KdfConfigService, - useValue: mock(), - }, - { - provide: SyncService, - useValue: mock(), - }, - { - provide: ToastService, - useValue: mockToastService, - }, - ], - schemas: [NO_ERRORS_SCHEMA], - }).compileComponents(); - - fixture = TestBed.createComponent(LockComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe("ngOnInit", () => { - it("should call super.ngOnInit() once", async () => { - const superNgOnInitSpy = jest.spyOn(BaseLockComponent.prototype, "ngOnInit"); - await component.ngOnInit(); - expect(superNgOnInitSpy).toHaveBeenCalledTimes(1); - }); - - it('should set "autoPromptBiometric" to true if "biometricState.promptAutomatically$" resolves to true', async () => { - biometricStateService.promptAutomatically$ = of(true); - - await component.ngOnInit(); - expect(component["autoPromptBiometric"]).toBe(true); - }); - - it('should set "autoPromptBiometric" to false if "biometricState.promptAutomatically$" resolves to false', async () => { - biometricStateService.promptAutomatically$ = of(false); - - await component.ngOnInit(); - expect(component["autoPromptBiometric"]).toBe(false); - }); - - it('should set "biometricReady" to true if "stateService.getBiometricReady()" resolves to true', async () => { - component["canUseBiometric"] = jest.fn().mockResolvedValue(true); - - await component.ngOnInit(); - expect(component["biometricReady"]).toBe(true); - }); - - it('should set "biometricReady" to false if "stateService.getBiometricReady()" resolves to false', async () => { - component["canUseBiometric"] = jest.fn().mockResolvedValue(false); - - await component.ngOnInit(); - expect(component["biometricReady"]).toBe(false); - }); - - it("should call displayBiometricUpdateWarning", async () => { - component["displayBiometricUpdateWarning"] = jest.fn(); - await component.ngOnInit(); - expect(component["displayBiometricUpdateWarning"]).toHaveBeenCalledTimes(1); - }); - - it("should call delayedAskForBiometric", async () => { - component["delayedAskForBiometric"] = jest.fn(); - await component.ngOnInit(); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledTimes(1); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledWith(500); - }); - - it("should call delayedAskForBiometric when queryParams change", async () => { - activatedRouteMock.queryParams = of({ promptBiometric: true }); - component["delayedAskForBiometric"] = jest.fn(); - await component.ngOnInit(); - - expect(component["delayedAskForBiometric"]).toHaveBeenCalledTimes(1); - expect(component["delayedAskForBiometric"]).toHaveBeenCalledWith(500); - }); - - it("should call messagingService.send", async () => { - await component.ngOnInit(); - expect(messagingServiceMock.send).toHaveBeenCalledWith("getWindowIsFocused"); - }); - - describe("broadcasterService.subscribe", () => { - it('should call onWindowHidden() when "broadcasterService.subscribe" is called with "windowHidden"', async () => { - component["onWindowHidden"] = jest.fn(); - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ command: "windowHidden" }); - expect(component["onWindowHidden"]).toHaveBeenCalledTimes(1); - }); - - it('should call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is false', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = null; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: true, - } as any); - expect(component["deferFocus"]).toBe(false); - expect(component["focusInput"]).toHaveBeenCalledTimes(1); - }); - - it('should not call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = null; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: false, - } as any); - expect(component["deferFocus"]).toBe(true); - expect(component["focusInput"]).toHaveBeenCalledTimes(0); - }); - - it('should call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is true and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = true; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: true, - } as any); - expect(component["deferFocus"]).toBe(false); - expect(component["focusInput"]).toHaveBeenCalledTimes(1); - }); - - it('should not call focusInput() when "broadcasterService.subscribe" is called with "windowIsFocused" is false and deferFocus is true', async () => { - component["focusInput"] = jest.fn(); - component["deferFocus"] = true; - await component.ngOnInit(); - broadcasterServiceMock.subscribe.mock.calls[0][1]({ - command: "windowIsFocused", - windowIsFocused: false, - } as any); - expect(component["deferFocus"]).toBe(true); - expect(component["focusInput"]).toHaveBeenCalledTimes(0); - }); - }); - }); - - describe("ngOnDestroy", () => { - it("should call super.ngOnDestroy()", () => { - const superNgOnDestroySpy = jest.spyOn(BaseLockComponent.prototype, "ngOnDestroy"); - component.ngOnDestroy(); - expect(superNgOnDestroySpy).toHaveBeenCalledTimes(1); - }); - - it("should call broadcasterService.unsubscribe()", () => { - component.ngOnDestroy(); - expect(broadcasterServiceMock.unsubscribe).toHaveBeenCalledTimes(1); - }); - }); - - describe("focusInput", () => { - it('should call "focus" on #pin input if pinEnabled is true', () => { - component["pinEnabled"] = true; - global.document.getElementById = jest.fn().mockReturnValue({ focus: jest.fn() }); - component["focusInput"](); - expect(global.document.getElementById).toHaveBeenCalledWith("pin"); - }); - - it('should call "focus" on #masterPassword input if pinEnabled is false', () => { - component["pinEnabled"] = false; - global.document.getElementById = jest.fn().mockReturnValue({ focus: jest.fn() }); - component["focusInput"](); - expect(global.document.getElementById).toHaveBeenCalledWith("masterPassword"); - }); - }); - - describe("delayedAskForBiometric", () => { - beforeEach(() => { - component["supportsBiometric"] = true; - component["autoPromptBiometric"] = true; - }); - - it('should wait for "delay" milliseconds', fakeAsync(async () => { - const delaySpy = jest.spyOn(global, "setTimeout"); - // 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 - component["delayedAskForBiometric"](5000); - - tick(4000); - component["biometricAsked"] = false; - - tick(1000); - component["biometricAsked"] = true; - - expect(delaySpy).toHaveBeenCalledWith(expect.any(Function), 5000); - })); - - it('should return; if "params" is defined and "params.promptBiometric" is false', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000, { promptBiometric: false }); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it('should not return; if "params" is defined and "params.promptBiometric" is true', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000, { promptBiometric: true }); - tick(5000); - expect(component["biometricAsked"]).toBe(true); - })); - - it('should not return; if "params" is undefined', fakeAsync(async () => { - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(true); - })); - - it('should return; if "supportsBiometric" is false', fakeAsync(async () => { - component["supportsBiometric"] = 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it('should return; if "autoPromptBiometric" is false', fakeAsync(async () => { - component["autoPromptBiometric"] = 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 - component["delayedAskForBiometric"](5000); - tick(5000); - expect(component["biometricAsked"]).toBe(false); - })); - - it("should call unlockBiometric() if biometricAsked is false and window is visible", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(true); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(1); - })); - - it("should not call unlockBiometric() if biometricAsked is false and window is not visible", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(false); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(0); - })); - - it("should not call unlockBiometric() if biometricAsked is true", fakeAsync(async () => { - isWindowVisibleMock.mockResolvedValue(true); - component["unlockBiometric"] = jest.fn(); - component["biometricAsked"] = true; - - // 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 - component["delayedAskForBiometric"](5000); - tick(5000); - - expect(component["unlockBiometric"]).toHaveBeenCalledTimes(0); - })); - }); - - describe("canUseBiometric", () => { - it("should call biometric.enabled with current active user", async () => { - await component["canUseBiometric"](); - - expect(ipc.keyManagement.biometric.enabled).toHaveBeenCalledWith(mockUserId); - }); - }); - - it('onWindowHidden() should set "showPassword" to false', () => { - component["showPassword"] = true; - component["onWindowHidden"](); - expect(component["showPassword"]).toBe(false); - }); -}); diff --git a/apps/desktop/src/auth/lock.component.ts b/apps/desktop/src/auth/lock.component.ts deleted file mode 100644 index ea2ac546ec1..00000000000 --- a/apps/desktop/src/auth/lock.component.ts +++ /dev/null @@ -1,235 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom, map, switchMap } from "rxjs"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; -import { PinServiceAbstraction } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { DeviceType } from "@bitwarden/common/enums"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - KdfConfigService, - KeyService, - BiometricsService, - BiometricStateService, -} from "@bitwarden/key-management"; - -const BroadcasterSubscriptionId = "LockComponent"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", -}) -export class LockComponent extends BaseLockComponent implements OnInit, OnDestroy { - private deferFocus: boolean = null; - protected biometricReady = false; - private biometricAsked = false; - private autoPromptBiometric = false; - private timerId: any; - - constructor( - masterPasswordService: InternalMasterPasswordServiceAbstraction, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - messagingService: MessagingService, - keyService: KeyService, - vaultTimeoutService: VaultTimeoutService, - vaultTimeoutSettingsService: VaultTimeoutSettingsService, - environmentService: EnvironmentService, - protected override stateService: StateService, - apiService: ApiService, - private route: ActivatedRoute, - private broadcasterService: BroadcasterService, - ngZone: NgZone, - policyApiService: PolicyApiServiceAbstraction, - policyService: InternalPolicyService, - passwordStrengthService: PasswordStrengthServiceAbstraction, - logService: LogService, - dialogService: DialogService, - deviceTrustService: DeviceTrustServiceAbstraction, - userVerificationService: UserVerificationService, - pinService: PinServiceAbstraction, - biometricStateService: BiometricStateService, - biometricsService: BiometricsService, - accountService: AccountService, - authService: AuthService, - kdfConfigService: KdfConfigService, - syncService: SyncService, - toastService: ToastService, - ) { - super( - masterPasswordService, - router, - i18nService, - platformUtilsService, - messagingService, - keyService, - vaultTimeoutService, - vaultTimeoutSettingsService, - environmentService, - stateService, - apiService, - logService, - ngZone, - policyApiService, - policyService, - passwordStrengthService, - dialogService, - deviceTrustService, - userVerificationService, - pinService, - biometricStateService, - biometricsService, - accountService, - authService, - kdfConfigService, - syncService, - toastService, - ); - } - - async ngOnInit() { - await super.ngOnInit(); - this.autoPromptBiometric = await firstValueFrom( - this.biometricStateService.promptAutomatically$, - ); - this.biometricReady = await this.canUseBiometric(); - - await this.displayBiometricUpdateWarning(); - - // 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.delayedAskForBiometric(500); - this.route.queryParams.pipe(switchMap((params) => this.delayedAskForBiometric(500, params))); - - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - case "windowIsFocused": - if (this.deferFocus === null) { - this.deferFocus = !message.windowIsFocused; - if (!this.deferFocus) { - this.focusInput(); - } - } else if (this.deferFocus && message.windowIsFocused) { - this.focusInput(); - this.deferFocus = false; - } - break; - default: - } - }); - }); - this.messagingService.send("getWindowIsFocused"); - - // start background listener until destroyed on interval - this.timerId = setInterval(async () => { - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.biometricReady = await this.canUseBiometric(); - }, 1000); - } - - ngOnDestroy() { - super.ngOnDestroy(); - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - clearInterval(this.timerId); - } - - onWindowHidden() { - this.showPassword = false; - } - - private async delayedAskForBiometric(delay: number, params?: any) { - await new Promise((resolve) => setTimeout(resolve, delay)); - - if (params && !params.promptBiometric) { - return; - } - - if (!this.supportsBiometric || !this.autoPromptBiometric || this.biometricAsked) { - return; - } - - if (await firstValueFrom(this.biometricStateService.promptCancelled$)) { - return; - } - - this.biometricAsked = true; - if (await ipc.platform.isWindowVisible()) { - // 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.unlockBiometric(); - } - } - - private async canUseBiometric() { - const userId = await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))); - return await ipc.keyManagement.biometric.enabled(userId); - } - - private focusInput() { - document.getElementById(this.pinEnabled ? "pin" : "masterPassword")?.focus(); - } - - private async displayBiometricUpdateWarning(): Promise { - if (await firstValueFrom(this.biometricStateService.dismissedRequirePasswordOnStartCallout$)) { - return; - } - - if (this.platformUtilsService.getDevice() !== DeviceType.WindowsDesktop) { - return; - } - - if (await firstValueFrom(this.biometricStateService.biometricUnlockEnabled$)) { - const response = await this.dialogService.openSimpleDialog({ - title: { key: "windowsBiometricUpdateWarningTitle" }, - content: { key: "windowsBiometricUpdateWarning" }, - type: "warning", - }); - - await this.biometricStateService.setRequirePasswordOnStart(response); - if (response) { - await this.biometricStateService.setPromptAutomatically(false); - } - this.supportsBiometric = await this.canUseBiometric(); - await this.biometricStateService.setDismissedRequirePasswordOnStartCallout(); - } - } - - get biometricText() { - switch (this.platformUtilsService.getDevice()) { - case DeviceType.MacOsDesktop: - return "unlockWithTouchId"; - case DeviceType.WindowsDesktop: - return "unlockWithWindowsHello"; - case DeviceType.LinuxDesktop: - return "unlockWithPolkit"; - default: - throw new Error("Unsupported platform"); - } - } -} diff --git a/apps/web/src/app/auth/lock.component.html b/apps/web/src/app/auth/lock.component.html deleted file mode 100644 index f630906223b..00000000000 --- a/apps/web/src/app/auth/lock.component.html +++ /dev/null @@ -1,27 +0,0 @@ -
    - - {{ "masterPass" | i18n }} - - - {{ "loggedInAsEmailOn" | i18n: email : webVaultHostname }} - - -
    - -
    - - -
    -
    diff --git a/apps/web/src/app/auth/lock.component.ts b/apps/web/src/app/auth/lock.component.ts deleted file mode 100644 index 36e9c81f2c7..00000000000 --- a/apps/web/src/app/auth/lock.component.ts +++ /dev/null @@ -1,51 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnInit, inject } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; - -import { LockComponent as BaseLockComponent } from "@bitwarden/angular/auth/components/lock.component"; - -import { SharedModule } from "../shared"; - -@Component({ - selector: "app-lock", - templateUrl: "lock.component.html", - standalone: true, - imports: [SharedModule], -}) -export class LockComponent extends BaseLockComponent implements OnInit { - formBuilder = inject(FormBuilder); - - formGroup = this.formBuilder.group({ - masterPassword: ["", { validators: Validators.required, updateOn: "submit" }], - }); - - get masterPasswordFormControl() { - return this.formGroup.controls.masterPassword; - } - - async ngOnInit() { - await super.ngOnInit(); - - this.masterPasswordFormControl.setValue(this.masterPassword); - - this.onSuccessfulSubmit = async () => { - await this.router.navigateByUrl(this.successRoute); - }; - } - - async superSubmit() { - await super.submit(); - } - - submit = async () => { - this.formGroup.markAllAsTouched(); - - if (this.formGroup.invalid) { - return; - } - - this.masterPassword = this.masterPasswordFormControl.value; - await this.superSubmit(); - }; -} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 9f2a86c1c06..ad536110b74 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -12,7 +12,6 @@ import { } from "@bitwarden/angular/auth/guards"; import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; import { generatorSwap } from "@bitwarden/angular/tools/generator/generator-swap"; -import { extensionRefreshSwap } from "@bitwarden/angular/utils/extension-refresh-swap"; import { NewDeviceVerificationNoticeGuard } from "@bitwarden/angular/vault/guards"; import { AnonLayoutWrapperComponent, @@ -26,7 +25,7 @@ import { RegistrationLinkExpiredComponent, LoginComponent, LoginSecondaryContentComponent, - LockV2Component, + LockComponent, LockIcon, TwoFactorTimeoutIcon, UserLockIcon, @@ -56,7 +55,6 @@ import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizatio import { CreateOrganizationComponent } from "./admin-console/settings/create-organization.component"; import { deepLinkGuard } from "./auth/guards/deep-link.guard"; import { HintComponent } from "./auth/hint.component"; -import { LockComponent } from "./auth/lock.component"; import { LoginDecryptionOptionsComponentV1 } from "./auth/login/login-decryption-options/login-decryption-options-v1.component"; import { LoginComponentV1 } from "./auth/login/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "./auth/login/login-via-auth-request-v1.component"; @@ -509,44 +507,23 @@ const routes: Routes = [ }, }, }, - ...extensionRefreshSwap( - LockComponent, - LockV2Component, - { - path: "lock", - canActivate: [deepLinkGuard(), lockGuard()], - children: [ - { - path: "", - component: LockComponent, - }, - ], - data: { - pageTitle: { - key: "yourVaultIsLockedV2", - }, - pageIcon: LockIcon, - showReadonlyHostname: true, - } satisfies AnonLayoutWrapperData, - }, - { - path: "lock", - canActivate: [deepLinkGuard(), lockGuard()], - children: [ - { - path: "", - component: LockV2Component, - }, - ], - data: { - pageTitle: { - key: "yourVaultIsLockedV2", - }, - pageIcon: LockIcon, - showReadonlyHostname: true, - } satisfies AnonLayoutWrapperData, - }, - ), + { + path: "lock", + canActivate: [deepLinkGuard(), lockGuard()], + children: [ + { + path: "", + component: LockComponent, + }, + ], + data: { + pageTitle: { + key: "yourVaultIsLockedV2", + }, + pageIcon: LockIcon, + showReadonlyHostname: true, + } satisfies AnonLayoutWrapperData, + }, { path: "2fa", canActivate: [unauthGuardFn()], diff --git a/libs/angular/src/auth/components/lock.component.ts b/libs/angular/src/auth/components/lock.component.ts deleted file mode 100644 index 20ad37fd2b4..00000000000 --- a/libs/angular/src/auth/components/lock.component.ts +++ /dev/null @@ -1,398 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom, Subject } from "rxjs"; -import { concatMap, map, take, takeUntil } from "rxjs/operators"; - -import { PinServiceAbstraction, PinLockType } from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout-settings.service"; -import { VaultTimeoutService } from "@bitwarden/common/abstractions/vault-timeout/vault-timeout.service"; -import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; -import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; -import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction"; -import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; -import { VerificationType } from "@bitwarden/common/auth/enums/verification-type"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { - MasterPasswordVerification, - MasterPasswordVerificationResponse, -} from "@bitwarden/common/auth/types/verification"; -import { ClientType } from "@bitwarden/common/enums"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { KeySuffixOptions } from "@bitwarden/common/platform/enums"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { UserId } from "@bitwarden/common/types/guid"; -import { UserKey } from "@bitwarden/common/types/key"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; -import { - KdfConfigService, - KeyService, - BiometricStateService, - BiometricsService, -} from "@bitwarden/key-management"; - -@Directive() -export class LockComponent implements OnInit, OnDestroy { - masterPassword = ""; - pin = ""; - showPassword = false; - email: string; - pinEnabled = false; - masterPasswordEnabled = false; - webVaultHostname = ""; - formPromise: Promise; - supportsBiometric: boolean; - biometricLock: boolean; - - private activeUserId: UserId; - protected successRoute = "vault"; - protected forcePasswordResetRoute = "update-temp-password"; - protected onSuccessfulSubmit: () => Promise; - - private invalidPinAttempts = 0; - private pinLockType: PinLockType; - - private enforcedMasterPasswordOptions: MasterPasswordPolicyOptions = undefined; - - private destroy$ = new Subject(); - - constructor( - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected router: Router, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - protected messagingService: MessagingService, - protected keyService: KeyService, - protected vaultTimeoutService: VaultTimeoutService, - protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, - protected environmentService: EnvironmentService, - protected stateService: StateService, - protected apiService: ApiService, - protected logService: LogService, - protected ngZone: NgZone, - protected policyApiService: PolicyApiServiceAbstraction, - protected policyService: InternalPolicyService, - protected passwordStrengthService: PasswordStrengthServiceAbstraction, - protected dialogService: DialogService, - protected deviceTrustService: DeviceTrustServiceAbstraction, - protected userVerificationService: UserVerificationService, - protected pinService: PinServiceAbstraction, - protected biometricStateService: BiometricStateService, - protected biometricsService: BiometricsService, - protected accountService: AccountService, - protected authService: AuthService, - protected kdfConfigService: KdfConfigService, - protected syncService: SyncService, - protected toastService: ToastService, - ) {} - - async ngOnInit() { - this.accountService.activeAccount$ - .pipe( - concatMap(async (account) => { - this.activeUserId = account?.id; - await this.load(account?.id); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit() { - if (this.pinEnabled) { - return await this.handlePinRequiredUnlock(); - } - - await this.handleMasterPasswordRequiredUnlock(); - } - - async logOut() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "logOut" }, - content: { key: "logOutConfirmation" }, - acceptButtonText: { key: "logOut" }, - type: "warning", - }); - - if (confirmed) { - this.messagingService.send("logout", { userId: this.activeUserId }); - } - } - - async unlockBiometric(): Promise { - if (!this.biometricLock) { - return; - } - - await this.biometricStateService.setUserPromptCancelled(); - const userKey = await this.keyService.getUserKeyFromStorage( - KeySuffixOptions.Biometric, - this.activeUserId, - ); - - if (userKey) { - await this.setUserKeyAndContinue(userKey, this.activeUserId, false); - } - - return !!userKey; - } - - async isBiometricUnlockAvailable(): Promise { - if (!(await this.biometricsService.supportsBiometric())) { - return false; - } - return this.biometricsService.isBiometricUnlockAvailable(); - } - - togglePassword() { - this.showPassword = !this.showPassword; - const input = document.getElementById(this.pinEnabled ? "pin" : "masterPassword"); - if (this.ngZone.isStable) { - input.focus(); - } else { - this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus()); - } - } - - private async handlePinRequiredUnlock() { - if (this.pin == null || this.pin === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("pinRequired"), - }); - return; - } - - return await this.doUnlockWithPin(); - } - - private async doUnlockWithPin() { - const MAX_INVALID_PIN_ENTRY_ATTEMPTS = 5; - - try { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - const userKey = await this.pinService.decryptUserKeyWithPin(this.pin, userId); - - if (userKey) { - await this.setUserKeyAndContinue(userKey, userId); - return; // successfully unlocked - } - - // Failure state: invalid PIN or failed decryption - this.invalidPinAttempts++; - - // Log user out if they have entered an invalid PIN too many times - if (this.invalidPinAttempts >= MAX_INVALID_PIN_ENTRY_ATTEMPTS) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("tooManyInvalidPinEntryAttemptsLoggingOut"), - }); - this.messagingService.send("logout"); - return; - } - - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidPin"), - }); - } catch { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("unexpectedError"), - }); - } - } - - private async handleMasterPasswordRequiredUnlock() { - if (this.masterPassword == null || this.masterPassword === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("masterPasswordRequired"), - }); - return; - } - await this.doUnlockWithMasterPassword(); - } - - private async doUnlockWithMasterPassword() { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - - const verification = { - type: VerificationType.MasterPassword, - secret: this.masterPassword, - } as MasterPasswordVerification; - - let passwordValid = false; - let response: MasterPasswordVerificationResponse; - try { - this.formPromise = this.userVerificationService.verifyUserByMasterPassword( - verification, - userId, - this.email, - ); - response = await this.formPromise; - this.enforcedMasterPasswordOptions = MasterPasswordPolicyOptions.fromResponse( - response.policyOptions, - ); - passwordValid = true; - } catch (e) { - this.logService.error(e); - } finally { - this.formPromise = null; - } - - if (!passwordValid) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("invalidMasterPassword"), - }); - return; - } - - const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey( - response.masterKey, - userId, - ); - await this.setUserKeyAndContinue(userKey, userId, true); - } - - private async setUserKeyAndContinue( - key: UserKey, - userId: UserId, - evaluatePasswordAfterUnlock = false, - ) { - await this.keyService.setUserKey(key, userId); - - // Now that we have a decrypted user key in memory, we can check if we - // need to establish trust on the current device - const activeAccount = await firstValueFrom(this.accountService.activeAccount$); - await this.deviceTrustService.trustDeviceIfRequired(activeAccount.id); - - await this.doContinue(evaluatePasswordAfterUnlock); - } - - private async doContinue(evaluatePasswordAfterUnlock: boolean) { - await this.biometricStateService.resetUserPromptCancelled(); - this.messagingService.send("unlocked"); - - if (evaluatePasswordAfterUnlock) { - try { - // If we do not have any saved policies, attempt to load them from the service - if (this.enforcedMasterPasswordOptions == undefined) { - this.enforcedMasterPasswordOptions = await firstValueFrom( - this.policyService.masterPasswordPolicyOptions$(), - ); - } - - if (this.requirePasswordChange()) { - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.WeakMasterPassword, - userId, - ); - // 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([this.forcePasswordResetRoute]); - return; - } - } catch (e) { - // Do not prevent unlock if there is an error evaluating policies - this.logService.error(e); - } - } - - // Vault can be de-synced since notifications get ignored while locked. Need to check whether sync is required using the sync service. - const clientType = this.platformUtilsService.getClientType(); - if (clientType === ClientType.Browser || clientType === ClientType.Desktop) { - // Desktop and Browser have better offline support and to facilitate this we don't make the user wait for what - // could be an HTTP Timeout because their server is unreachable. - await Promise.race([ - this.syncService - .fullSync(false) - .catch((err) => this.logService.error("Error during unlock sync", err)), - new Promise((resolve) => - setTimeout(() => { - this.logService.warning("Skipping sync wait, continuing to unlock."); - resolve(); - }, 5_000), - ), - ]); - } else { - await this.syncService.fullSync(false); - } - - if (this.onSuccessfulSubmit != null) { - await this.onSuccessfulSubmit(); - } else if (this.router != null) { - // 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([this.successRoute]); - } - } - - private async load(userId: UserId) { - this.pinLockType = await this.pinService.getPinLockType(userId); - - this.pinEnabled = await this.pinService.isPinDecryptionAvailable(userId); - - this.masterPasswordEnabled = await this.userVerificationService.hasMasterPassword(); - - this.supportsBiometric = await this.biometricsService.supportsBiometric(); - this.biometricLock = - (await this.vaultTimeoutSettingsService.isBiometricLockSet()) && - ((await this.keyService.hasUserKeyStored(KeySuffixOptions.Biometric)) || - !this.platformUtilsService.supportsSecureStorage()); - this.email = await firstValueFrom( - this.accountService.activeAccount$.pipe(map((a) => a?.email)), - ); - - this.webVaultHostname = (await this.environmentService.getEnvironment()).getHostname(); - } - - /** - * Checks if the master password meets the enforced policy requirements - * If not, returns false - */ - private requirePasswordChange(): boolean { - if ( - this.enforcedMasterPasswordOptions == undefined || - !this.enforcedMasterPasswordOptions.enforceOnLogin - ) { - return false; - } - - const passwordStrength = this.passwordStrengthService.getPasswordStrength( - this.masterPassword, - this.email, - )?.score; - - return !this.policyService.evaluateMasterPassword( - passwordStrength, - this.masterPassword, - this.enforcedMasterPasswordOptions, - ); - } -} diff --git a/libs/auth/src/angular/lock/lock.component.ts b/libs/auth/src/angular/lock/lock.component.ts index bcbc2bd5751..aa7b43c2e53 100644 --- a/libs/auth/src/angular/lock/lock.component.ts +++ b/libs/auth/src/angular/lock/lock.component.ts @@ -75,7 +75,7 @@ const clientTypeToSuccessRouteRecord: Partial> = { IconButtonModule, ], }) -export class LockV2Component implements OnInit, OnDestroy { +export class LockComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); activeAccount: Account | null; @@ -543,8 +543,8 @@ export class LockV2Component implements OnInit, OnDestroy { const previousUrl = this.lockComponentService.getPreviousUrl(); /** * In a passkey flow, the `previousUrl` will still be `/fido2?` at this point - * because the `/lockV2` route doesn't save the URL in the `BrowserRouterService`. This is - * handled by the `doNotSaveUrl` property on the `lockV2` route in `app-routing.module.ts`. + * because the `/lock` route doesn't save the URL in the `BrowserRouterService`. This is + * handled by the `doNotSaveUrl` property on the `/lock` route in `app-routing.module.ts`. */ if (previousUrl) { await this.router.navigateByUrl(previousUrl); From ce5ae478a8a46575be9d20a04d9b4d33cba53132 Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 20 Dec 2024 19:46:24 +0100 Subject: [PATCH 07/13] [PM-15921] Remove v1 generator and generator history (#12345) * Remove v1 generator, generator history page and extension refresh conditional routing * Remove unused keys from en/messages.json --------- Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 22 - apps/browser/src/popup/app-routing.module.ts | 14 +- apps/browser/src/popup/app.module.ts | 4 - .../popup/generator/generator.component.html | 588 ------------------ .../popup/generator/generator.component.ts | 88 --- .../password-generator-history.component.html | 48 -- .../password-generator-history.component.ts | 28 - 7 files changed, 7 insertions(+), 785 deletions(-) delete mode 100644 apps/browser/src/tools/popup/generator/generator.component.html delete mode 100644 apps/browser/src/tools/popup/generator/generator.component.ts delete mode 100644 apps/browser/src/tools/popup/generator/password-generator-history.component.html delete mode 100644 apps/browser/src/tools/popup/generator/password-generator-history.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index de438a09467..4b7d9eb2992 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -454,9 +454,6 @@ "length": { "message": "Length" }, - "passwordMinLength": { - "message": "Minimum password length" - }, "uppercase": { "message": "Uppercase (A-Z)", "description": "deprecated. Use uppercaseLabel instead." @@ -528,10 +525,6 @@ "minSpecial": { "message": "Minimum special" }, - "avoidAmbChar": { - "message": "Avoid ambiguous characters", - "description": "deprecated. Use avoidAmbiguous instead." - }, "avoidAmbiguous": { "message": "Avoid ambiguous characters", "description": "Label for the avoid ambiguous characters checkbox." @@ -2047,9 +2040,6 @@ "clone": { "message": "Clone" }, - "passwordGeneratorPolicyInEffect": { - "message": "One or more organization policies are affecting your generator settings." - }, "passwordGenerator": { "message": "Password generator" }, @@ -2884,9 +2874,6 @@ "error": { "message": "Error" }, - "regenerateUsername": { - "message": "Regenerate username" - }, "generateUsername": { "message": "Generate username" }, @@ -2927,9 +2914,6 @@ } } }, - "usernameType": { - "message": "Username type" - }, "plusAddressedEmail": { "message": "Plus addressed email", "description": "Username generator option that appends a random sub-address to the username. For example: address+subaddress@email.com" @@ -2952,12 +2936,6 @@ "websiteName": { "message": "Website name" }, - "whatWouldYouLikeToGenerate": { - "message": "What would you like to generate?" - }, - "passwordType": { - "message": "Password type" - }, "service": { "message": "Service" }, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 549a677857c..1f42c1cf31d 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -86,8 +86,6 @@ import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; -import { GeneratorComponent } from "../tools/popup/generator/generator.component"; -import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; import { SendTypeComponent } from "../tools/popup/send/send-type.component"; @@ -330,15 +328,16 @@ const routes: Routes = [ }), { path: "generator", - component: GeneratorComponent, + component: CredentialGeneratorComponent, canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(PasswordGeneratorHistoryComponent, CredentialGeneratorHistoryComponent, { + { path: "generator-history", + component: CredentialGeneratorHistoryComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "import", component: ImportBrowserV2Component, @@ -757,11 +756,12 @@ const routes: Routes = [ canDeactivate: [clearVaultStateGuard], data: { elevation: 0 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(GeneratorComponent, CredentialGeneratorComponent, { + { path: "generator", + component: CredentialGeneratorComponent, canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, - }), + }, { path: "settings", component: SettingsV2Component, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 3d8cb267798..c2697b2d490 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -56,8 +56,6 @@ import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.comp import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component"; -import { GeneratorComponent } from "../tools/popup/generator/generator.component"; -import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/password-generator-history.component"; import { SendListComponent } from "../tools/popup/send/components/send-list.component"; import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; @@ -160,8 +158,6 @@ import "../platform/popup/locales"; LoginDecryptionOptionsComponentV1, NotificationsSettingsV1Component, AppearanceComponent, - GeneratorComponent, - PasswordGeneratorHistoryComponent, PasswordHistoryComponent, PremiumComponent, RegisterComponent, diff --git a/apps/browser/src/tools/popup/generator/generator.component.html b/apps/browser/src/tools/popup/generator/generator.component.html deleted file mode 100644 index d92d32a5623..00000000000 --- a/apps/browser/src/tools/popup/generator/generator.component.html +++ /dev/null @@ -1,588 +0,0 @@ - -
    - - -
    -

    - {{ "generator" | i18n }} -

    -
    - -
    -
    -
    - - {{ "passwordGeneratorPolicyInEffect" | i18n }} - -
    -
    -
    - - -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -

    - {{ "options" | i18n }} -

    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    - -
    -
    -
    - - - -
    -
    - {{ "passwordMinLength" | i18n }} - - {{ passwordOptionsMinLengthForReader$ | async }} - - {{ passwordOptions.minLength }} -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - -
    - -
    -

    - {{ "options" | i18n }} -

    -
    -
    - -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    - -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - -
    - - -
    -
    -
    - - -
    -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/generator/generator.component.ts b/apps/browser/src/tools/popup/generator/generator.component.ts deleted file mode 100644 index d744c58f4d1..00000000000 --- a/apps/browser/src/tools/popup/generator/generator.component.ts +++ /dev/null @@ -1,88 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { Component, NgZone, OnInit } from "@angular/core"; -import { ActivatedRoute } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { GeneratorComponent as BaseGeneratorComponent } from "@bitwarden/angular/tools/generator/components/generator.component"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; -import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { AddEditCipherInfo } from "@bitwarden/common/vault/types/add-edit-cipher-info"; -import { ToastService } from "@bitwarden/components"; -import { - PasswordGenerationServiceAbstraction, - UsernameGenerationServiceAbstraction, -} from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-generator", - templateUrl: "generator.component.html", -}) -export class GeneratorComponent extends BaseGeneratorComponent implements OnInit { - private addEditCipherInfo: AddEditCipherInfo; - private cipherState: CipherView; - private cipherService: CipherService; - - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - usernameGenerationService: UsernameGenerationServiceAbstraction, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - accountService: AccountService, - cipherService: CipherService, - route: ActivatedRoute, - logService: LogService, - ngZone: NgZone, - private location: Location, - toastService: ToastService, - ) { - super( - passwordGenerationService, - usernameGenerationService, - platformUtilsService, - accountService, - i18nService, - logService, - route, - ngZone, - window, - toastService, - ); - this.cipherService = cipherService; - } - - async ngOnInit() { - this.addEditCipherInfo = await firstValueFrom(this.cipherService.addEditCipherInfo$); - if (this.addEditCipherInfo != null) { - this.cipherState = this.addEditCipherInfo.cipher; - } - this.comingFromAddEdit = this.cipherState != null; - if (this.cipherState?.login?.hasUris) { - this.usernameWebsite = this.cipherState.login.uris[0].hostname; - } - await super.ngOnInit(); - } - - select() { - super.select(); - if (this.type === "password") { - this.cipherState.login.password = this.password; - } else if (this.type === "username") { - this.cipherState.login.username = this.username; - } - this.addEditCipherInfo.cipher = this.cipherState; - // 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.cipherService.setAddEditCipherInfo(this.addEditCipherInfo); - this.close(); - } - - close() { - this.location.back(); - } -} diff --git a/apps/browser/src/tools/popup/generator/password-generator-history.component.html b/apps/browser/src/tools/popup/generator/password-generator-history.component.html deleted file mode 100644 index 8f4a246fc5e..00000000000 --- a/apps/browser/src/tools/popup/generator/password-generator-history.component.html +++ /dev/null @@ -1,48 +0,0 @@ -
    -
    - -
    -

    - {{ "passwordHistory" | i18n }} -

    -
    - -
    -
    -
    -
    -
    -
    -
    -
    -
    - {{ h.date | date: "medium" }} -
    -
    -
    - -
    -
    -
    -
    -
    -

    {{ "noPasswordsInList" | i18n }}

    -
    -
    diff --git a/apps/browser/src/tools/popup/generator/password-generator-history.component.ts b/apps/browser/src/tools/popup/generator/password-generator-history.component.ts deleted file mode 100644 index 2436ae51a7d..00000000000 --- a/apps/browser/src/tools/popup/generator/password-generator-history.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Location } from "@angular/common"; -import { Component } from "@angular/core"; - -import { PasswordGeneratorHistoryComponent as BasePasswordGeneratorHistoryComponent } from "@bitwarden/angular/tools/generator/components/password-generator-history.component"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-password-generator-history", - templateUrl: "password-generator-history.component.html", -}) -export class PasswordGeneratorHistoryComponent extends BasePasswordGeneratorHistoryComponent { - constructor( - passwordGenerationService: PasswordGenerationServiceAbstraction, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - private location: Location, - toastService: ToastService, - ) { - super(passwordGenerationService, platformUtilsService, i18nService, window, toastService); - } - - close() { - this.location.back(); - } -} From 64166a9354dc2e1397d38ec5de10f795e68213e3 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Fri, 20 Dec 2024 14:03:50 -0500 Subject: [PATCH 08/13] [PM-16102] Increase clickable area for bit-item actions (#12450) --- .../components/src/badge/badge.component.html | 3 +++ ...{badge.directive.ts => badge.component.ts} | 13 ++++++----- libs/components/src/badge/badge.module.ts | 6 ++--- libs/components/src/badge/badge.stories.ts | 10 ++++----- libs/components/src/badge/index.ts | 2 +- .../src/item/item-action.component.ts | 8 +++++++ .../src/item/item-content.component.ts | 4 ++++ libs/components/src/item/item.stories.ts | 22 +++++++++---------- 8 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 libs/components/src/badge/badge.component.html rename libs/components/src/badge/{badge.directive.ts => badge.component.ts} (89%) diff --git a/libs/components/src/badge/badge.component.html b/libs/components/src/badge/badge.component.html new file mode 100644 index 00000000000..6f586ec3523 --- /dev/null +++ b/libs/components/src/badge/badge.component.html @@ -0,0 +1,3 @@ + + + diff --git a/libs/components/src/badge/badge.directive.ts b/libs/components/src/badge/badge.component.ts similarity index 89% rename from libs/components/src/badge/badge.directive.ts rename to libs/components/src/badge/badge.component.ts index eef876a664d..29644a11ac7 100644 --- a/libs/components/src/badge/badge.directive.ts +++ b/libs/components/src/badge/badge.component.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { Component, ElementRef, HostBinding, Input } from "@angular/core"; import { FocusableElement } from "../shared/focusable-element"; @@ -28,12 +29,14 @@ const hoverStyles: Record = { info: ["hover:tw-bg-info-600", "hover:tw-border-info-600", "hover:!tw-text-black"], }; -@Directive({ +@Component({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", - providers: [{ provide: FocusableElement, useExisting: BadgeDirective }], + providers: [{ provide: FocusableElement, useExisting: BadgeComponent }], + imports: [CommonModule], + templateUrl: "badge.component.html", standalone: true, }) -export class BadgeDirective implements FocusableElement { +export class BadgeComponent implements FocusableElement { @HostBinding("class") get classList() { return [ "tw-inline-block", @@ -63,7 +66,7 @@ export class BadgeDirective implements FocusableElement { ] .concat(styles[this.variant]) .concat(this.hasHoverEffects ? hoverStyles[this.variant] : []) - .concat(this.truncate ? ["tw-truncate", this.maxWidthClass] : []); + .concat(this.truncate ? this.maxWidthClass : []); } @HostBinding("attr.title") get titleAttr() { if (this.title !== undefined) { diff --git a/libs/components/src/badge/badge.module.ts b/libs/components/src/badge/badge.module.ts index e7f3770785a..d9a6e712820 100644 --- a/libs/components/src/badge/badge.module.ts +++ b/libs/components/src/badge/badge.module.ts @@ -1,9 +1,9 @@ import { NgModule } from "@angular/core"; -import { BadgeDirective } from "./badge.directive"; +import { BadgeComponent } from "./badge.component"; @NgModule({ - imports: [BadgeDirective], - exports: [BadgeDirective], + imports: [BadgeComponent], + exports: [BadgeComponent], }) export class BadgeModule {} diff --git a/libs/components/src/badge/badge.stories.ts b/libs/components/src/badge/badge.stories.ts index b8ac7ec8efe..5d697f8ad8f 100644 --- a/libs/components/src/badge/badge.stories.ts +++ b/libs/components/src/badge/badge.stories.ts @@ -1,14 +1,14 @@ import { CommonModule } from "@angular/common"; import { Meta, moduleMetadata, StoryObj } from "@storybook/angular"; -import { BadgeDirective } from "./badge.directive"; +import { BadgeComponent } from "./badge.component"; export default { title: "Component Library/Badge", - component: BadgeDirective, + component: BadgeComponent, decorators: [ moduleMetadata({ - imports: [CommonModule, BadgeDirective], + imports: [CommonModule, BadgeComponent], }), ], args: { @@ -21,9 +21,9 @@ export default { url: "https://www.figma.com/file/Zt3YSeb6E6lebAffrNLa0h/Tailwind-Component-Library?node-id=1881%3A16956", }, }, -} as Meta; +} as Meta; -type Story = StoryObj; +type Story = StoryObj; export const Variants: Story = { render: (args) => ({ diff --git a/libs/components/src/badge/index.ts b/libs/components/src/badge/index.ts index a8f5babda91..ae19f4df288 100644 --- a/libs/components/src/badge/index.ts +++ b/libs/components/src/badge/index.ts @@ -1,2 +1,2 @@ -export { BadgeDirective, BadgeVariant } from "./badge.directive"; +export { BadgeComponent, BadgeVariant } from "./badge.component"; export * from "./badge.module"; diff --git a/libs/components/src/item/item-action.component.ts b/libs/components/src/item/item-action.component.ts index 8cabf5c5c23..ae8cb719724 100644 --- a/libs/components/src/item/item-action.component.ts +++ b/libs/components/src/item/item-action.component.ts @@ -8,5 +8,13 @@ import { A11yCellDirective } from "../a11y/a11y-cell.directive"; imports: [], template: ``, providers: [{ provide: A11yCellDirective, useExisting: ItemActionComponent }], + host: { + class: + /** + * `top` and `bottom` units should be kept in sync with `item-content.component.ts`'s y-axis padding. + * we want this `:after` element to be the same height as the `item-content` + */ + "[&>button]:tw-relative [&>button:not([bit-item-content])]:after:tw-content-[''] [&>button]:after:tw-absolute [&>button]:after:tw-block bit-compact:[&>button]:after:tw-top-[-0.8rem] bit-compact:[&>button]:after:tw-bottom-[-0.8rem] [&>button]:after:tw-top-[-0.85rem] [&>button]:after:tw-bottom-[-0.85rem] [&>button]:after:tw-right-[-0.25rem] [&>button]:after:tw-left-[-0.25rem]", + }, }) export class ItemActionComponent extends A11yCellDirective {} diff --git a/libs/components/src/item/item-content.component.ts b/libs/components/src/item/item-content.component.ts index 824b6a596a0..f6cc3f133ad 100644 --- a/libs/components/src/item/item-content.component.ts +++ b/libs/components/src/item/item-content.component.ts @@ -19,6 +19,10 @@ import { TypographyModule } from "../typography"; templateUrl: `item-content.component.html`, host: { class: + /** + * y-axis padding should be kept in sync with `item-action.component.ts`'s `top` and `bottom` units. + * we want this to be the same height as the `item-action`'s `:after` element + */ "fvw-target tw-outline-none tw-text-main hover:tw-text-main tw-no-underline hover:tw-no-underline tw-text-base tw-py-2 tw-px-4 bit-compact:tw-py-1.5 bit-compact:tw-px-2 tw-bg-transparent tw-w-full tw-border-none tw-flex tw-gap-4 tw-items-center tw-justify-between", }, changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/libs/components/src/item/item.stories.ts b/libs/components/src/item/item.stories.ts index 675172565f1..5adf9d3c49d 100644 --- a/libs/components/src/item/item.stories.ts +++ b/libs/components/src/item/item.stories.ts @@ -70,7 +70,7 @@ export const Default: Story = { - + @@ -163,7 +163,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -182,7 +182,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -201,7 +201,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -220,7 +220,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -239,7 +239,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -258,7 +258,7 @@ const multipleActionListTemplate = /*html*/ ` - + @@ -332,14 +332,14 @@ export const SingleActionWithBadge: Story = { Foobar - Auto-fill + Fill Helloooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo! - Auto-fill + Fill @@ -375,7 +375,7 @@ export const VirtualScrolling: Story = { - + @@ -405,7 +405,7 @@ export const WithoutBorderRadius: Story = { - + From 4b42092c93537d69f2357e9fc5090fd7f48600a2 Mon Sep 17 00:00:00 2001 From: cd-bitwarden <106776772+cd-bitwarden@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:42:15 -0500 Subject: [PATCH 09/13] [PM-15047] Updating admin console 3 dots menu to use th enewer attachments modal (#12402) * Updating admin console 3 dots menu to use th enewer attachments modal * Update apps/web/src/app/vault/org-vault/vault.component.ts Co-authored-by: Shane Melton * lint fix --------- Co-authored-by: --global <> Co-authored-by: Shane Melton --- .../app/vault/org-vault/vault.component.ts | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index e1aca825960..15453a8c064 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -104,6 +104,10 @@ import { } from "../components/vault-item-dialog/vault-item-dialog.component"; import { VaultItemEvent } from "../components/vault-items/vault-item-event"; import { VaultItemsModule } from "../components/vault-items/vault-items.module"; +import { + AttachmentDialogResult, + AttachmentsV2Component, +} from "../individual-vault/attachments-v2.component"; import { BulkDeleteDialogResult, openBulkDeleteDialog, @@ -119,7 +123,6 @@ import { VaultHeaderComponent } from "../org-vault/vault-header/vault-header.com import { getNestedCollectionTree } from "../utils/collection-utils"; import { AddEditComponent } from "./add-edit.component"; -import { AttachmentsComponent } from "./attachments.component"; import { BulkCollectionsDialogComponent, BulkCollectionsDialogResult, @@ -761,29 +764,18 @@ export class VaultComponent implements OnInit, OnDestroy { return; } - let madeAttachmentChanges = false; - - const [modal] = await this.modalService.openViewRef( - AttachmentsComponent, - this.attachmentsModalRef, - (comp) => { - comp.organization = this.organization; - comp.cipherId = cipher.id; - comp.onUploadedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - comp.onDeletedAttachment - .pipe(takeUntil(this.destroy$)) - .subscribe(() => (madeAttachmentChanges = true)); - }, - ); - - modal.onClosed.pipe(takeUntil(this.destroy$)).subscribe(() => { - if (madeAttachmentChanges) { - this.refresh(); - } - madeAttachmentChanges = false; + const dialogRef = AttachmentsV2Component.open(this.dialogService, { + cipherId: cipher.id as CipherId, }); + + const result = await firstValueFrom(dialogRef.closed); + + if ( + result.action === AttachmentDialogResult.Removed || + result.action === AttachmentDialogResult.Uploaded + ) { + this.refresh(); + } } async addCipher(cipherType?: CipherType) { From 0619ef507fb6224ecbdc2813192766300752e68a Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Fri, 20 Dec 2024 20:46:34 +0100 Subject: [PATCH 10/13] [PM-7114] Remove legacy Send code from browser extension (#12342) * Remove Send grouping, type and state * Delete Send list and add-edit --------- Co-authored-by: Daniel James Smith --- .github/whitelist-capital-letters.txt | 1 - apps/browser/src/_locales/en/messages.json | 88 ---- .../src/models/browserSendComponentState.ts | 20 - apps/browser/src/popup/app-routing.module.ts | 22 +- apps/browser/src/popup/app.component.ts | 4 - apps/browser/src/popup/app.module.ts | 8 - .../src/popup/services/services.module.ts | 6 - .../send/components/send-list.component.html | 98 ----- .../send/components/send-list.component.ts | 38 -- .../popup/send/send-add-edit.component.html | 411 ------------------ .../popup/send/send-add-edit.component.ts | 140 ------ .../popup/send/send-groupings.component.html | 119 ----- .../popup/send/send-groupings.component.ts | 203 --------- .../tools/popup/send/send-type.component.html | 68 --- .../tools/popup/send/send-type.component.ts | 190 -------- .../browser-send-state.service.spec.ts | 59 --- .../services/browser-send-state.service.ts | 71 --- .../popup/services/key-definitions.spec.ts | 39 -- .../tools/popup/services/key-definitions.ts | 27 -- 19 files changed, 8 insertions(+), 1604 deletions(-) delete mode 100644 apps/browser/src/models/browserSendComponentState.ts delete mode 100644 apps/browser/src/tools/popup/send/components/send-list.component.html delete mode 100644 apps/browser/src/tools/popup/send/components/send-list.component.ts delete mode 100644 apps/browser/src/tools/popup/send/send-add-edit.component.html delete mode 100644 apps/browser/src/tools/popup/send/send-add-edit.component.ts delete mode 100644 apps/browser/src/tools/popup/send/send-groupings.component.html delete mode 100644 apps/browser/src/tools/popup/send/send-groupings.component.ts delete mode 100644 apps/browser/src/tools/popup/send/send-type.component.html delete mode 100644 apps/browser/src/tools/popup/send/send-type.component.ts delete mode 100644 apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts delete mode 100644 apps/browser/src/tools/popup/services/browser-send-state.service.ts delete mode 100644 apps/browser/src/tools/popup/services/key-definitions.spec.ts delete mode 100644 apps/browser/src/tools/popup/services/key-definitions.ts diff --git a/.github/whitelist-capital-letters.txt b/.github/whitelist-capital-letters.txt index a320149281b..73d323851e5 100644 --- a/.github/whitelist-capital-letters.txt +++ b/.github/whitelist-capital-letters.txt @@ -37,7 +37,6 @@ ./apps/browser/store/windows/AppxManifest.xml ./apps/browser/src/background/nativeMessaging.background.ts ./apps/browser/src/models/browserComponentState.ts -./apps/browser/src/models/browserSendComponentState.ts ./apps/browser/src/models/browserGroupingsComponentState.ts ./apps/browser/src/models/biometricErrors.ts ./apps/browser/src/browser/safariApp.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 4b7d9eb2992..8fe283ba289 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1150,9 +1150,6 @@ "moveToOrganization": { "message": "Move to organization" }, - "share": { - "message": "Share" - }, "movedItemToOrg": { "message": "$ITEMNAME$ moved to $ORGNAME$", "placeholders": { @@ -2379,14 +2376,6 @@ "message": "Send details", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "searchSends": { - "message": "Search Sends", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "addSend": { - "message": "Add Send", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendTypeText": { "message": "Text" }, @@ -2403,16 +2392,9 @@ "hideTextByDefault": { "message": "Hide text by default" }, - "maxAccessCountReached": { - "message": "Max access count reached", - "description": "This text will be displayed after a Send has been accessed the maximum amount of times." - }, "expired": { "message": "Expired" }, - "pendingDeletion": { - "message": "Pending deletion" - }, "passwordProtected": { "message": "Password protected" }, @@ -2462,24 +2444,9 @@ "message": "Edit Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendTypeHeader": { - "message": "What type of Send is this?", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendNameDesc": { - "message": "A friendly name to describe this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendFileDesc": { - "message": "The file you want to send." - }, "deletionDate": { "message": "Deletion date" }, - "deletionDateDesc": { - "message": "The Send will be permanently deleted on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "deletionDateDescV2": { "message": "The Send will be permanently deleted on this date.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2487,10 +2454,6 @@ "expirationDate": { "message": "Expiration date" }, - "expirationDateDesc": { - "message": "If set, access to this Send will expire on the specified date and time.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "oneDay": { "message": "1 day" }, @@ -2506,43 +2469,10 @@ "custom": { "message": "Custom" }, - "maximumAccessCount": { - "message": "Maximum Access Count" - }, - "maximumAccessCountDesc": { - "message": "If set, users will no longer be able to access this Send once the maximum access count is reached.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendPasswordDesc": { - "message": "Optionally require a password for users to access this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, "sendPasswordDescV3": { "message": "Add an optional password for recipients to access this Send.", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." }, - "sendNotesDesc": { - "message": "Private notes about this Send.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendDisableDesc": { - "message": "Deactivate this Send so that no one can access it.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendShareDesc": { - "message": "Copy this Send's link to clipboard upon save.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "sendTextDesc": { - "message": "The text you want to send." - }, - "sendHideText": { - "message": "Hide this Send's text by default.", - "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." - }, - "currentAccessCount": { - "message": "Current access count" - }, "createSend": { "message": "New Send", "description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated." @@ -2625,18 +2555,6 @@ "sendFileCalloutHeader": { "message": "Before you start" }, - "sendFirefoxCustomDatePopoutMessage1": { - "message": "To use a calendar style date picker", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read '**To use a calendar style date picker ** click here to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage2": { - "message": "click here", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker **click here** to pop out your window.'" - }, - "sendFirefoxCustomDatePopoutMessage3": { - "message": "to pop out your window.", - "description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'To use a calendar style date picker click here **to pop out your window.**'" - }, "expirationDateIsInvalid": { "message": "The expiration date provided is not valid." }, @@ -2652,15 +2570,9 @@ "dateParsingError": { "message": "There was an error saving your deletion and expiration dates." }, - "hideEmail": { - "message": "Hide my email address from recipients." - }, "hideYourEmail": { "message": "Hide your email address from viewers." }, - "sendOptionsPolicyInEffect": { - "message": "One or more organization policies are affecting your Send options." - }, "passwordPrompt": { "message": "Master password re-prompt" }, diff --git a/apps/browser/src/models/browserSendComponentState.ts b/apps/browser/src/models/browserSendComponentState.ts deleted file mode 100644 index 204595df272..00000000000 --- a/apps/browser/src/models/browserSendComponentState.ts +++ /dev/null @@ -1,20 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { DeepJsonify } from "@bitwarden/common/types/deep-jsonify"; - -import { BrowserComponentState } from "./browserComponentState"; - -export class BrowserSendComponentState extends BrowserComponentState { - sends: SendView[]; - - static fromJSON(json: DeepJsonify) { - if (json == null) { - return null; - } - - return Object.assign(new BrowserSendComponentState(), json, { - sends: json.sends?.map((s) => SendView.fromJSON(s)), - }); - } -} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 1f42c1cf31d..0a6ff456938 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -86,9 +86,6 @@ import BrowserPopupUtils from "../platform/popup/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { CredentialGeneratorHistoryComponent } from "../tools/popup/generator/credential-generator-history.component"; import { CredentialGeneratorComponent } from "../tools/popup/generator/credential-generator.component"; -import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; -import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; -import { SendTypeComponent } from "../tools/popup/send/send-type.component"; import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/send-v2/add-edit/send-add-edit.component"; import { SendCreatedComponent } from "../tools/popup/send-v2/send-created/send-created.component"; import { SendV2Component } from "../tools/popup/send-v2/send-v2.component"; @@ -415,21 +412,17 @@ const routes: Routes = [ data: { elevation: 1 } satisfies RouteDataProperties, }), { - path: "send-type", - component: SendTypeComponent, - canActivate: [authGuard], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - ...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, { path: "add-send", + component: SendAddEditV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(SendAddEditComponent, SendAddEditV2Component, { + }, + { path: "edit-send", + component: SendAddEditV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), + }, { path: "send-created", component: SendCreatedComponent, @@ -768,11 +761,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(SendGroupingsComponent, SendV2Component, { + { path: "send", + component: SendV2Component, canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, - }), + }, ], }, { diff --git a/apps/browser/src/popup/app.component.ts b/apps/browser/src/popup/app.component.ts index 248050ed61c..3382e24689a 100644 --- a/apps/browser/src/popup/app.component.ts +++ b/apps/browser/src/popup/app.component.ts @@ -26,7 +26,6 @@ import { PopupCompactModeService } from "../platform/popup/layout/popup-compact- import { PopupWidthService } from "../platform/popup/layout/popup-width.service"; import { PopupViewCacheService } from "../platform/popup/view-cache/popup-view-cache.service"; import { initPopupClosedListener } from "../platform/services/popup-view-cache-background.service"; -import { BrowserSendStateService } from "../tools/popup/services/browser-send-state.service"; import { VaultBrowserStateService } from "../vault/services/vault-browser-state.service"; import { routerTransition } from "./app-routing.animations"; @@ -57,7 +56,6 @@ export class AppComponent implements OnInit, OnDestroy { private i18nService: I18nService, private router: Router, private stateService: StateService, - private browserSendStateService: BrowserSendStateService, private vaultBrowserStateService: VaultBrowserStateService, private cipherService: CipherService, private changeDetectorRef: ChangeDetectorRef, @@ -243,8 +241,6 @@ export class AppComponent implements OnInit, OnDestroy { await Promise.all([ this.vaultBrowserStateService.setBrowserGroupingsComponentState(null), this.vaultBrowserStateService.setBrowserVaultItemsComponentState(null), - this.browserSendStateService.setBrowserSendComponentState(null), - this.browserSendStateService.setBrowserSendTypeComponentState(null), ]); } diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index c2697b2d490..b547078084b 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -56,10 +56,6 @@ import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.comp import { PopupPageComponent } from "../platform/popup/layout/popup-page.component"; import { PopupTabNavigationComponent } from "../platform/popup/layout/popup-tab-navigation.component"; import { FilePopoutCalloutComponent } from "../tools/popup/components/file-popout-callout.component"; -import { SendListComponent } from "../tools/popup/send/components/send-list.component"; -import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; -import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; -import { SendTypeComponent } from "../tools/popup/send/send-type.component"; import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component"; import { CipherRowComponent } from "../vault/popup/components/cipher-row.component"; import { AddEditCustomFieldsComponent } from "../vault/popup/components/vault/add-edit-custom-fields.component"; @@ -161,10 +157,6 @@ import "../platform/popup/locales"; PasswordHistoryComponent, PremiumComponent, RegisterComponent, - SendAddEditComponent, - SendGroupingsComponent, - SendListComponent, - SendTypeComponent, SetPasswordComponent, VaultSettingsComponent, ShareComponent, diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 7014d908ac3..5b27833636f 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -152,7 +152,6 @@ import { ForegroundSyncService } from "../../platform/sync/foreground-sync.servi import { fromChromeRuntimeMessaging } from "../../platform/utils/from-chrome-runtime-messaging"; import { ExtensionLockComponentService } from "../../services/extension-lock-component.service"; import { ForegroundVaultTimeoutService } from "../../services/vault-timeout/foreground-vault-timeout.service"; -import { BrowserSendStateService } from "../../tools/popup/services/browser-send-state.service"; import { FilePopoutUtilsService } from "../../tools/popup/services/file-popout-utils.service"; import { Fido2UserVerificationService } from "../../vault/services/fido2-user-verification.service"; import { VaultBrowserStateService } from "../../vault/services/vault-browser-state.service"; @@ -473,11 +472,6 @@ const safeProviders: SafeProvider[] = [ useClass: UserNotificationSettingsService, deps: [StateProvider], }), - safeProvider({ - provide: BrowserSendStateService, - useClass: BrowserSendStateService, - deps: [StateProvider], - }), safeProvider({ provide: MessageListener, useFactory: (subject: Subject>>, ngZone: NgZone) => diff --git a/apps/browser/src/tools/popup/send/components/send-list.component.html b/apps/browser/src/tools/popup/send/components/send-list.component.html deleted file mode 100644 index 05c8e3e3754..00000000000 --- a/apps/browser/src/tools/popup/send/components/send-list.component.html +++ /dev/null @@ -1,98 +0,0 @@ -
    - -
    - - - -
    -
    diff --git a/apps/browser/src/tools/popup/send/components/send-list.component.ts b/apps/browser/src/tools/popup/send/components/send-list.component.ts deleted file mode 100644 index bab818cce6a..00000000000 --- a/apps/browser/src/tools/popup/send/components/send-list.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, EventEmitter, Input, Output } from "@angular/core"; - -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; - -@Component({ - selector: "app-send-list", - templateUrl: "send-list.component.html", -}) -export class SendListComponent { - @Input() sends: SendView[]; - @Input() title: string; - @Input() disabledByPolicy = false; - @Output() onSelected = new EventEmitter(); - @Output() onCopySendLink = new EventEmitter(); - @Output() onRemovePassword = new EventEmitter(); - @Output() onDeleteSend = new EventEmitter(); - - sendType = SendType; - - selectSend(s: SendView) { - this.onSelected.emit(s); - } - - copySendLink(s: SendView) { - this.onCopySendLink.emit(s); - } - - removePassword(s: SendView) { - this.onRemovePassword.emit(s); - } - - delete(s: SendView) { - this.onDeleteSend.emit(s); - } -} diff --git a/apps/browser/src/tools/popup/send/send-add-edit.component.html b/apps/browser/src/tools/popup/send/send-add-edit.component.html deleted file mode 100644 index 38c8a8175bb..00000000000 --- a/apps/browser/src/tools/popup/send/send-add-edit.component.html +++ /dev/null @@ -1,411 +0,0 @@ -
    -
    -
    - -
    -

    - {{ title }} -

    -
    - -
    -
    -
    - - - {{ "sendDisabledWarning" | i18n }} - - - {{ "sendOptionsPolicyInEffect" | i18n }} - - - - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - -
    - - -
    -
    -
    -
    - -
    -
    -
    - -
    {{ send.file.fileName }} ({{ send.file.sizeName }})
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    -
    - - -
    -
    -
    - -
    -

    - {{ "share" | i18n }} -

    -
    - -
    - - -
    -
    -
    - -
    -

    - -

    -
    -
    - -
    -
    - -
    - - -
    -
    - -
    -
    -
    - - -
    -
    - -
    - - -
    -
    - -
    - - -
    -
    - -
    -
    -
    -
    - - -
    - -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    -
    - - - -
    -
    - -
    -
    -
    - -
    - -
    -
    -
    - - -
    -
    - -
    - -
    -
    -
    - - -
    -
    -
    - -
    -
    -
    - - -
    -
    -
    -
    - -
    -
    - -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-add-edit.component.ts b/apps/browser/src/tools/popup/send/send-add-edit.component.ts deleted file mode 100644 index 66b0355bb76..00000000000 --- a/apps/browser/src/tools/popup/send/send-add-edit.component.ts +++ /dev/null @@ -1,140 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { DatePipe, Location } from "@angular/common"; -import { Component, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { FilePopoutUtilsService } from "../services/file-popout-utils.service"; - -@Component({ - selector: "app-send-add-edit", - templateUrl: "send-add-edit.component.html", -}) -// eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class SendAddEditComponent extends BaseAddEditComponent implements OnInit { - // Options header - showOptions = false; - // File visibility - isFirefox = false; - inPopout = false; - showFileSelector = false; - - constructor( - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - messagingService: MessagingService, - policyService: PolicyService, - environmentService: EnvironmentService, - datePipe: DatePipe, - sendService: SendService, - private route: ActivatedRoute, - private router: Router, - private location: Location, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - formBuilder: FormBuilder, - private filePopoutUtilsService: FilePopoutUtilsService, - billingAccountProfileStateService: BillingAccountProfileStateService, - accountService: AccountService, - toastService: ToastService, - ) { - super( - i18nService, - platformUtilsService, - environmentService, - datePipe, - sendService, - messagingService, - policyService, - logService, - stateService, - sendApiService, - dialogService, - formBuilder, - billingAccountProfileStateService, - accountService, - toastService, - ); - } - - popOutWindow() { - // 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 - BrowserPopupUtils.openCurrentPagePopout(window); - } - - async ngOnInit() { - // File visibility - this.showFileSelector = - !this.editMode && !this.filePopoutUtilsService.showFilePopoutMessage(window); - this.inPopout = BrowserPopupUtils.inPopout(window); - this.isFirefox = this.platformUtilsService.isFirefox(); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (params.sendId) { - this.sendId = params.sendId; - } - if (params.type) { - const type = parseInt(params.type, null); - this.type = type; - } - await super.ngOnInit(); - }); - - window.setTimeout(() => { - if (!this.editMode) { - document.getElementById("name").focus(); - } - }, 200); - } - - async submit(): Promise { - if (await super.submit()) { - this.cancel(); - return true; - } - - return false; - } - - async delete(): Promise { - if (await super.delete()) { - this.cancel(); - return true; - } - - return false; - } - - cancel() { - // If true, the window was pop'd out on the add-send page. location.back will not work - const isPopup = (window as any)?.previousPopupUrl?.startsWith("/add-send") ?? false; - if (!isPopup) { - // 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(["tabs/send"]); - } else { - this.location.back(); - } - } -} diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.html b/apps/browser/src/tools/popup/send/send-groupings.component.html deleted file mode 100644 index 213afdfa227..00000000000 --- a/apps/browser/src/tools/popup/send/send-groupings.component.html +++ /dev/null @@ -1,119 +0,0 @@ - -
    - -
    -

    {{ "send" | i18n }}

    - -
    - -
    -
    -
    - - {{ "sendDisabledWarning" | i18n }} - -
    - - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    - -
    -

    - {{ "types" | i18n }} -

    -
    - - -
    -
    -
    -

    - {{ "allSends" | i18n }} -
    {{ sends.length }}
    -

    -
    - -
    -
    -
    - -
    -

    {{ "noItemsInList" | i18n }}

    -
    -
    -
    - - -
    -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-groupings.component.ts b/apps/browser/src/tools/popup/send/send-groupings.component.ts deleted file mode 100644 index 80c2de1cdac..00000000000 --- a/apps/browser/src/tools/popup/send/send-groupings.component.ts +++ /dev/null @@ -1,203 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; - -import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserSendStateService } from "../services/browser-send-state.service"; - -const ComponentId = "SendComponent"; - -@Component({ - selector: "app-send-groupings", - templateUrl: "send-groupings.component.html", -}) -export class SendGroupingsComponent extends BaseSendComponent implements OnInit, OnDestroy { - // Header - showLeftHeader = true; - // State Handling - state: BrowserSendComponentState; - private loadedTimeout: number; - - constructor( - sendService: SendService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - ngZone: NgZone, - policyService: PolicyService, - searchService: SearchService, - private stateService: BrowserSendStateService, - private router: Router, - private syncService: SyncService, - private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - toastService: ToastService, - ) { - super( - sendService, - i18nService, - platformUtilsService, - environmentService, - ngZone, - searchService, - policyService, - logService, - sendApiService, - dialogService, - toastService, - ); - this.onSuccessfulLoad = async () => { - this.selectAll(); - }; - } - - async ngOnInit() { - // Determine Header details - this.showLeftHeader = !( - BrowserPopupUtils.inSidebar(window) && this.platformUtilsService.isFirefox() - ); - // Clear state of Send Type Component - await this.stateService.setBrowserSendTypeComponentState(null); - // Let super class finish - await super.ngOnInit(); - // Handle State Restore if necessary - const restoredScopeState = await this.restoreState(); - if (this.state?.searchText != null) { - this.searchText = this.state.searchText; - } - - if (!this.syncService.syncInProgress) { - // 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.load(); - } else { - this.loadedTimeout = window.setTimeout(() => { - if (!this.loaded) { - // 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.load(); - } - }, 5000); - } - - if (!this.syncService.syncInProgress || restoredScopeState) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - - // Load all sends if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - window.setTimeout(() => { - // 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.load(); - }, 500); - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // Remove timeout - if (this.loadedTimeout != null) { - window.clearTimeout(this.loadedTimeout); - } - // Save state - // 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.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); - } - - async selectType(type: SendType) { - // 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(["/send-type"], { queryParams: { type: type } }); - } - - async selectSend(s: SendView) { - // 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(["/edit-send"], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; - } - // 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(["/add-send"]); - } - - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; - } - // 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 - super.removePassword(s); - } - - showSearching() { - return this.hasSearched || (!this.searchPending && this.isSearchable); - } - - getSendCount(sends: SendView[], type: SendType): number { - return sends.filter((s) => s.type === type).length; - } - - private async saveState() { - this.state = Object.assign(new BrowserSendComponentState(), { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - sends: this.sends, - }); - await this.stateService.setBrowserSendComponentState(this.state); - } - - private async restoreState(): Promise { - this.state = await this.stateService.getBrowserSendComponentState(); - if (this.state == null) { - return false; - } - - if (this.state.sends != null) { - this.sends = this.state.sends; - } - - return true; - } -} diff --git a/apps/browser/src/tools/popup/send/send-type.component.html b/apps/browser/src/tools/popup/send/send-type.component.html deleted file mode 100644 index 0ad6bed2881..00000000000 --- a/apps/browser/src/tools/popup/send/send-type.component.html +++ /dev/null @@ -1,68 +0,0 @@ -
    -
    - -
    -

    {{ "send" | i18n }}

    - -
    - -
    -
    -
    - - {{ "sendDisabledWarning" | i18n }} - -
    - - -

    {{ "noItemsInList" | i18n }}

    - -
    -
    -
    -

    - {{ groupingTitle }} - {{ filteredSends.length }} -

    -
    - - -
    -
    -
    diff --git a/apps/browser/src/tools/popup/send/send-type.component.ts b/apps/browser/src/tools/popup/send/send-type.component.ts deleted file mode 100644 index bcc24b443fc..00000000000 --- a/apps/browser/src/tools/popup/send/send-type.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Location } from "@angular/common"; -import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { first } from "rxjs/operators"; - -import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component"; -import { SearchService } from "@bitwarden/common/abstractions/search.service"; -import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; -import { SendView } from "@bitwarden/common/tools/send/models/view/send.view"; -import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; -import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import BrowserPopupUtils from "../../../platform/popup/browser-popup-utils"; -import { BrowserSendStateService } from "../services/browser-send-state.service"; - -const ComponentId = "SendTypeComponent"; - -@Component({ - selector: "app-send-type", - templateUrl: "send-type.component.html", -}) -export class SendTypeComponent extends BaseSendComponent implements OnInit, OnDestroy { - groupingTitle: string; - // State Handling - state: BrowserComponentState; - private refreshTimeout: number; - private applySavedState = true; - - constructor( - sendService: SendService, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - ngZone: NgZone, - policyService: PolicyService, - searchService: SearchService, - private stateService: BrowserSendStateService, - private route: ActivatedRoute, - private location: Location, - private changeDetectorRef: ChangeDetectorRef, - private broadcasterService: BroadcasterService, - private router: Router, - logService: LogService, - sendApiService: SendApiService, - dialogService: DialogService, - toastService: ToastService, - ) { - super( - sendService, - i18nService, - platformUtilsService, - environmentService, - ngZone, - searchService, - policyService, - logService, - sendApiService, - dialogService, - toastService, - ); - this.onSuccessfulLoad = async () => { - this.selectType(this.type); - }; - this.applySavedState = - (window as any).previousPopupUrl != null && - !(window as any).previousPopupUrl.startsWith("/send-type"); - } - - async ngOnInit() { - // Let super class finish - await super.ngOnInit(); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (params) => { - if (this.applySavedState) { - this.state = await this.stateService.getBrowserSendTypeComponentState(); - if (this.state?.searchText != null) { - this.searchText = this.state.searchText; - } - } - - if (params.type != null) { - this.type = parseInt(params.type, null); - switch (this.type) { - case SendType.Text: - this.groupingTitle = this.i18nService.t("sendTypeText"); - break; - case SendType.File: - this.groupingTitle = this.i18nService.t("sendTypeFile"); - break; - default: - break; - } - await this.load((s) => s.type === this.type); - } - - // Restore state and remove reference - if (this.applySavedState && this.state != null) { - // 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 - BrowserPopupUtils.setContentScrollY(window, this.state?.scrollY); - } - // 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.stateService.setBrowserSendTypeComponentState(null); - }); - - // Refresh Send list if sync completed in background - this.broadcasterService.subscribe(ComponentId, (message: any) => { - // 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.ngZone.run(async () => { - switch (message.command) { - case "syncCompleted": - if (message.successfully) { - this.refreshTimeout = window.setTimeout(() => { - // 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.refresh(); - }, 500); - } - break; - default: - break; - } - - this.changeDetectorRef.detectChanges(); - }); - }); - } - - ngOnDestroy() { - // Remove timeout - if (this.refreshTimeout != null) { - window.clearTimeout(this.refreshTimeout); - } - // Save state - // 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.saveState(); - // Unsubscribe - this.broadcasterService.unsubscribe(ComponentId); - } - - async selectSend(s: SendView) { - // 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(["/edit-send"], { queryParams: { sendId: s.id } }); - } - - async addSend() { - if (this.disableSend) { - return; - } - // 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(["/add-send"], { queryParams: { type: this.type } }); - } - - async removePassword(s: SendView): Promise { - if (this.disableSend) { - return; - } - // 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 - super.removePassword(s); - } - - back() { - (window as any).routeDirection = "b"; - this.location.back(); - } - - private async saveState() { - this.state = { - scrollY: BrowserPopupUtils.getContentScrollY(window), - searchText: this.searchText, - }; - await this.stateService.setBrowserSendTypeComponentState(this.state); - } -} diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts deleted file mode 100644 index 6f0ae1455ad..00000000000 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - FakeAccountService, - mockAccountServiceWith, -} from "@bitwarden/common/../spec/fake-account-service"; -import { FakeStateProvider } from "@bitwarden/common/../spec/fake-state-provider"; -import { awaitAsync } from "@bitwarden/common/../spec/utils"; - -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BrowserSendStateService } from "./browser-send-state.service"; - -describe("Browser Send State Service", () => { - let stateProvider: FakeStateProvider; - - let accountService: FakeAccountService; - let stateService: BrowserSendStateService; - const mockUserId = Utils.newGuid() as UserId; - - beforeEach(() => { - accountService = mockAccountServiceWith(mockUserId); - stateProvider = new FakeStateProvider(accountService); - - stateService = new BrowserSendStateService(stateProvider); - }); - - describe("getBrowserSendComponentState", () => { - it("should return BrowserSendComponentState", async () => { - const state = new BrowserSendComponentState(); - state.scrollY = 0; - state.searchText = "test"; - - await stateService.setBrowserSendComponentState(state); - - await awaitAsync(); - - const actual = await stateService.getBrowserSendComponentState(); - expect(actual).toStrictEqual(state); - }); - }); - - describe("getBrowserSendTypeComponentState", () => { - it("should return BrowserComponentState", async () => { - const state = new BrowserComponentState(); - state.scrollY = 0; - state.searchText = "test"; - - await stateService.setBrowserSendTypeComponentState(state); - - await awaitAsync(); - - const actual = await stateService.getBrowserSendTypeComponentState(); - expect(actual).toStrictEqual(state); - }); - }); -}); diff --git a/apps/browser/src/tools/popup/services/browser-send-state.service.ts b/apps/browser/src/tools/popup/services/browser-send-state.service.ts deleted file mode 100644 index d6c5ae4fd10..00000000000 --- a/apps/browser/src/tools/popup/services/browser-send-state.service.ts +++ /dev/null @@ -1,71 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Observable, firstValueFrom } from "rxjs"; - -import { ActiveUserState, StateProvider } from "@bitwarden/common/platform/state"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; - -/** Get or set the active user's component state for the Send browser component - */ -export class BrowserSendStateService { - /** Observable that contains the current state for active user Sends including the send data and type counts - * along with the search text and scroll position - */ - browserSendComponentState$: Observable; - - /** Observable that contains the current state for active user Sends that only includes the search text - * and scroll position - */ - browserSendTypeComponentState$: Observable; - - private activeUserBrowserSendComponentState: ActiveUserState; - private activeUserBrowserSendTypeComponentState: ActiveUserState; - - constructor(protected stateProvider: StateProvider) { - this.activeUserBrowserSendComponentState = this.stateProvider.getActive(BROWSER_SEND_COMPONENT); - this.browserSendComponentState$ = this.activeUserBrowserSendComponentState.state$; - - this.activeUserBrowserSendTypeComponentState = this.stateProvider.getActive( - BROWSER_SEND_TYPE_COMPONENT, - ); - this.browserSendTypeComponentState$ = this.activeUserBrowserSendTypeComponentState.state$; - } - - /** Get the active user's browser send component state - * @returns { BrowserSendComponentState } contains the sends and type counts along with the scroll position and search text for the - * send component on the browser - */ - async getBrowserSendComponentState(): Promise { - return await firstValueFrom(this.browserSendComponentState$); - } - - /** Set the active user's browser send component state - * @param { BrowserSendComponentState } value sets the sends along with the scroll position and search text for - * the send component on the browser - */ - async setBrowserSendComponentState(value: BrowserSendComponentState): Promise { - await this.activeUserBrowserSendComponentState.update(() => value, { - shouldUpdate: (current) => !(current == null && value == null), - }); - } - - /** Get the active user's browser component state - * @returns { BrowserComponentState } contains the scroll position and search text for the sends menu on the browser - */ - async getBrowserSendTypeComponentState(): Promise { - return await firstValueFrom(this.browserSendTypeComponentState$); - } - - /** Set the active user's browser component state - * @param { BrowserComponentState } value set the scroll position and search text for the send component on the browser - */ - async setBrowserSendTypeComponentState(value: BrowserComponentState): Promise { - await this.activeUserBrowserSendTypeComponentState.update(() => value, { - shouldUpdate: (current) => !(current == null && value == null), - }); - } -} diff --git a/apps/browser/src/tools/popup/services/key-definitions.spec.ts b/apps/browser/src/tools/popup/services/key-definitions.spec.ts deleted file mode 100644 index 7517771669c..00000000000 --- a/apps/browser/src/tools/popup/services/key-definitions.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Jsonify } from "type-fest"; - -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -import { BROWSER_SEND_COMPONENT, BROWSER_SEND_TYPE_COMPONENT } from "./key-definitions"; - -describe("Key definitions", () => { - describe("BROWSER_SEND_COMPONENT", () => { - it("should deserialize BrowserSendComponentState", () => { - const keyDef = BROWSER_SEND_COMPONENT; - - const expectedState = { - scrollY: 0, - searchText: "test", - }; - - const result = keyDef.deserializer( - JSON.parse(JSON.stringify(expectedState)) as Jsonify, - ); - - expect(result).toEqual(expectedState); - }); - }); - - describe("BROWSER_SEND_TYPE_COMPONENT", () => { - it("should deserialize BrowserComponentState", () => { - const keyDef = BROWSER_SEND_TYPE_COMPONENT; - - const expectedState = { - scrollY: 0, - searchText: "test", - }; - - const result = keyDef.deserializer(JSON.parse(JSON.stringify(expectedState))); - - expect(result).toEqual(expectedState); - }); - }); -}); diff --git a/apps/browser/src/tools/popup/services/key-definitions.ts b/apps/browser/src/tools/popup/services/key-definitions.ts deleted file mode 100644 index 87c6da126cb..00000000000 --- a/apps/browser/src/tools/popup/services/key-definitions.ts +++ /dev/null @@ -1,27 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Jsonify } from "type-fest"; - -import { BROWSER_SEND_MEMORY, UserKeyDefinition } from "@bitwarden/common/platform/state"; - -import { BrowserComponentState } from "../../../models/browserComponentState"; -import { BrowserSendComponentState } from "../../../models/browserSendComponentState"; - -export const BROWSER_SEND_COMPONENT = new UserKeyDefinition( - BROWSER_SEND_MEMORY, - "browser_send_component", - { - deserializer: (obj: Jsonify) => - BrowserSendComponentState.fromJSON(obj), - clearOn: ["logout", "lock"], - }, -); - -export const BROWSER_SEND_TYPE_COMPONENT = new UserKeyDefinition( - BROWSER_SEND_MEMORY, - "browser_send_type_component", - { - deserializer: (obj: Jsonify) => BrowserComponentState.fromJSON(obj), - clearOn: ["logout", "lock"], - }, -); From 23210f4a39f7bf61c8b6224b9eda7c1d29cd7380 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 23 Dec 2024 10:37:12 -0500 Subject: [PATCH 11/13] [PM-16102] Display autofill shortcut on Fill button hover (#12502) --- .../vault-list-items-container.component.html | 2 +- .../vault-list-items-container.component.ts | 29 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html index a493ea2171c..067c8dbdf0b 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.html @@ -55,7 +55,7 @@

    bitBadge variant="primary" (click)="doAutofill(cipher)" - [title]="'autofillTitle' | i18n: cipher.name" + [title]="autofillShortcutTooltip() ?? ('autofillTitle' | i18n: cipher.name)" [attr.aria-label]="'autofillTitle' | i18n: cipher.name" > {{ "fill" | i18n }} diff --git a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts index 3b139d79015..b4022560c35 100644 --- a/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts +++ b/apps/browser/src/vault/popup/components/vault-v2/vault-list-items-container/vault-list-items-container.component.ts @@ -2,12 +2,22 @@ // @ts-strict-ignore import { ScrollingModule } from "@angular/cdk/scrolling"; import { CommonModule } from "@angular/common"; -import { booleanAttribute, Component, EventEmitter, inject, Input, Output } from "@angular/core"; +import { + AfterViewInit, + booleanAttribute, + Component, + EventEmitter, + inject, + Input, + Output, + signal, +} from "@angular/core"; import { Router, RouterLink } from "@angular/router"; import { map } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; import { @@ -50,7 +60,7 @@ import { ItemMoreOptionsComponent } from "../item-more-options/item-more-options templateUrl: "vault-list-items-container.component.html", standalone: true, }) -export class VaultListItemsContainerComponent { +export class VaultListItemsContainerComponent implements AfterViewInit { private compactModeService = inject(CompactModeService); /** @@ -133,14 +143,29 @@ export class VaultListItemsContainerComponent { return cipher.collections[0]?.name; } + protected autofillShortcutTooltip = signal(undefined); + constructor( private i18nService: I18nService, private vaultPopupAutofillService: VaultPopupAutofillService, private passwordRepromptService: PasswordRepromptService, private cipherService: CipherService, private router: Router, + private platformUtilsService: PlatformUtilsService, ) {} + async ngAfterViewInit() { + const autofillShortcut = await this.platformUtilsService.getAutofillKeyboardShortcut(); + + if (autofillShortcut === "") { + this.autofillShortcutTooltip.set(undefined); + } else { + const autofillTitle = this.i18nService.t("autoFill"); + + this.autofillShortcutTooltip.set(`${autofillTitle} ${autofillShortcut}`); + } + } + /** * Launches the login cipher in a new browser tab. */ From 395258d63ed39305a6fa7f21becbc842210a8db5 Mon Sep 17 00:00:00 2001 From: Victoria League Date: Mon, 23 Dec 2024 12:08:52 -0500 Subject: [PATCH 12/13] [PM-16102] Add min width on interactive badges (#12514) --- libs/components/src/badge/badge.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/components/src/badge/badge.component.ts b/libs/components/src/badge/badge.component.ts index 29644a11ac7..0f8a64ee998 100644 --- a/libs/components/src/badge/badge.component.ts +++ b/libs/components/src/badge/badge.component.ts @@ -65,7 +65,7 @@ export class BadgeComponent implements FocusableElement { "disabled:tw-cursor-not-allowed", ] .concat(styles[this.variant]) - .concat(this.hasHoverEffects ? hoverStyles[this.variant] : []) + .concat(this.hasHoverEffects ? [...hoverStyles[this.variant], "tw-min-w-10"] : []) .concat(this.truncate ? this.maxWidthClass : []); } @HostBinding("attr.title") get titleAttr() { From 35f4edddcff85b55e191857407c7873c60174f8e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 09:41:40 -0500 Subject: [PATCH 13/13] [deps] Platform: Update @types/node to v22.10.2 (#12544) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../native-messaging-test-runner/package-lock.json | 8 ++++---- apps/desktop/native-messaging-test-runner/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/desktop/native-messaging-test-runner/package-lock.json b/apps/desktop/native-messaging-test-runner/package-lock.json index 469f8bd1da1..4bf939c8b41 100644 --- a/apps/desktop/native-messaging-test-runner/package-lock.json +++ b/apps/desktop/native-messaging-test-runner/package-lock.json @@ -18,7 +18,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" } @@ -106,9 +106,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" diff --git a/apps/desktop/native-messaging-test-runner/package.json b/apps/desktop/native-messaging-test-runner/package.json index e32b9e8e987..6ab267be4a8 100644 --- a/apps/desktop/native-messaging-test-runner/package.json +++ b/apps/desktop/native-messaging-test-runner/package.json @@ -23,7 +23,7 @@ "yargs": "17.7.2" }, "devDependencies": { - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-ipc": "9.2.3", "typescript": "4.7.4" }, diff --git a/package-lock.json b/package-lock.json index a8ae6daf236..6112ddbd966 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,7 +111,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3", @@ -10072,9 +10072,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 03991f9d70b..5522f9c5be5 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@types/koa-json": "2.0.23", "@types/lowdb": "1.0.15", "@types/lunr": "2.3.7", - "@types/node": "22.10.1", + "@types/node": "22.10.2", "@types/node-fetch": "2.6.4", "@types/node-forge": "1.3.11", "@types/node-ipc": "9.2.3",