From 0959069b76fe06781a2d16310a64e88e3dd7c0bb Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 19 Nov 2024 13:46:56 -0500 Subject: [PATCH 001/113] PM-8113 - Deprecate TwoFactorComponentRefactor feature flag in favor of UnauthenticatedExtensionUIRefresh flag --- apps/browser/src/popup/app-routing.module.ts | 3 +- apps/desktop/src/app/app-routing.module.ts | 3 +- apps/web/src/app/oss-routing.module.ts | 3 +- ...wo-factor-component-refactor-route-swap.ts | 31 ------------------- libs/common/src/enums/feature-flag.enum.ts | 2 -- 5 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 libs/angular/src/utils/two-factor-component-refactor-route-swap.ts diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index d53e51e9df2..a68e9d201db 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -38,7 +38,6 @@ import { } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { EnvironmentComponent } from "../auth/popup/environment.component"; @@ -190,7 +189,7 @@ const routes: Routes = [ canMatch: [extensionRefreshRedirect("/lockV2")], data: { state: "lock", doNotSaveUrl: true } satisfies RouteDataProperties, }, - ...twofactorRefactorSwap( + ...unauthUiRefreshSwap( TwoFactorComponent, AnonLayoutWrapperComponent, { diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index f5023cb4249..73887fbcb6f 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -35,7 +35,6 @@ import { } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { HintComponent } from "../auth/hint.component"; @@ -83,7 +82,7 @@ const routes: Routes = [ path: "admin-approval-requested", component: LoginViaAuthRequestComponent, }, - ...twofactorRefactorSwap( + ...unauthUiRefreshSwap( TwoFactorComponent, AnonLayoutWrapperComponent, { diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index b208bb3f8d1..0af41b4ac35 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -34,7 +34,6 @@ import { } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap"; import { flagEnabled, Flags } from "../utils/flags"; import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component"; @@ -457,7 +456,7 @@ const routes: Routes = [ path: "2fa", canActivate: [unauthGuardFn()], children: [ - ...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, { + ...unauthUiRefreshSwap(TwoFactorComponent, TwoFactorAuthComponent, { path: "", }), { diff --git a/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts b/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts deleted file mode 100644 index 8b57a3eb94f..00000000000 --- a/libs/angular/src/utils/two-factor-component-refactor-route-swap.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Type, inject } from "@angular/core"; -import { Route, Routes } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { componentRouteSwap } from "./component-route-swap"; -/** - * Helper function to swap between two components based on the TwoFactorComponentRefactor feature flag. - * @param defaultComponent - The current non-refactored component to render. - * @param refreshedComponent - The new refactored component to render. - * @param defaultOptions - The options to apply to the default component and the refactored component, if alt options are not provided. - * @param altOptions - The options to apply to the refactored component. - */ -export function twofactorRefactorSwap( - defaultComponent: Type, - refreshedComponent: Type, - defaultOptions: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.TwoFactorComponentRefactor); - }, - defaultOptions, - altOptions, - ); -} diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index ec5a8e47d73..d4d1d2c8074 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -14,7 +14,6 @@ export enum FeatureFlag { UseTreeWalkerApiForPageDetailsCollection = "use-tree-walker-api-for-page-details-collection", EmailVerification = "email-verification", InlineMenuFieldQualification = "inline-menu-field-qualification", - TwoFactorComponentRefactor = "two-factor-component-refactor", InlineMenuPositioningImprovements = "inline-menu-positioning-improvements", ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner", VaultBulkManagementAction = "vault-bulk-management-action", @@ -64,7 +63,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.UseTreeWalkerApiForPageDetailsCollection]: FALSE, [FeatureFlag.EmailVerification]: FALSE, [FeatureFlag.InlineMenuFieldQualification]: FALSE, - [FeatureFlag.TwoFactorComponentRefactor]: FALSE, [FeatureFlag.InlineMenuPositioningImprovements]: FALSE, [FeatureFlag.ProviderClientVaultPrivacyBanner]: FALSE, [FeatureFlag.VaultBulkManagementAction]: FALSE, From 4d0695778b118d6527d15e9123c0947db8d32575 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 21 Nov 2024 12:32:07 -0500 Subject: [PATCH 002/113] PM-8113 - Rename all existing 2FA components as V1. --- ...o-factor.component.html => two-factor-v1.component.html} | 0 .../{two-factor.component.ts => two-factor-v1.component.ts} | 6 +++--- apps/browser/src/popup/app-routing.module.ts | 4 ++-- apps/browser/src/popup/app.module.ts | 4 ++-- apps/desktop/src/app/app-routing.module.ts | 4 ++-- apps/desktop/src/app/app.module.ts | 4 ++-- ...o-factor.component.html => two-factor-v1.component.html} | 0 .../{two-factor.component.ts => two-factor-v1.component.ts} | 6 +++--- ...o-factor.component.html => two-factor-v1.component.html} | 0 .../{two-factor.component.ts => two-factor-v1.component.ts} | 6 +++--- apps/web/src/app/oss-routing.module.ts | 4 ++-- apps/web/src/app/shared/loose-components.module.ts | 6 +++--- ...or.component.spec.ts => two-factor-v1.component.spec.ts} | 4 ++-- .../{two-factor.component.ts => two-factor-v1.component.ts} | 2 +- 14 files changed, 25 insertions(+), 25 deletions(-) rename apps/browser/src/auth/popup/{two-factor.component.html => two-factor-v1.component.html} (100%) rename apps/browser/src/auth/popup/{two-factor.component.ts => two-factor-v1.component.ts} (97%) rename apps/desktop/src/auth/{two-factor.component.html => two-factor-v1.component.html} (100%) rename apps/desktop/src/auth/{two-factor.component.ts => two-factor-v1.component.ts} (96%) rename apps/web/src/app/auth/{two-factor.component.html => two-factor-v1.component.html} (100%) rename apps/web/src/app/auth/{two-factor.component.ts => two-factor-v1.component.ts} (95%) rename libs/angular/src/auth/components/{two-factor.component.spec.ts => two-factor-v1.component.spec.ts} (99%) rename libs/angular/src/auth/components/{two-factor.component.ts => two-factor-v1.component.ts} (99%) diff --git a/apps/browser/src/auth/popup/two-factor.component.html b/apps/browser/src/auth/popup/two-factor-v1.component.html similarity index 100% rename from apps/browser/src/auth/popup/two-factor.component.html rename to apps/browser/src/auth/popup/two-factor-v1.component.html diff --git a/apps/browser/src/auth/popup/two-factor.component.ts b/apps/browser/src/auth/popup/two-factor-v1.component.ts similarity index 97% rename from apps/browser/src/auth/popup/two-factor.component.ts rename to apps/browser/src/auth/popup/two-factor-v1.component.ts index 27c4604be91..1d518188252 100644 --- a/apps/browser/src/auth/popup/two-factor.component.ts +++ b/apps/browser/src/auth/popup/two-factor-v1.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { Subject, Subscription, firstValueFrom } from "rxjs"; import { filter, first, takeUntil } from "rxjs/operators"; -import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; +import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -35,9 +35,9 @@ import { closeTwoFactorAuthPopout } from "./utils/auth-popout-window"; @Component({ selector: "app-two-factor", - templateUrl: "two-factor.component.html", + templateUrl: "two-factor-v1.component.html", }) -export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit, OnDestroy { +export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); inPopout = BrowserPopupUtils.inPopout(window); diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index a68e9d201db..70572073a14 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -59,7 +59,7 @@ import { AccountSecurityComponent } from "../auth/popup/settings/account-securit import { SsoComponent } from "../auth/popup/sso.component"; import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; -import { TwoFactorComponent } from "../auth/popup/two-factor.component"; +import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { Fido2V1Component } from "../autofill/popup/fido2/fido2-v1.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; @@ -190,7 +190,7 @@ const routes: Routes = [ data: { state: "lock", doNotSaveUrl: true } satisfies RouteDataProperties, }, ...unauthUiRefreshSwap( - TwoFactorComponent, + TwoFactorComponentV1, AnonLayoutWrapperComponent, { path: "2fa", diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 2d0ccd1d1c0..064f33bb30c 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -35,7 +35,7 @@ import { AccountSecurityComponent } from "../auth/popup/settings/account-securit import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component"; import { SsoComponent } from "../auth/popup/sso.component"; import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component"; -import { TwoFactorComponent } from "../auth/popup/two-factor.component"; +import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { Fido2CipherRowV1Component } from "../autofill/popup/fido2/fido2-cipher-row-v1.component"; import { Fido2CipherRowComponent } from "../autofill/popup/fido2/fido2-cipher-row.component"; @@ -181,7 +181,7 @@ import "../platform/popup/locales"; SyncComponent, TabsComponent, TabsV2Component, - TwoFactorComponent, + TwoFactorComponentV1, TwoFactorOptionsComponent, UpdateTempPasswordComponent, UserVerificationComponent, diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 73887fbcb6f..24c822c002d 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -47,7 +47,7 @@ import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { SsoComponent } from "../auth/sso.component"; import { TwoFactorAuthComponent } from "../auth/two-factor-auth.component"; -import { TwoFactorComponent } from "../auth/two-factor.component"; +import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { VaultComponent } from "../vault/app/vault/vault.component"; @@ -83,7 +83,7 @@ const routes: Routes = [ component: LoginViaAuthRequestComponent, }, ...unauthUiRefreshSwap( - TwoFactorComponent, + TwoFactorComponentV1, AnonLayoutWrapperComponent, { path: "2fa", diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index d787234e8b3..6eed95bec76 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -20,7 +20,7 @@ import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { SsoComponent } from "../auth/sso.component"; import { TwoFactorOptionsComponent } from "../auth/two-factor-options.component"; -import { TwoFactorComponent } from "../auth/two-factor.component"; +import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { SshAgentService } from "../platform/services/ssh-agent.service"; import { PremiumComponent } from "../vault/app/accounts/premium.component"; @@ -93,7 +93,7 @@ import { SendComponent } from "./tools/send/send.component"; SettingsComponent, ShareComponent, SsoComponent, - TwoFactorComponent, + TwoFactorComponentV1, TwoFactorOptionsComponent, UpdateTempPasswordComponent, VaultComponent, diff --git a/apps/desktop/src/auth/two-factor.component.html b/apps/desktop/src/auth/two-factor-v1.component.html similarity index 100% rename from apps/desktop/src/auth/two-factor.component.html rename to apps/desktop/src/auth/two-factor-v1.component.html diff --git a/apps/desktop/src/auth/two-factor.component.ts b/apps/desktop/src/auth/two-factor-v1.component.ts similarity index 96% rename from apps/desktop/src/auth/two-factor.component.ts rename to apps/desktop/src/auth/two-factor-v1.component.ts index 0050ec65608..532b00f2522 100644 --- a/apps/desktop/src/auth/two-factor.component.ts +++ b/apps/desktop/src/auth/two-factor-v1.component.ts @@ -2,7 +2,7 @@ import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } fro import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; -import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; +import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { ModalService } from "@bitwarden/angular/services/modal.service"; import { @@ -33,10 +33,10 @@ const BroadcasterSubscriptionId = "TwoFactorComponent"; @Component({ selector: "app-two-factor", - templateUrl: "two-factor.component.html", + templateUrl: "two-factor-v1.component.html", }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDestroy { +export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnDestroy { @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef; diff --git a/apps/web/src/app/auth/two-factor.component.html b/apps/web/src/app/auth/two-factor-v1.component.html similarity index 100% rename from apps/web/src/app/auth/two-factor.component.html rename to apps/web/src/app/auth/two-factor-v1.component.html diff --git a/apps/web/src/app/auth/two-factor.component.ts b/apps/web/src/app/auth/two-factor-v1.component.ts similarity index 95% rename from apps/web/src/app/auth/two-factor.component.ts rename to apps/web/src/app/auth/two-factor-v1.component.ts index 691170233c8..9298bef7e0f 100644 --- a/apps/web/src/app/auth/two-factor.component.ts +++ b/apps/web/src/app/auth/two-factor-v1.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { Subject, takeUntil, lastValueFrom } from "rxjs"; -import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; +import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -33,10 +33,10 @@ import { @Component({ selector: "app-two-factor", - templateUrl: "two-factor.component.html", + templateUrl: "two-factor-v1.component.html", }) // eslint-disable-next-line rxjs-angular/prefer-takeuntil -export class TwoFactorComponent extends BaseTwoFactorComponent implements OnInit, OnDestroy { +export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy { @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) twoFactorOptionsModal: ViewContainerRef; formGroup = this.formBuilder.group({ diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 0af41b4ac35..55f822317f2 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -61,7 +61,7 @@ import { CompleteTrialInitiationComponent } from "./auth/trial-initiation/comple import { freeTrialTextResolver } from "./auth/trial-initiation/complete-trial-initiation/resolver/free-trial-text.resolver"; import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component"; import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component"; -import { TwoFactorComponent } from "./auth/two-factor.component"; +import { TwoFactorComponentV1 } from "./auth/two-factor-v1.component"; import { UpdatePasswordComponent } from "./auth/update-password.component"; import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "./auth/verify-email-token.component"; @@ -456,7 +456,7 @@ const routes: Routes = [ path: "2fa", canActivate: [unauthGuardFn()], children: [ - ...unauthUiRefreshSwap(TwoFactorComponent, TwoFactorAuthComponent, { + ...unauthUiRefreshSwap(TwoFactorComponentV1, TwoFactorAuthComponent, { path: "", }), { diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index a238f2110ce..ece7a1312a8 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -52,7 +52,7 @@ import { TwoFactorYubiKeyComponent } from "../auth/settings/two-factor-yubikey.c import { UserVerificationModule } from "../auth/shared/components/user-verification"; import { SsoComponent } from "../auth/sso.component"; import { TwoFactorOptionsComponent } from "../auth/two-factor-options.component"; -import { TwoFactorComponent } from "../auth/two-factor.component"; +import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component"; import { UpdatePasswordComponent } from "../auth/update-password.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { VerifyEmailTokenComponent } from "../auth/verify-email-token.component"; @@ -160,7 +160,7 @@ import { SharedModule } from "./shared.module"; SponsoringOrgRowComponent, SsoComponent, TwoFactorAuthenticatorComponent, - TwoFactorComponent, + TwoFactorComponentV1, TwoFactorDuoComponent, TwoFactorEmailComponent, TwoFactorOptionsComponent, @@ -227,7 +227,7 @@ import { SharedModule } from "./shared.module"; SponsoringOrgRowComponent, SsoComponent, TwoFactorAuthenticatorComponent, - TwoFactorComponent, + TwoFactorComponentV1, TwoFactorDuoComponent, TwoFactorEmailComponent, TwoFactorOptionsComponent, diff --git a/libs/angular/src/auth/components/two-factor.component.spec.ts b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts similarity index 99% rename from libs/angular/src/auth/components/two-factor.component.spec.ts rename to libs/angular/src/auth/components/two-factor-v1.component.spec.ts index e21d119adf8..55be60aafdd 100644 --- a/libs/angular/src/auth/components/two-factor.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts @@ -34,11 +34,11 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { ToastService } from "@bitwarden/components"; -import { TwoFactorComponent } from "./two-factor.component"; +import { TwoFactorComponentV1 } from "./two-factor-v1.component"; // test component that extends the TwoFactorComponent @Component({}) -class TestTwoFactorComponent extends TwoFactorComponent {} +class TestTwoFactorComponent extends TwoFactorComponentV1 {} interface TwoFactorComponentProtected { trustedDeviceEncRoute: string; diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor-v1.component.ts similarity index 99% rename from libs/angular/src/auth/components/two-factor.component.ts rename to libs/angular/src/auth/components/two-factor-v1.component.ts index eaff9d665fd..70de10c19c3 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor-v1.component.ts @@ -37,7 +37,7 @@ import { ToastService } from "@bitwarden/components"; import { CaptchaProtectedComponent } from "./captcha-protected.component"; @Directive() -export class TwoFactorComponent extends CaptchaProtectedComponent implements OnInit, OnDestroy { +export class TwoFactorComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy { token = ""; remember = false; webAuthnReady = false; From 411385e9be4b0d0da2fc8582e77ff90645c3eb6d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 21 Nov 2024 17:46:31 -0500 Subject: [PATCH 003/113] PM-8113 - TwoFactorAuthComp - Add comment explaining that tagged unused import is used a dialog. --- .../components/two-factor-auth/two-factor-auth.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts index 58edeed93e9..322aa43afc8 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts @@ -62,7 +62,7 @@ import { AsyncActionsModule, RouterLink, ButtonModule, - TwoFactorOptionsComponent, + TwoFactorOptionsComponent, // used as dialog TwoFactorAuthAuthenticatorComponent, TwoFactorAuthEmailComponent, TwoFactorAuthDuoComponent, From 2ac5348480275d17aa4504ad2fa01d1ec27f6f27 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 3 Dec 2024 15:02:34 -0500 Subject: [PATCH 004/113] PM-8113 - 2FA Auth Comp - deprecate captcha --- .../two-factor-auth.component.ts | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts index 322aa43afc8..7205938d307 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.ts @@ -25,7 +25,6 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -37,8 +36,6 @@ import { ToastService, } from "@bitwarden/components"; -import { CaptchaProtectedComponent } from "../captcha-protected.component"; - import { TwoFactorAuthAuthenticatorComponent } from "./two-factor-auth-authenticator.component"; import { TwoFactorAuthDuoComponent } from "./two-factor-auth-duo.component"; import { TwoFactorAuthEmailComponent } from "./two-factor-auth-email.component"; @@ -71,7 +68,7 @@ import { ], providers: [I18nPipe], }) -export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements OnInit { +export class TwoFactorAuthComponent implements OnInit { token = ""; remember = false; orgIdentifier: string = null; @@ -128,9 +125,8 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements constructor( protected loginStrategyService: LoginStrategyServiceAbstraction, protected router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, + private i18nService: I18nService, + private platformUtilsService: PlatformUtilsService, private dialogService: DialogService, protected route: ActivatedRoute, private logService: LogService, @@ -144,9 +140,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements private formBuilder: FormBuilder, @Inject(WINDOW) protected win: Window, protected toastService: ToastService, - ) { - super(environmentService, i18nService, platformUtilsService, toastService); - } + ) {} async ngOnInit() { if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) { @@ -183,8 +177,6 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements } async submit() { - await this.setupCaptcha(); - if (this.token == null || this.token === "") { this.toastService.showToast({ variant: "error", @@ -197,7 +189,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements try { this.formPromise = this.loginStrategyService.logInTwoFactor( new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember), - this.captchaToken, + null, ); const authResult: AuthResult = await this.formPromise; this.logService.info("Successfully submitted two factor token"); @@ -251,9 +243,7 @@ export class TwoFactorAuthComponent extends CaptchaProtectedComponent implements } private async handleLoginResponse(authResult: AuthResult) { - if (this.handleCaptchaRequired(authResult)) { - return; - } else if (this.handleMigrateEncryptionKey(authResult)) { + if (this.handleMigrateEncryptionKey(authResult)) { return; } From fd8923a18785e50273f37b3c2769f48bf74c6d76 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 3 Dec 2024 15:02:57 -0500 Subject: [PATCH 005/113] PM-8113 - LoginStrategySvc - add todo for deprecation of captcha response --- libs/auth/src/common/abstractions/login-strategy.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index a46636532bf..7aeaf7ce7c5 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -65,6 +65,7 @@ export abstract class LoginStrategyServiceAbstraction { */ logInTwoFactor: ( twoFactor: TokenTwoFactorRequest, + // TODO: PM-15162 - deprecate captchaResponse captchaResponse: string, ) => Promise; /** From 21af124dc21a5d01cbc02a59cfc942edcbf37547 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 3 Dec 2024 15:10:08 -0500 Subject: [PATCH 006/113] PM-8113 - TwoFactorAuth tests - remove captcha --- .../two-factor-auth.component.spec.ts | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts index 755813a677a..7d43e8b0a6e 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.spec.ts @@ -25,10 +25,6 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { - Environment, - 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"; @@ -64,7 +60,6 @@ describe("TwoFactorComponent", () => { let mockApiService: MockProxy; let mockPlatformUtilsService: MockProxy; let mockWin: MockProxy; - let mockEnvironmentService: MockProxy; let mockStateService: MockProxy; let mockLogService: MockProxy; let mockTwoFactorService: MockProxy; @@ -98,10 +93,6 @@ describe("TwoFactorComponent", () => { mockApiService = mock(); mockPlatformUtilsService = mock(); mockWin = mock(); - const mockEnvironment = mock(); - mockEnvironment.getWebVaultUrl.mockReturnValue("http://example.com"); - mockEnvironmentService = mock(); - mockEnvironmentService.environment$ = new BehaviorSubject(mockEnvironment); mockStateService = mock(); mockLogService = mock(); @@ -171,7 +162,6 @@ describe("TwoFactorComponent", () => { { provide: ApiService, useValue: mockApiService }, { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, { provide: WINDOW, useValue: mockWin }, - { provide: EnvironmentService, useValue: mockEnvironmentService }, { provide: StateService, useValue: mockStateService }, { provide: ActivatedRoute, @@ -247,12 +237,10 @@ describe("TwoFactorComponent", () => { describe("submit", () => { const token = "testToken"; const remember = false; - const captchaToken = "testCaptchaToken"; beforeEach(() => { component.token = token; component.remember = remember; - component.captchaToken = captchaToken; selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); }); @@ -267,33 +255,10 @@ describe("TwoFactorComponent", () => { // Assert expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith( new TokenTwoFactorRequest(component.selectedProviderType, token, remember), - captchaToken, + null, // captcha token not supported ); }); - it("should return when handleCaptchaRequired returns true", async () => { - // Arrange - const captchaSiteKey = "testCaptchaSiteKey"; - const authResult = new AuthResult(); - authResult.captchaSiteKey = captchaSiteKey; - - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - - // Note: the any casts are required b/c typescript cant recognize that - // handleCaptureRequired is a method on TwoFactorComponent b/c it is inherited - // from the CaptchaProtectedComponent - const handleCaptchaRequiredSpy = jest - .spyOn(component, "handleCaptchaRequired") - .mockReturnValue(true); - - // Act - const result = await component.submit(); - - // Assert - expect(handleCaptchaRequiredSpy).toHaveBeenCalled(); - expect(result).toBeUndefined(); - }); - it("calls onSuccessfulLogin when defined", async () => { // Arrange component.onSuccessfulLogin = jest.fn().mockResolvedValue(undefined); @@ -405,12 +370,10 @@ describe("TwoFactorComponent", () => { describe("submit", () => { const token = "testToken"; const remember = false; - const captchaToken = "testCaptchaToken"; beforeEach(() => { component.token = token; component.remember = remember; - component.captchaToken = captchaToken; }); describe("Trusted Device Encryption scenarios", () => { From 84d817e9363253e890906a2edd7df0030288023f Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 3 Dec 2024 15:15:29 -0500 Subject: [PATCH 007/113] PM-8113 - TwoFactorAuthComp HTML - remove captcha --- .../components/two-factor-auth/two-factor-auth.component.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html index 8462a18ac2e..3698febe551 100644 --- a/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html +++ b/libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html @@ -32,9 +32,6 @@

{{ "noTwoStepProviders" | i18n }}

{{ "noTwoStepProviders2" | i18n }}

-
- -
- + #duoComponent + /> + + {{ "rememberMe" | i18n }} + + + +

{{ "noTwoStepProviders" | i18n }}

+

{{ "noTwoStepProviders2" | i18n }}

+
+ +
+ + - - {{ "cancel" | i18n }} - -
- - + + {{ "cancel" | i18n }} + +
+ + + diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 3d4be1bea68..0383ee71a27 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -36,6 +36,7 @@ import { SyncService } from "@bitwarden/common/platform/sync"; import { AsyncActionsModule, ButtonModule, + CheckboxModule, DialogService, FormFieldModule, ToastService, @@ -67,6 +68,7 @@ import { FormFieldModule, AsyncActionsModule, RouterLink, + CheckboxModule, ButtonModule, TwoFactorOptionsComponent, // used as dialog TwoFactorAuthAuthenticatorComponent, @@ -78,6 +80,8 @@ import { providers: [I18nPipe], }) export class TwoFactorAuthComponent implements OnInit, OnDestroy { + loading = true; + token = ""; remember = false; orgSsoIdentifier: string = null; @@ -155,6 +159,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { await this.twoFactorAuthComponentService.extendPopupWidthIfRequired?.( this.selectedProviderType, ); + + this.loading = false; } private async processWebAuthnResponseIfExists() { From 2c620ea44669c4bc0c9574d37206063979f3a6e2 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 7 Jan 2025 13:22:04 -0500 Subject: [PATCH 063/113] PM-8113 - TwoFactorAuthDuoComponent - update takeUntilDestroyed to pass in destroy context as you can't use takeUntilDestroyed in ngOnInit without it. --- .../two-factor-auth-duo/two-factor-auth-duo.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index 5638cd5c404..089d073ff0e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; -import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core"; +import { Component, DestroyRef, EventEmitter, Input, OnInit, Output } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; @@ -53,12 +53,13 @@ export class TwoFactorAuthDuoComponent implements OnInit { protected platformUtilsService: PlatformUtilsService, protected toastService: ToastService, private twoFactorAuthDuoComponentService: TwoFactorAuthDuoComponentService, + private destroyRef: DestroyRef, ) {} async ngOnInit(): Promise { this.twoFactorAuthDuoComponentService .listenForDuo2faResult$() - .pipe(takeUntilDestroyed()) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((duo2faResult: Duo2faResult) => { this.token.emit(duo2faResult.token); }); From d5391baacaf5b79d9c1c0c5ac40a35c9cc0273e5 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 15:32:54 -0500 Subject: [PATCH 064/113] PM-8113 - TwoFactorAuthWebAuthnComponent - remove no longer necessary webauthn new tab check as webauthn seems to work without it --- .../child-components/two-factor-auth-webauthn.component.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts index ba3b645c68d..9a2d50b936e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts @@ -13,7 +13,6 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; -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 { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; @@ -62,11 +61,6 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { private toastService: ToastService, ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - - if (this.platformUtilsService.getClientType() == ClientType.Browser) { - // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe - this.webAuthnNewTab = true; - } } async ngOnInit(): Promise { From ee8592c1c953f60b0498d2ca8ea58765beca9a3c Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 16:10:14 -0500 Subject: [PATCH 065/113] PM-8113 - TwoFactorAuthWebAuthnComp - refactor names and add todo --- .../two-factor-auth-webauthn.component.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts index 9a2d50b936e..b28b88d8259 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts @@ -49,7 +49,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { webAuthnReady = false; webAuthnNewTab = false; webAuthnSupported = false; - webAuthn: WebAuthnIFrame = null; + webAuthnIframe: WebAuthnIFrame = null; constructor( protected i18nService: I18nService, @@ -61,6 +61,8 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { private toastService: ToastService, ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); + + // TODO: test webauthNewTab removal on other browsers } async ngOnInit(): Promise { @@ -68,12 +70,10 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { this.token.emit(this.route.snapshot.paramMap.get("webAuthnResponse")); } - this.cleanupWebAuthn(); - if (this.win != null && this.webAuthnSupported) { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); - this.webAuthn = new WebAuthnIFrame( + this.webAuthnIframe = new WebAuthnIFrame( this.win, webVaultUrl, this.webAuthnNewTab, @@ -105,7 +105,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.cleanupWebAuthn(); + this.cleanupWebAuthnIframe(); } async authWebAuthn() { @@ -113,17 +113,17 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { TwoFactorProviderType.WebAuthn, ); - if (!this.webAuthnSupported || this.webAuthn == null) { + if (!this.webAuthnSupported || this.webAuthnIframe == null) { return; } - this.webAuthn.init(providerData); + this.webAuthnIframe.init(providerData); } - private cleanupWebAuthn() { - if (this.webAuthn != null) { - this.webAuthn.stop(); - this.webAuthn.cleanup(); + private cleanupWebAuthnIframe() { + if (this.webAuthnIframe != null) { + this.webAuthnIframe.stop(); + this.webAuthnIframe.cleanup(); } } } From 206b23f56d4d56493167697ab67c676729ee9e6d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 19:39:46 -0500 Subject: [PATCH 066/113] PM-8113 - (1) Move WebAuthn 2FA comp to own folder (2) build out client service for new tab logic --- ...-factor-auth-webauthn-component.service.ts | 24 +++++++++++++++++++ .../two-factor-auth/child-components/index.ts | 1 + ...-factor-auth-webauthn-component.service.ts | 12 ++++++++++ .../two-factor-auth-webauthn/index.ts | 2 ++ ...-factor-auth-webauthn-component.service.ts | 9 +++++++ .../two-factor-auth-webauthn.component.html | 0 .../two-factor-auth-webauthn.component.ts | 2 +- .../two-factor-auth.component.ts | 2 +- 8 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts create mode 100644 libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts create mode 100644 libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts create mode 100644 libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts rename libs/auth/src/angular/two-factor-auth/child-components/{ => two-factor-auth-webauthn}/two-factor-auth-webauthn.component.html (100%) rename libs/auth/src/angular/two-factor-auth/child-components/{ => two-factor-auth-webauthn}/two-factor-auth-webauthn.component.ts (97%) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts new file mode 100644 index 00000000000..8b88af67dd7 --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts @@ -0,0 +1,24 @@ +import { + DefaultTwoFactorAuthWebAuthnComponentService, + TwoFactorAuthWebAuthnComponentService, +} from "@bitwarden/auth/angular"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +export class ExtensionTwoFactorAuthWebAuthnComponentService + extends DefaultTwoFactorAuthWebAuthnComponentService + implements TwoFactorAuthWebAuthnComponentService +{ + constructor(private platformUtilsService: PlatformUtilsService) { + super(); + } + + async shouldOpenWebAuthnInNewTab(): Promise { + const isChrome = this.platformUtilsService.isChrome(); + if (isChrome) { + // Chrome now supports WebAuthn in the iframe in the extension now. + return false; + } + + return true; + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/index.ts index de8bfa59589..429da3f14b3 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/index.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/index.ts @@ -1,2 +1,3 @@ export * from "./two-factor-auth-email"; export * from "./two-factor-auth-duo"; +export * from "./two-factor-auth-webauthn"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts new file mode 100644 index 00000000000..6d5ee2ae65f --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts @@ -0,0 +1,12 @@ +import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; + +export class DefaultTwoFactorAuthWebAuthnComponentService + implements TwoFactorAuthWebAuthnComponentService +{ + /** + * Default implementation is to not open in a new tab. + */ + async shouldOpenWebAuthnInNewTab(): Promise { + return false; + } +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts new file mode 100644 index 00000000000..154566280c7 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/index.ts @@ -0,0 +1,2 @@ +export * from "./two-factor-auth-webauthn-component.service"; +export * from "./default-two-factor-auth-webauthn-component.service"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts new file mode 100644 index 00000000000..3d856db7b26 --- /dev/null +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts @@ -0,0 +1,9 @@ +/** + * A service that manages all cross client functionality for the WebAuthn 2FA component. + */ +export abstract class TwoFactorAuthWebAuthnComponentService { + /** + * Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab. + */ + abstract shouldOpenWebAuthnInNewTab(): Promise; +} diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html similarity index 100% rename from libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.html rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.html diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts similarity index 97% rename from libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts rename to libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index b28b88d8259..c9440f72aa1 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -62,7 +62,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - // TODO: test webauthNewTab removal on other browsers + // TODO: test webauthNewTab removal on other browsers (works on chrome, but not firefox or) } async ngOnInit(): Promise { diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 0383ee71a27..728119f443b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -45,7 +45,7 @@ import { import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component"; import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component"; import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component"; -import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn.component"; +import { TwoFactorAuthWebAuthnComponent } from "./child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component"; import { TwoFactorAuthYubikeyComponent } from "./child-components/two-factor-auth-yubikey.component"; import { LegacyKeyMigrationAction, From b3befbdb727157f87d2076a3312b41a1b2cb0094 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 19:44:20 -0500 Subject: [PATCH 067/113] PM-8113 - Register TwoFactorAuthWebAuthnComponentServices --- apps/browser/src/popup/services/services.module.ts | 7 +++++++ libs/angular/src/services/jslib-services.module.ts | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/apps/browser/src/popup/services/services.module.ts b/apps/browser/src/popup/services/services.module.ts index 229d55a0ea4..165e64d87c0 100644 --- a/apps/browser/src/popup/services/services.module.ts +++ b/apps/browser/src/popup/services/services.module.ts @@ -30,6 +30,7 @@ import { TwoFactorAuthComponentService, TwoFactorAuthEmailComponentService, TwoFactorAuthDuoComponentService, + TwoFactorAuthWebAuthnComponentService, } from "@bitwarden/auth/angular"; import { LockService, LoginEmailService, PinServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -129,6 +130,7 @@ import { ExtensionLoginDecryptionOptionsService } from "../../auth/popup/login-d import { ExtensionTwoFactorAuthComponentService } from "../../auth/services/extension-two-factor-auth-component.service"; import { ExtensionTwoFactorAuthDuoComponentService } from "../../auth/services/extension-two-factor-auth-duo-component.service"; import { ExtensionTwoFactorAuthEmailComponentService } from "../../auth/services/extension-two-factor-auth-email-component.service"; +import { ExtensionTwoFactorAuthWebAuthnComponentService } from "../../auth/services/extension-two-factor-auth-webauthn-component.service"; import { AutofillService as AutofillServiceAbstraction } from "../../autofill/services/abstractions/autofill.service"; import AutofillService from "../../autofill/services/autofill.service"; import { InlineMenuFieldQualificationService } from "../../autofill/services/inline-menu-field-qualification.service"; @@ -542,6 +544,11 @@ const safeProviders: SafeProvider[] = [ useClass: ExtensionTwoFactorAuthEmailComponentService, deps: [DialogService, WINDOW], }), + safeProvider({ + provide: TwoFactorAuthWebAuthnComponentService, + useClass: ExtensionTwoFactorAuthWebAuthnComponentService, + deps: [PlatformUtilsService], + }), safeProvider({ provide: TwoFactorAuthDuoComponentService, useClass: ExtensionTwoFactorAuthDuoComponentService, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 02971d605aa..94a862967c2 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -24,6 +24,8 @@ import { DefaultTwoFactorAuthComponentService, DefaultTwoFactorAuthEmailComponentService, TwoFactorAuthEmailComponentService, + DefaultTwoFactorAuthWebAuthnComponentService, + TwoFactorAuthWebAuthnComponentService, } from "@bitwarden/auth/angular"; import { AuthRequestServiceAbstraction, @@ -1374,6 +1376,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultTwoFactorAuthComponentService, deps: [], }), + safeProvider({ + provide: TwoFactorAuthWebAuthnComponentService, + useClass: DefaultTwoFactorAuthWebAuthnComponentService, + deps: [], + }), safeProvider({ provide: TwoFactorAuthEmailComponentService, useClass: DefaultTwoFactorAuthEmailComponentService, From 78a09b1b9af465264c4ca4045f1022a45a4cb022 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 19:55:22 -0500 Subject: [PATCH 068/113] PM-8113 - Tweak TwoFactorAuthWebAuthnComponentService and add to TwoFactorAuthWebAuthnComponent --- .../extension-two-factor-auth-webauthn-component.service.ts | 2 +- .../default-two-factor-auth-webauthn-component.service.ts | 2 +- .../two-factor-auth-webauthn-component.service.ts | 2 +- .../two-factor-auth-webauthn.component.ts | 6 ++++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts index 8b88af67dd7..14a949fb086 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts @@ -12,7 +12,7 @@ export class ExtensionTwoFactorAuthWebAuthnComponentService super(); } - async shouldOpenWebAuthnInNewTab(): Promise { + shouldOpenWebAuthnInNewTab(): boolean { const isChrome = this.platformUtilsService.isChrome(); if (isChrome) { // Chrome now supports WebAuthn in the iframe in the extension now. diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts index 6d5ee2ae65f..3d3578c656e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/default-two-factor-auth-webauthn-component.service.ts @@ -6,7 +6,7 @@ export class DefaultTwoFactorAuthWebAuthnComponentService /** * Default implementation is to not open in a new tab. */ - async shouldOpenWebAuthnInNewTab(): Promise { + shouldOpenWebAuthnInNewTab(): boolean { return false; } } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts index 3d856db7b26..e448d97cc33 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts @@ -5,5 +5,5 @@ export abstract class TwoFactorAuthWebAuthnComponentService { /** * Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab. */ - abstract shouldOpenWebAuthnInNewTab(): Promise; + abstract shouldOpenWebAuthnInNewTab(): boolean; } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index c9440f72aa1..d634cc0b3d3 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -25,6 +25,8 @@ import { ToastService, } from "@bitwarden/components"; +import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; + @Component({ standalone: true, selector: "app-two-factor-auth-webauthn", @@ -59,10 +61,10 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { protected twoFactorService: TwoFactorService, protected route: ActivatedRoute, private toastService: ToastService, + private twoFactorAuthWebAuthnComponentService: TwoFactorAuthWebAuthnComponentService, ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - - // TODO: test webauthNewTab removal on other browsers (works on chrome, but not firefox or) + this.webAuthnNewTab = this.twoFactorAuthWebAuthnComponentService.shouldOpenWebAuthnInNewTab(); } async ngOnInit(): Promise { From 7c53135060214d89fdc27321ee844273df6ac184 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 20:16:21 -0500 Subject: [PATCH 069/113] PM-8113 - WebTwoFactorAuthDuoComponentService - fix type issue --- .../web-two-factor-auth-duo-component.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts index a99305627b2..ac8eccb5198 100644 --- a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts +++ b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts @@ -9,7 +9,7 @@ export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComp constructor(private platformUtilsService: PlatformUtilsService) { const duoResultChannel: BroadcastChannel = new BroadcastChannel("duoResult"); - this.duo2faResult$ = fromEvent(duoResultChannel, "message").pipe( + this.duo2faResult$ = fromEvent(duoResultChannel, "message").pipe( map((msg: MessageEvent) => { return { code: msg.data.code, From 097108893c410eb3bc32c6bec14b5f3263f221d8 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 20:19:40 -0500 Subject: [PATCH 070/113] PM-8113 - ExtensionTwoFactorAuthDuoComponentService - attempt to fix type issue. --- .../extension-two-factor-auth-duo-component.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts index 93041028075..87925185874 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts @@ -22,7 +22,9 @@ export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthD ) {} listenForDuo2faResult$(): Observable { return this.browserMessagingApi.messageListener$().pipe( - filter((msg: Message) => msg.command === "duoResult"), + filter((msg): msg is Message => { + return (msg as Message).command === "duoResult"; + }), map((msg: Message) => { return { code: msg.code, From 30aabc3f69d64fc72c3962a3ef44c8eb29722a0b Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 9 Jan 2025 20:31:31 -0500 Subject: [PATCH 071/113] PM-8113 - Remove ts-strict-ignore --- .../two-factor-auth-duo/two-factor-auth-duo.component.ts | 2 -- .../two-factor-auth-email/two-factor-auth-email.component.ts | 2 -- .../two-factor-auth-webauthn.component.ts | 2 -- .../src/angular/two-factor-auth/two-factor-auth.component.ts | 3 --- 4 files changed, 9 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index 089d073ff0e..b70a7247116 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, DestroyRef, EventEmitter, Input, OnInit, Output } from "@angular/core"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index dd811d3e73f..1052cef366e 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, OnInit, Output } from "@angular/core"; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index d634cc0b3d3..6793ee5a152 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { DialogModule } from "@angular/cdk/dialog"; import { CommonModule } from "@angular/common"; import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core"; diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 728119f443b..d6dc53d28bb 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -1,6 +1,3 @@ -// TODO: ensure this file is type safe and remove ts-strict-ignore before PR -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { CommonModule } from "@angular/common"; import { Component, DestroyRef, Inject, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; From 2dc94df438afe12a53a87049ecaa3bf9c5176fe5 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 13 Jan 2025 17:32:32 -0500 Subject: [PATCH 072/113] PM-8113 - TwoFactorAuthWebAuthnComponent - satisfy strict typescript reqs. --- .../two-factor-auth-webauthn.component.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 6793ee5a152..37ddfa42372 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -13,6 +13,7 @@ import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-p import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; 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 { ButtonModule, @@ -49,7 +50,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { webAuthnReady = false; webAuthnNewTab = false; webAuthnSupported = false; - webAuthnIframe: WebAuthnIFrame = null; + webAuthnIframe: WebAuthnIFrame | undefined = undefined; constructor( protected i18nService: I18nService, @@ -60,6 +61,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { protected route: ActivatedRoute, private toastService: ToastService, private twoFactorAuthWebAuthnComponentService: TwoFactorAuthWebAuthnComponentService, + private logService: LogService, ) { this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); this.webAuthnNewTab = this.twoFactorAuthWebAuthnComponentService.shouldOpenWebAuthnInNewTab(); @@ -67,7 +69,11 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { if (this.route.snapshot.paramMap.has("webAuthnResponse")) { - this.token.emit(this.route.snapshot.paramMap.get("webAuthnResponse")); + const webAuthnResponse = this.route.snapshot.paramMap.get("webAuthnResponse"); + + if (webAuthnResponse != null) { + this.token.emit(webAuthnResponse); + } } if (this.win != null && this.webAuthnSupported) { @@ -109,9 +115,14 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { } async authWebAuthn() { - const providerData = (await this.twoFactorService.getProviders()).get( - TwoFactorProviderType.WebAuthn, - ); + const providers = await this.twoFactorService.getProviders(); + + if (providers == null) { + this.logService.error("No 2FA providers found. Unable to authenticate with WebAuthn."); + return; + } + + const providerData = providers?.get(TwoFactorProviderType.WebAuthn); if (!this.webAuthnSupported || this.webAuthnIframe == null) { return; From 22a82be4c8bf2c31621c4f814de5aab62b430589 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 13 Jan 2025 17:42:37 -0500 Subject: [PATCH 073/113] PM-8113 - TwoFactorAuthComponent - some progress on strict TS conversion --- .../two-factor-auth.component.ts | 29 ++++++++++++------- .../abstractions/login-strategy.service.ts | 2 +- .../login-strategy.service.ts | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index d6dc53d28bb..eb53cb25bf0 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -79,9 +79,9 @@ import { export class TwoFactorAuthComponent implements OnInit, OnDestroy { loading = true; - token = ""; + token: string | undefined = undefined; remember = false; - orgSsoIdentifier: string = null; + orgSsoIdentifier: string | undefined = undefined; inSsoFlow = false; providers = TwoFactorProviders; @@ -104,7 +104,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { title = ""; - formPromise: Promise; + formPromise: Promise | undefined; submitForm = async () => { await this.submit(); @@ -137,7 +137,10 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { async ngOnInit() { this.inSsoFlow = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true"; - this.orgSsoIdentifier = this.activatedRoute.snapshot.queryParamMap.get("identifier"); + + this.orgSsoIdentifier = + this.activatedRoute.snapshot.queryParamMap.get("identifier") ?? undefined; + this.listenFor2faSessionTimeout(); if (this.twoFactorAuthComponentService.shouldCheckForWebauthnResponseOnInit()) { @@ -149,8 +152,13 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { await this.setTitleByTwoFactorProviderType(); this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => { - this.token = value.token; - this.remember = value.remember; + if (value.token) { + this.token = value.token; + } + + if (value.remember) { + this.remember = value.remember; + } }); await this.twoFactorAuthComponentService.extendPopupWidthIfRequired?.( @@ -215,7 +223,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { try { this.formPromise = this.loginStrategyService.logInTwoFactor( new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember), - null, ); const authResult: AuthResult = await this.formPromise; this.logService.info("Successfully submitted two factor token"); @@ -293,7 +300,9 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { // Save off the OrgSsoIdentifier for use in the TDE flows // - TDE login decryption options component // - Browser SSO on extension open - await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgSsoIdentifier); + if (this.orgSsoIdentifier !== undefined) { + await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgSsoIdentifier); + } // note: this flow affects both TDE & standard users if (this.isForcePasswordResetRequired(authResult)) { @@ -387,7 +396,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { await this.router.navigate(["login-initiated"]); } - private async handleChangePasswordRequired(orgIdentifier: string) { + private async handleChangePasswordRequired(orgIdentifier: string | undefined) { await this.router.navigate(["set-password"], { queryParams: { identifier: orgIdentifier, @@ -414,7 +423,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { return forceResetReasons.includes(authResult.forcePasswordReset); } - private async handleForcePasswordReset(orgIdentifier: string) { + private async handleForcePasswordReset(orgIdentifier: string | undefined) { await this.router.navigate(["update-temp-password"], { queryParams: { identifier: orgIdentifier, diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index 28aa2c17410..4e0a4dffde9 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -68,7 +68,7 @@ export abstract class LoginStrategyServiceAbstraction { logInTwoFactor: ( twoFactor: TokenTwoFactorRequest, // TODO: PM-15162 - deprecate captchaResponse - captchaResponse: string, + captchaResponse?: string, ) => Promise; /** * Creates a master key from the provided master password and email. diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 57a653b205e..d3441a7f3f8 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -232,7 +232,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async logInTwoFactor( twoFactor: TokenTwoFactorRequest, - captchaResponse: string, + captchaResponse?: string, ): Promise { if (!(await this.isSessionValid())) { throw new Error(this.i18nService.t("sessionTimeout")); From 282d7e455cc09f113dd122cc789af338ad235ad0 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 16 Jan 2025 11:23:06 -0500 Subject: [PATCH 074/113] PM-8113 - TwoFactorAuthComp - fixed all strict typescript issues. --- .../two-factor-auth.component.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index eb53cb25bf0..47d94f0a149 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -87,7 +87,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { providers = TwoFactorProviders; providerType = TwoFactorProviderType; selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; - providerData: { [key: string]: string }; // TODO: build more specific type + // TODO: PM-17176 - build more specific type for 2FA metadata + providerData: { [key: string]: string } | undefined; @ViewChild("duoComponent") duoComponent!: TwoFactorAuthDuoComponent; @@ -186,7 +187,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private async set2faProviderData() { const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(this.selectedProviderType); + return providers?.get(this.selectedProviderType); }); this.providerData = providerData; } @@ -239,10 +240,17 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { async selectOtherTwofactorMethod() { const dialogRef = TwoFactorOptionsComponent.open(this.dialogService); - const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed); - if (response.result === TwoFactorOptionsDialogResult.Provider) { + const response: TwoFactorOptionsDialogResultType | undefined = await lastValueFrom( + dialogRef.closed, + ); + + if ( + response !== undefined && + response !== null && + response.result === TwoFactorOptionsDialogResult.Provider + ) { const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(response.type); + return providers?.get(response.type); }); this.providerData = providerData; this.selectedProviderType = response.type; @@ -362,7 +370,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } private async isTrustedDeviceEncEnabled( - trustedDeviceOption: TrustedDeviceUserDecryptionOption, + trustedDeviceOption: TrustedDeviceUserDecryptionOption | undefined, ): Promise { const ssoTo2faFlowActive = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true"; @@ -375,12 +383,18 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { // If user doesn't have a MP, but has reset password permission, they must set a MP if ( !userDecryptionOpts.hasMasterPassword && - userDecryptionOpts.trustedDeviceOption.hasManageResetPasswordPermission + userDecryptionOpts.trustedDeviceOption?.hasManageResetPasswordPermission ) { // Set flag so that auth guard can redirect to set password screen after decryption (trusted or untrusted device) // Note: we cannot directly navigate to the set password screen in this scenario as we are in a pre-decryption state, and // if you try to set a new MP before decrypting, you will invalidate the user's data by making a new user key. const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + + if (!userId) { + this.logService.error("User ID not found when setting TDE force set password reason"); + return; + } + await this.masterPasswordService.setForceSetPasswordReason( ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, userId, From 6f3ab5c87021f23286cff6945dd9f4c802b48378 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 16 Jan 2025 11:26:25 -0500 Subject: [PATCH 075/113] PM-8113 - TwoFactorAuthComp - remove no longer necessary webauthn code --- .../angular/two-factor-auth/two-factor-auth.component.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 47d94f0a149..93c9cb4adaf 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -345,12 +345,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { return; } - // TODO: test extension + webauthn 2fa to ensure these calls are no longer necessary: - // this.onSuccessfulLogin = async () => { - // this.messagingService.send("reloadPopup"); // this is just a navigate to "/" - // window.close(); - // }; - const defaultSuccessRoute = await this.determineDefaultSuccessRoute(); await this.router.navigate([defaultSuccessRoute], { From 36bc5f3617b9bfa9b864bc3e09847068b2af3927 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 16 Jan 2025 17:22:57 -0500 Subject: [PATCH 076/113] PM-8113 - ExtensionTwoFactorAuthComponentService - handleSso2faFlowSuccess - add more context --- .../services/extension-two-factor-auth-component.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index 8def30023e9..9faa845ad42 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -43,6 +43,9 @@ export class ExtensionTwoFactorAuthComponentService // We don't need this window anymore because the intent is for the user to be left // on the web vault screen which tells them to continue in the browser extension (sidebar or popup) + // We don't want the user to be left with a floating, popped out extension which could be lost behind + // another window or minimized. Currently, the popped out window thinks it is active and wouldn't time out + // which leads to the security concern. So, we close the popped out extension to avoid this. await closeTwoFactorAuthPopout(); } From 97050d2d151272d3ea13ad6932e85dbe11735027 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 16 Jan 2025 17:24:50 -0500 Subject: [PATCH 077/113] PM-8113 - TwoFactorAuthComp - TDE should use same success handler method --- .../src/angular/two-factor-auth/two-factor-auth.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 93c9cb4adaf..cf77c70ecee 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -395,9 +395,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { ); } - // TODO: determine why this is necessary? - if (this.twoFactorAuthComponentService.closeWindow !== undefined) { - this.twoFactorAuthComponentService.closeWindow(); + if (this.twoFactorAuthComponentService.handleSso2faFlowSuccess !== undefined) { + await this.twoFactorAuthComponentService.handleSso2faFlowSuccess(); return; } From b6c2abf86a714a71fbbfda63ff322b85e45472c0 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 17 Jan 2025 15:18:32 -0500 Subject: [PATCH 078/113] PM-8113 - Fix SSO + 2FA result handling by closing proper popout window --- .../src/auth/popup/utils/auth-popout-window.spec.ts | 9 +++++++++ apps/browser/src/auth/popup/utils/auth-popout-window.ts | 8 ++++++++ .../extension-two-factor-auth-component.service.ts | 4 ++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts index 9e7d69fad97..4e58b832141 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts @@ -9,6 +9,7 @@ import { openSsoAuthResultPopout, openTwoFactorAuthPopout, closeTwoFactorAuthPopout, + closeSsoAuthResultPopout, } from "./auth-popout-window"; describe("AuthPopoutWindow", () => { @@ -97,6 +98,14 @@ describe("AuthPopoutWindow", () => { }); }); + describe("closeSsoAuthResultPopout", () => { + it("closes the SSO authentication result popout window", async () => { + await closeSsoAuthResultPopout(); + + expect(closeSingleActionPopoutSpy).toHaveBeenCalledWith(AuthPopoutType.ssoAuthResult); + }); + }); + describe("openTwoFactorAuthPopout", () => { it("opens a window that facilitates two factor authentication", async () => { await openTwoFactorAuthPopout({ data: "data", remember: "remember" }); diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.ts index 5a0e577807f..9556a87806a 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.ts @@ -82,11 +82,19 @@ async function closeTwoFactorAuthPopout() { await BrowserPopupUtils.closeSingleActionPopout(AuthPopoutType.twoFactorAuth); } +/** + * Closes the two-factor authentication popout window. + */ +async function closeSsoAuthResultPopout() { + await BrowserPopupUtils.closeSingleActionPopout(AuthPopoutType.ssoAuthResult); +} + export { AuthPopoutType, openUnlockPopout, closeUnlockPopout, openSsoAuthResultPopout, + closeSsoAuthResultPopout, openTwoFactorAuthPopout, closeTwoFactorAuthPopout, }; diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index 9faa845ad42..d3fc6143ea8 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -5,7 +5,7 @@ import { import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { closeTwoFactorAuthPopout } from "../popup/utils/auth-popout-window"; +import { closeSsoAuthResultPopout } from "../popup/utils/auth-popout-window"; export class ExtensionTwoFactorAuthComponentService extends DefaultTwoFactorAuthComponentService @@ -46,7 +46,7 @@ export class ExtensionTwoFactorAuthComponentService // We don't want the user to be left with a floating, popped out extension which could be lost behind // another window or minimized. Currently, the popped out window thinks it is active and wouldn't time out // which leads to the security concern. So, we close the popped out extension to avoid this. - await closeTwoFactorAuthPopout(); + await closeSsoAuthResultPopout(); } private async isLinux(): Promise { From 2b9f97a7b60bb0f83a3116ca00daa578984ed29e Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 17 Jan 2025 15:19:16 -0500 Subject: [PATCH 079/113] PM-8113 - Add todo --- apps/browser/src/background/runtime.background.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 38bb2ec50c9..bf69e13d723 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -333,6 +333,8 @@ export default class RuntimeBackground { return; } + // TODO: investigate when this is triggered and consider a rename to make it specific + // to webauthn 2fa await openTwoFactorAuthPopout(msg); break; } From 5b6f5520d116a1e795104d20796439fb93c13114 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 20 Jan 2025 12:32:13 -0500 Subject: [PATCH 080/113] PM-8113 - Webauthn 2FA - As webauthn popout doesn't persist SSO state, have to genercize success logic (which should be a good thing but requires confirmation testing). --- ...nsion-two-factor-auth-component.service.ts | 46 +++++++++++++++---- .../two-factor-auth-webauthn.component.ts | 3 ++ .../two-factor-auth-component.service.ts | 4 +- .../two-factor-auth.component.ts | 15 +++--- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index d3fc6143ea8..67492e97e32 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -5,7 +5,12 @@ import { import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { BrowserApi } from "../../platform/browser/browser-api"; -import { closeSsoAuthResultPopout } from "../popup/utils/auth-popout-window"; +import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; +import { + AuthPopoutType, + closeSsoAuthResultPopout, + closeTwoFactorAuthPopout, +} from "../popup/utils/auth-popout-window"; export class ExtensionTwoFactorAuthComponentService extends DefaultTwoFactorAuthComponentService @@ -36,17 +41,40 @@ export class ExtensionTwoFactorAuthComponentService this.window.close(); } - async handleSso2faFlowSuccess(): Promise { + async handle2faSuccess(): Promise { + // TODO: confirm that moving this from SSO flow only to general flow doesn't introduce any issues // Force sidebars (FF && Opera) to reload while exempting current window - // because we are just going to close the current window. + // because we are just going to close the current window if it is in a popout + // or navigate forward if it is in the popup BrowserApi.reloadOpenWindows(true); - // We don't need this window anymore because the intent is for the user to be left - // on the web vault screen which tells them to continue in the browser extension (sidebar or popup) - // We don't want the user to be left with a floating, popped out extension which could be lost behind - // another window or minimized. Currently, the popped out window thinks it is active and wouldn't time out - // which leads to the security concern. So, we close the popped out extension to avoid this. - await closeSsoAuthResultPopout(); + await this.closeSingleActionPopouts(); + } + + private async closeSingleActionPopouts(): Promise { + // If we are in a single action popout, we don't need the popout anymore because the intent + // is for the user to be left on the web vault screen which tells them to continue in + // the browser extension (sidebar or popup). We don't want the user to be left with a + // floating, popped out extension which could be lost behind another window or minimized. + // Currently, the popped out window thinks it is active and wouldn't time out which + // leads to the security concern. So, we close the popped out extension to avoid this. + const inSsoAuthResultPopout = BrowserPopupUtils.inSingleActionPopout( + this.window, + AuthPopoutType.ssoAuthResult, + ); + if (inSsoAuthResultPopout) { + await closeSsoAuthResultPopout(); + return; + } + + const inTwoFactorAuthPopout = BrowserPopupUtils.inSingleActionPopout( + this.window, + AuthPopoutType.twoFactorAuth, + ); + + if (inTwoFactorAuthPopout) { + await closeTwoFactorAuthPopout(); + } } private async isLinux(): Promise { diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 37ddfa42372..50554931447 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -72,7 +72,10 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { const webAuthnResponse = this.route.snapshot.paramMap.get("webAuthnResponse"); if (webAuthnResponse != null) { + // TODO: determine if we even need this with the top level processing of the webauthn response. this.token.emit(webAuthnResponse); + // TODO: should we early return? + // return; } } diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index 4b398b5a268..487f9be9ac4 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -47,8 +47,8 @@ export abstract class TwoFactorAuthComponentService { abstract determineLegacyKeyMigrationAction(): LegacyKeyMigrationAction; /** - * Optionally handles the success flow for the SSO + 2FA required flow. + * Optionally executes 2FA success logic. * Only defined on clients that require custom success handling. */ - abstract handleSso2faFlowSuccess?(): Promise; + abstract handle2faSuccess?(): Promise; } diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index cf77c70ecee..198335fd890 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -146,6 +146,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { if (this.twoFactorAuthComponentService.shouldCheckForWebauthnResponseOnInit()) { await this.processWebAuthnResponseIfExists(); + // TODO: should we return here? + // return; } await this.setSelected2faProviderType(); @@ -336,12 +338,9 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { return await this.handleChangePasswordRequired(this.orgSsoIdentifier); } - // if we are in the SSO flow and we have a custom success handler, call it - if ( - this.inSsoFlow && - this.twoFactorAuthComponentService.handleSso2faFlowSuccess !== undefined - ) { - await this.twoFactorAuthComponentService.handleSso2faFlowSuccess(); + // if we have a custom success handler, call it + if (this.twoFactorAuthComponentService.handle2faSuccess !== undefined) { + await this.twoFactorAuthComponentService.handle2faSuccess(); return; } @@ -395,8 +394,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { ); } - if (this.twoFactorAuthComponentService.handleSso2faFlowSuccess !== undefined) { - await this.twoFactorAuthComponentService.handleSso2faFlowSuccess(); + if (this.twoFactorAuthComponentService.handle2faSuccess !== undefined) { + await this.twoFactorAuthComponentService.handle2faSuccess(); return; } From 66ac280ba83f938b979fda0d59e0fac496829bfb Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 22 Jan 2025 16:49:16 -0500 Subject: [PATCH 081/113] PM-8113 - Per main changes, remove deprecated I18nPipe from 2fa comps that use it. --- .../two-factor-auth-duo/two-factor-auth-duo.component.ts | 3 +-- .../two-factor-auth-email/two-factor-auth-email.component.ts | 3 +-- .../src/angular/two-factor-auth/two-factor-auth.component.ts | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index b70a7247116..e670ff9fec9 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -5,7 +5,6 @@ import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { @@ -38,7 +37,7 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthDuoComponent implements OnInit { @Output() token = new EventEmitter(); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index 1052cef366e..ac536298ab2 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -4,7 +4,6 @@ import { Component, EventEmitter, OnInit, Output } from "@angular/core"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; @@ -41,7 +40,7 @@ import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-comp AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthEmailComponent implements OnInit { @Output() token = new EventEmitter(); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 198335fd890..50b64828ba6 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -6,7 +6,6 @@ import { ActivatedRoute, Router, RouterLink } from "@angular/router"; import { lastValueFrom, firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { LoginStrategyServiceAbstraction, @@ -74,7 +73,7 @@ import { TwoFactorAuthYubikeyComponent, TwoFactorAuthWebAuthnComponent, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthComponent implements OnInit, OnDestroy { loading = true; From a35ab8f2d2fc125cf272056e8edbdc597fe922b7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Wed, 22 Jan 2025 17:18:13 -0500 Subject: [PATCH 082/113] PM-8113 - Remove more incorrect i18nPipes --- .../two-factor-auth-webauthn.component.ts | 3 +-- .../child-components/two-factor-auth-yubikey.component.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index 50554931447..aefdc10df22 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -6,7 +6,6 @@ import { ActivatedRoute } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; @@ -42,7 +41,7 @@ import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauth AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { @Output() token = new EventEmitter(); diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts index 71e8508f8ce..eeb0b34cd12 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-yubikey.component.ts @@ -4,7 +4,6 @@ import { Component, EventEmitter, Output } from "@angular/core"; import { ReactiveFormsModule, FormsModule } from "@angular/forms"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { ButtonModule, LinkModule, @@ -29,7 +28,7 @@ import { AsyncActionsModule, FormsModule, ], - providers: [I18nPipe], + providers: [], }) export class TwoFactorAuthYubikeyComponent { tokenValue: string = ""; From 09f4a468c93fcb0ef2457d0825ca1b45b65c7929 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 17:23:52 -0500 Subject: [PATCH 083/113] PM-8113 - TwoFactorAuth + Webauthn - Refactor logic --- ...nsion-two-factor-auth-component.service.ts | 2 +- ...-factor-auth-webauthn-component.service.ts | 6 +++ ...-factor-auth-webauthn-component.service.ts | 1 + .../two-factor-auth-webauthn.component.ts | 36 +++++++++++----- ...fault-two-factor-auth-component.service.ts | 2 +- .../two-factor-auth-component.service.ts | 4 +- .../two-factor-auth.component.html | 2 +- .../two-factor-auth.component.ts | 43 +++++++++++-------- 8 files changed, 62 insertions(+), 34 deletions(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index 67492e97e32..03d76dd47fa 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -20,7 +20,7 @@ export class ExtensionTwoFactorAuthComponentService super(); } - shouldCheckForWebauthnResponseOnInit(): boolean { + shouldCheckForWebAuthnQueryParamResponse(): boolean { return true; } diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts index 14a949fb086..84a54cbc12e 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts @@ -12,6 +12,12 @@ export class ExtensionTwoFactorAuthWebAuthnComponentService super(); } + /** + * In the browser extension, we open webAuthn in a new web client tab sometimes due to inline + * WebAuthn Iframe's not working in some browsers. We open a 2FA popout upon successful + * completion of WebAuthn submission with query parameters to finish the 2FA process. + * @returns boolean + */ shouldOpenWebAuthnInNewTab(): boolean { const isChrome = this.platformUtilsService.isChrome(); if (isChrome) { diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts index e448d97cc33..49842c53796 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn-component.service.ts @@ -4,6 +4,7 @@ export abstract class TwoFactorAuthWebAuthnComponentService { /** * Determines if the WebAuthn 2FA should be opened in a new tab or can be completed in the current tab. + * In a browser extension context, we open WebAuthn in a new web client tab. */ abstract shouldOpenWebAuthnInNewTab(): boolean; } diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts index aefdc10df22..61ce984a019 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-webauthn/two-factor-auth-webauthn.component.ts @@ -25,6 +25,11 @@ import { import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauthn-component.service"; +export interface WebAuthnResult { + token: string; + remember?: boolean; +} + @Component({ standalone: true, selector: "app-two-factor-auth-webauthn", @@ -44,7 +49,7 @@ import { TwoFactorAuthWebAuthnComponentService } from "./two-factor-auth-webauth providers: [], }) export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { - @Output() token = new EventEmitter(); + @Output() webAuthnResultEmitter = new EventEmitter(); webAuthnReady = false; webAuthnNewTab = false; @@ -67,17 +72,26 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { } async ngOnInit(): Promise { - if (this.route.snapshot.paramMap.has("webAuthnResponse")) { - const webAuthnResponse = this.route.snapshot.paramMap.get("webAuthnResponse"); - - if (webAuthnResponse != null) { - // TODO: determine if we even need this with the top level processing of the webauthn response. - this.token.emit(webAuthnResponse); - // TODO: should we early return? - // return; - } + if (this.webAuthnNewTab && this.route.snapshot.paramMap.has("webAuthnResponse")) { + this.submitWebAuthnNewTabResponse(); + } else { + await this.buildWebAuthnIFrame(); + } + } + + private submitWebAuthnNewTabResponse() { + const webAuthnNewTabResponse = this.route.snapshot.paramMap.get("webAuthnResponse"); + const remember = this.route.snapshot.queryParamMap.get("remember") === "true"; + + if (webAuthnNewTabResponse != null) { + this.webAuthnResultEmitter.emit({ + token: webAuthnNewTabResponse, + remember, + }); } + } + private async buildWebAuthnIFrame() { if (this.win != null && this.webAuthnSupported) { const env = await firstValueFrom(this.environmentService.environment$); const webVaultUrl = env.getWebVaultUrl(); @@ -88,7 +102,7 @@ export class TwoFactorAuthWebAuthnComponent implements OnInit, OnDestroy { this.platformUtilsService, this.i18nService, (token: string) => { - this.token.emit(token); + this.webAuthnResultEmitter.emit({ token }); }, (error: string) => { this.toastService.showToast({ diff --git a/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts index 579a71aa4b5..67a74158be2 100644 --- a/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/default-two-factor-auth-component.service.ts @@ -4,7 +4,7 @@ import { } from "./two-factor-auth-component.service"; export class DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService { - shouldCheckForWebauthnResponseOnInit() { + shouldCheckForWebAuthnQueryParamResponse() { return false; } diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index 487f9be9ac4..2e71f637ea3 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -12,9 +12,9 @@ export enum LegacyKeyMigrationAction { export abstract class TwoFactorAuthComponentService { /** * Determines if the client should check for a webauthn response on init. - * Currently, only the extension should check on init. + * Currently, only the extension should check during component initialization. */ - abstract shouldCheckForWebauthnResponseOnInit(): boolean; + abstract shouldCheckForWebAuthnQueryParamResponse(): boolean; /** * Extends the popup width if required. diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html index b1f041e795e..e9717f7a0ea 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -19,7 +19,7 @@ *ngIf="selectedProviderType === providerType.Yubikey" /> Date: Thu, 23 Jan 2025 17:49:23 -0500 Subject: [PATCH 084/113] PM-8113 - TwoFactorAuth - build submitting loading logic --- .../two-factor-auth.component.html | 81 +++++++++---------- .../two-factor-auth.component.ts | 4 + 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html index e9717f7a0ea..7bae5cc159c 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -1,11 +1,11 @@ - -
- -
-
+
+ +
+ +
+
- - + + {{ "rememberMe" | i18n }} - +

{{ "noTwoStepProviders" | i18n }}

{{ "noTwoStepProviders2" | i18n }}

- -
- - + - - {{ "cancel" | i18n }} - -
- - -
+ +
+ + + + + {{ "cancel" | i18n }} + +
+ + diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 6dfcde224c9..47fbe77f337 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -220,12 +220,14 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async submit() { + this.loading = true; if (this.token == null || this.token === "") { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("verificationCodeRequired"), }); + this.loading = false; return; } @@ -243,6 +245,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("invalidVerificationCode"), }); + } finally { + this.loading = false; } } From 2091245471e3d1cbc153536ed2cebe13279da91d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 17:53:25 -0500 Subject: [PATCH 085/113] PM-8113 - TwoFactorAuth - remove loading as submitting. --- .../two-factor-auth.component.html | 81 ++++++++++--------- .../two-factor-auth.component.ts | 4 - 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html index 7bae5cc159c..93c1b8a6f20 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -1,11 +1,11 @@ -
- -
- -
-
+ +
+ +
+
- + + - {{ "rememberMe" | i18n }} - +

{{ "noTwoStepProviders" | i18n }}

{{ "noTwoStepProviders2" | i18n }}

-
- - -
- - + +
+ + - - {{ "cancel" | i18n }} - -
- - + + {{ "cancel" | i18n }} + +
+ + +
diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 47fbe77f337..6dfcde224c9 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -220,14 +220,12 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async submit() { - this.loading = true; if (this.token == null || this.token === "") { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("verificationCodeRequired"), }); - this.loading = false; return; } @@ -245,8 +243,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { title: this.i18nService.t("errorOccurred"), message: this.i18nService.t("invalidVerificationCode"), }); - } finally { - this.loading = false; } } From 1b72325aa666d54fcdccbadd4c7eca364cfe64aa Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 18:19:43 -0500 Subject: [PATCH 086/113] PM-8113 - TwoFactorAuth - update to latest authN session timeout logic --- .../two-factor-auth/two-factor-auth.component.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 6dfcde224c9..8bcb405368e 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -113,7 +113,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { await this.submit(); }; - private twoFactorSessionTimeoutRoute = "2fa-timeout"; + private authenticationSessionTimeoutRoute = "authentication-timeout"; constructor( private loginStrategyService: LoginStrategyServiceAbstraction, @@ -144,7 +144,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { this.orgSsoIdentifier = this.activatedRoute.snapshot.queryParamMap.get("identifier") ?? undefined; - this.listenFor2faSessionTimeout(); + this.listenForAuthnSessionTimeout(); await this.setSelected2faProviderType(); await this.set2faProviderData(); @@ -192,8 +192,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { this.providerData = providerData; } - private listenFor2faSessionTimeout() { - this.loginStrategyService.twoFactorTimeout$ + private listenForAuthnSessionTimeout() { + this.loginStrategyService.authenticationSessionTimeout$ .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(async (expired) => { if (!expired) { @@ -201,10 +201,10 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } try { - await this.router.navigate([this.twoFactorSessionTimeoutRoute]); + await this.router.navigate([this.authenticationSessionTimeoutRoute]); } catch (err) { this.logService.error( - `Failed to navigate to ${this.twoFactorSessionTimeoutRoute} route`, + `Failed to navigate to ${this.authenticationSessionTimeoutRoute} route`, err, ); } From dd4ef7b8123b62ad186a8155661962ad45a4d70a Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 18:27:59 -0500 Subject: [PATCH 087/113] PM-8113 - AuthPopoutWindow - Add new single action popout for email 2FA so we can close it programmatically --- .../popup/utils/auth-popout-window.spec.ts | 20 ++++++++++++++++++ .../auth/popup/utils/auth-popout-window.ts | 21 ++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts index deb71f73cd6..ea76e70351f 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.spec.ts @@ -10,6 +10,8 @@ import { openTwoFactorAuthWebAuthnPopout, closeTwoFactorAuthWebAuthnPopout, closeSsoAuthResultPopout, + openTwoFactorAuthEmailPopout, + closeTwoFactorAuthEmailPopout, } from "./auth-popout-window"; describe("AuthPopoutWindow", () => { @@ -124,4 +126,22 @@ describe("AuthPopoutWindow", () => { expect(closeSingleActionPopoutSpy).toHaveBeenCalledWith(AuthPopoutType.twoFactorAuthWebAuthn); }); }); + + describe("openTwoFactorAuthEmailPopout", () => { + it("opens a window that facilitates two factor authentication via email", async () => { + await openTwoFactorAuthEmailPopout(); + + expect(openPopoutSpy).toHaveBeenCalledWith("popup/index.html#/2fa", { + singleActionKey: AuthPopoutType.twoFactorAuthEmail, + }); + }); + }); + + describe("closeTwoFactorAuthEmailPopout", () => { + it("closes the two-factor authentication email window", async () => { + await closeTwoFactorAuthEmailPopout(); + + expect(closeSingleActionPopoutSpy).toHaveBeenCalledWith(AuthPopoutType.twoFactorAuthEmail); + }); + }); }); diff --git a/apps/browser/src/auth/popup/utils/auth-popout-window.ts b/apps/browser/src/auth/popup/utils/auth-popout-window.ts index 8d6e7fa92cd..48cf13ee22d 100644 --- a/apps/browser/src/auth/popup/utils/auth-popout-window.ts +++ b/apps/browser/src/auth/popup/utils/auth-popout-window.ts @@ -7,6 +7,7 @@ const AuthPopoutType = { unlockExtension: "auth_unlockExtension", ssoAuthResult: "auth_ssoAuthResult", twoFactorAuthWebAuthn: "auth_twoFactorAuthWebAuthn", + twoFactorAuthEmail: "auth_twoFactorAuthEmail", } as const; const extensionUnlockUrls = new Set([ chrome.runtime.getURL("popup/index.html#/lock"), @@ -87,12 +88,28 @@ async function openTwoFactorAuthWebAuthnPopout(twoFactorAuthWebAuthnData: { } /** - * Closes the two-factor authentication popout window. + * Closes the two-factor authentication WebAuthn popout window. */ async function closeTwoFactorAuthWebAuthnPopout() { await BrowserPopupUtils.closeSingleActionPopout(AuthPopoutType.twoFactorAuthWebAuthn); } +/** + * Opens a popout that facilitates two-factor authentication via email. + */ +async function openTwoFactorAuthEmailPopout() { + await BrowserPopupUtils.openPopout("popup/index.html#/2fa", { + singleActionKey: AuthPopoutType.twoFactorAuthEmail, + }); +} + +/** + * Closes the two-factor authentication email popout window. + */ +async function closeTwoFactorAuthEmailPopout() { + await BrowserPopupUtils.closeSingleActionPopout(AuthPopoutType.twoFactorAuthEmail); +} + export { AuthPopoutType, openUnlockPopout, @@ -101,4 +118,6 @@ export { closeSsoAuthResultPopout, openTwoFactorAuthWebAuthnPopout, closeTwoFactorAuthWebAuthnPopout, + openTwoFactorAuthEmailPopout, + closeTwoFactorAuthEmailPopout, }; From 171d9511db13582bb1732d63a6bddb496956a97e Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 18:28:27 -0500 Subject: [PATCH 088/113] PM-8113 - Update ExtensionTwoFactorAuthComponentService to close email 2FA single action popouts. --- ...nsion-two-factor-auth-component.service.ts | 22 ++++++++++++++----- ...two-factor-auth-email-component.service.ts | 3 ++- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index 03d76dd47fa..f7317c3200b 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -9,7 +9,8 @@ import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; import { AuthPopoutType, closeSsoAuthResultPopout, - closeTwoFactorAuthPopout, + closeTwoFactorAuthEmailPopout, + closeTwoFactorAuthWebAuthnPopout, } from "../popup/utils/auth-popout-window"; export class ExtensionTwoFactorAuthComponentService @@ -67,13 +68,24 @@ export class ExtensionTwoFactorAuthComponentService return; } - const inTwoFactorAuthPopout = BrowserPopupUtils.inSingleActionPopout( + const inTwoFactorAuthWebAuthnPopout = BrowserPopupUtils.inSingleActionPopout( this.window, - AuthPopoutType.twoFactorAuth, + AuthPopoutType.twoFactorAuthWebAuthn, ); - if (inTwoFactorAuthPopout) { - await closeTwoFactorAuthPopout(); + if (inTwoFactorAuthWebAuthnPopout) { + await closeTwoFactorAuthWebAuthnPopout(); + return; + } + + const inTwoFactorAuthEmailPopout = BrowserPopupUtils.inSingleActionPopout( + this.window, + AuthPopoutType.twoFactorAuthEmail, + ); + + if (inTwoFactorAuthEmailPopout) { + await closeTwoFactorAuthEmailPopout(); + return; } } diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts index 10d203d3a84..f09b7a147fd 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts @@ -4,6 +4,7 @@ import { } from "@bitwarden/auth/angular"; import { DialogService } from "@bitwarden/components"; +import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window"; import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; // TODO: popup state persistence should eventually remove the need for this service @@ -26,7 +27,7 @@ export class ExtensionTwoFactorAuthEmailComponentService type: "warning", }); if (confirmed) { - await BrowserPopupUtils.openCurrentPagePopout(this.window); + await openTwoFactorAuthEmailPopout(); } } } From d28abe46fc710293f63c059202b9184319025ffd Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Thu, 23 Jan 2025 18:37:02 -0500 Subject: [PATCH 089/113] PM-8113 - Fix build after merge conflict issue --- libs/auth/src/angular/two-factor-auth/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/auth/src/angular/two-factor-auth/index.ts b/libs/auth/src/angular/two-factor-auth/index.ts index acf67d94c12..c5dc7b1a59d 100644 --- a/libs/auth/src/angular/two-factor-auth/index.ts +++ b/libs/auth/src/angular/two-factor-auth/index.ts @@ -1,7 +1,6 @@ export * from "./two-factor-auth-component.service"; export * from "./default-two-factor-auth-component.service"; export * from "./two-factor-auth.component"; -export * from "./two-factor-auth-expired.component"; export * from "./two-factor-auth.guard"; export * from "./child-components"; From 314c1a266604855730a7afe541693aa7244131b5 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 24 Jan 2025 16:05:15 -0500 Subject: [PATCH 090/113] PM-8113 - 2FA - Duo & Email comps - strict typescript adherence. --- .../two-factor-auth-duo.component.ts | 6 +-- .../two-factor-auth-email.component.ts | 46 ++++++++++++------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts index e670ff9fec9..a09d1aed191 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-duo/two-factor-auth-duo.component.ts @@ -43,7 +43,7 @@ export class TwoFactorAuthDuoComponent implements OnInit { @Output() token = new EventEmitter(); @Input() providerData: any; - duoFramelessUrl: string = null; + duoFramelessUrl: string | undefined = undefined; constructor( protected i18nService: I18nService, @@ -67,10 +67,10 @@ export class TwoFactorAuthDuoComponent implements OnInit { // Called via parent two-factor-auth component. async launchDuoFrameless(): Promise { - if (this.duoFramelessUrl === null) { + if (this.duoFramelessUrl === null || this.duoFramelessUrl === undefined) { this.toastService.showToast({ variant: "error", - title: null, + title: "", message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), }); return; diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts index ac536298ab2..0e92d4c71c5 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.ts @@ -45,8 +45,8 @@ import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-comp export class TwoFactorAuthEmailComponent implements OnInit { @Output() token = new EventEmitter(); - twoFactorEmail: string = null; - emailPromise: Promise; + twoFactorEmail: string | undefined = undefined; + emailPromise: Promise | undefined = undefined; tokenValue: string = ""; constructor( @@ -64,22 +64,35 @@ export class TwoFactorAuthEmailComponent implements OnInit { async ngOnInit(): Promise { await this.twoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa?.(); - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(TwoFactorProviderType.Email); - }); - this.twoFactorEmail = providerData.Email; + const providers = await this.twoFactorService.getProviders(); - if ((await this.twoFactorService.getProviders()).size > 1) { + if (!providers) { + throw new Error("User has no 2FA Providers"); + } + + const email2faProviderData = providers.get(TwoFactorProviderType.Email); + + if (!email2faProviderData) { + throw new Error("Unable to retrieve email 2FA provider data"); + } + + this.twoFactorEmail = email2faProviderData.Email; + + if (providers.size > 1) { await this.sendEmail(false); } } async sendEmail(doToast: boolean) { - if (this.emailPromise != null) { + if (this.emailPromise !== undefined) { return; } - if ((await this.loginStrategyService.getEmail()) == null) { + // TODO: PM-17545 - consider building a method on the login strategy service to get a mostly + // initialized TwoFactorEmailRequest in 1 call instead of 5 like we do today. + const email = await this.loginStrategyService.getEmail(); + + if (email == null) { this.toastService.showToast({ variant: "error", title: this.i18nService.t("errorOccurred"), @@ -90,19 +103,20 @@ export class TwoFactorAuthEmailComponent implements OnInit { try { const request = new TwoFactorEmailRequest(); - request.email = await this.loginStrategyService.getEmail(); - request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); + request.email = email; + + request.masterPasswordHash = (await this.loginStrategyService.getMasterPasswordHash()) ?? ""; request.ssoEmail2FaSessionToken = - await this.loginStrategyService.getSsoEmail2FaSessionToken(); + (await this.loginStrategyService.getSsoEmail2FaSessionToken()) ?? ""; request.deviceIdentifier = await this.appIdService.getAppId(); - request.authRequestAccessCode = await this.loginStrategyService.getAccessCode(); - request.authRequestId = await this.loginStrategyService.getAuthRequestId(); + request.authRequestAccessCode = (await this.loginStrategyService.getAccessCode()) ?? ""; + request.authRequestId = (await this.loginStrategyService.getAuthRequestId()) ?? ""; this.emailPromise = this.apiService.postTwoFactorEmail(request); await this.emailPromise; if (doToast) { this.toastService.showToast({ variant: "success", - title: null, + title: "", message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail), }); } @@ -110,6 +124,6 @@ export class TwoFactorAuthEmailComponent implements OnInit { this.logService.error(e); } - this.emailPromise = null; + this.emailPromise = undefined; } } From 8506449f9d3785f3923d5672cdb6fb3ac16f673a Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 24 Jan 2025 16:18:38 -0500 Subject: [PATCH 091/113] PM-8113 - TwoFactorAuth - Clean up unused stuff and get tests passing --- .../two-factor-auth/two-factor-auth.component.spec.ts | 11 +++++------ .../two-factor-auth/two-factor-auth.component.ts | 2 -- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index e4c1a16e701..af12c54371d 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -1,3 +1,5 @@ +// FIXME: Update this file to be type safe and remove this and next line +// @ts-strict-ignore import { Component } from "@angular/core"; import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ActivatedRoute, convertToParamMap, Router } from "@angular/router"; @@ -27,7 +29,6 @@ import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/maste import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.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 { SyncService } from "@bitwarden/common/platform/sync"; @@ -68,7 +69,6 @@ describe("TwoFactorComponent", () => { let mockToastService: MockProxy; let mockTwoFactorAuthCompService: MockProxy; let mockSyncService: MockProxy; - let mockMessagingService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -103,8 +103,9 @@ describe("TwoFactorComponent", () => { mockDialogService = mock(); mockToastService = mock(); mockTwoFactorAuthCompService = mock(); + mockTwoFactorAuthCompService.handle2faSuccess = undefined; + mockSyncService = mock(); - mockMessagingService = mock(); mockUserDecryptionOpts = { noMasterPassword: new UserDecryptionOptions({ @@ -149,7 +150,7 @@ describe("TwoFactorComponent", () => { }), }; - selectedUserDecryptionOptions = new BehaviorSubject(null); + selectedUserDecryptionOptions = new BehaviorSubject(undefined); mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; TestBed.configureTestingModule({ @@ -186,7 +187,6 @@ describe("TwoFactorComponent", () => { { provide: ToastService, useValue: mockToastService }, { provide: TwoFactorAuthComponentService, useValue: mockTwoFactorAuthCompService }, { provide: SyncService, useValue: mockSyncService }, - { provide: MessagingService, useValue: mockMessagingService }, ], }); @@ -260,7 +260,6 @@ describe("TwoFactorComponent", () => { // Assert expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith( new TokenTwoFactorRequest(component.selectedProviderType, token, remember), - null, // captcha token not supported ); }); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 8bcb405368e..9a509e8a4fe 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -26,7 +26,6 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.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 { SyncService } from "@bitwarden/common/platform/sync"; import { @@ -134,7 +133,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private toastService: ToastService, private twoFactorAuthComponentService: TwoFactorAuthComponentService, private syncService: SyncService, - private messagingService: MessagingService, private destroyRef: DestroyRef, ) {} From e6e3cf623a56f7b9a5dd60b9ba01967f176fdb7d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 24 Jan 2025 16:32:32 -0500 Subject: [PATCH 092/113] PM-8113 - Clean up used service method + TODO as I've confirmed it works for other flows. --- .../services/extension-two-factor-auth-component.service.ts | 5 ----- .../two-factor-auth/two-factor-auth-component.service.ts | 5 ----- 2 files changed, 10 deletions(-) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index f7317c3200b..ee2cc76c7b0 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -38,12 +38,7 @@ export class ExtensionTwoFactorAuthComponentService document.body.classList.remove("linux-webauthn"); } - closeWindow(): void { - this.window.close(); - } - async handle2faSuccess(): Promise { - // TODO: confirm that moving this from SSO flow only to general flow doesn't introduce any issues // Force sidebars (FF && Opera) to reload while exempting current window // because we are just going to close the current window if it is in a popout // or navigate forward if it is in the popup diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts index 2e71f637ea3..60b2916b697 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth-component.service.ts @@ -29,11 +29,6 @@ export abstract class TwoFactorAuthComponentService { */ abstract removePopupWidthExtension?(): void; - /** - * Optionally closes the window if the client requires it - */ - abstract closeWindow?(): void; - /** * We used to use the user's master key to encrypt their data. We deprecated that approach * and now use a user key. This method should be called if we detect that the user From 7d90d1aa5e6b605dc1510d93865145527ad075e5 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 24 Jan 2025 16:37:35 -0500 Subject: [PATCH 093/113] PM-8113 - TODO: test all comp services --- .../services/extension-two-factor-auth-component.service.ts | 1 + .../services/extension-two-factor-auth-duo-component.service.ts | 1 + .../extension-two-factor-auth-email-component.service.ts | 2 ++ .../extension-two-factor-auth-webauthn-component.service.ts | 1 + .../services/desktop-two-factor-auth-duo-component.service.ts | 1 + .../web-two-factor-auth-duo-component.service.ts | 1 + 6 files changed, 7 insertions(+) diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index ee2cc76c7b0..220feeeb60e 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -13,6 +13,7 @@ import { closeTwoFactorAuthWebAuthnPopout, } from "../popup/utils/auth-popout-window"; +// TODO: add tests export class ExtensionTwoFactorAuthComponentService extends DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts index 87925185874..8d0f3f7cc23 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts @@ -12,6 +12,7 @@ interface Message { code: string; state: string; } +// TODO: add tests export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { constructor( diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts index f09b7a147fd..304e48eaa57 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts @@ -8,6 +8,8 @@ import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; // TODO: popup state persistence should eventually remove the need for this service +// TODO: add tests + export class ExtensionTwoFactorAuthEmailComponentService extends DefaultTwoFactorAuthEmailComponentService implements TwoFactorAuthEmailComponentService diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts index 84a54cbc12e..96697e0363b 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts @@ -4,6 +4,7 @@ import { } from "@bitwarden/auth/angular"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// TODO: add tests export class ExtensionTwoFactorAuthWebAuthnComponentService extends DefaultTwoFactorAuthWebAuthnComponentService implements TwoFactorAuthWebAuthnComponentService diff --git a/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts index eef03ca5b53..d051124a6bc 100644 --- a/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts +++ b/apps/desktop/src/auth/services/desktop-two-factor-auth-duo-component.service.ts @@ -11,6 +11,7 @@ import { CommandDefinition, MessageListener } from "@bitwarden/common/platform/m // We should explore consolidating the messaging approach across clients - i.e., we // should use the same command definition across all clients. We use duoResult on extension for no real // benefit. +// TODO: add tests export const DUO_2FA_RESULT_COMMAND = new CommandDefinition<{ code: string; state: string }>( "duoCallback", ); diff --git a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts index ac8eccb5198..cee7b2559f7 100644 --- a/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts +++ b/apps/web/src/app/auth/core/services/two-factor-auth/web-two-factor-auth-duo-component.service.ts @@ -3,6 +3,7 @@ import { fromEvent, map, Observable, share } from "rxjs"; import { TwoFactorAuthDuoComponentService, Duo2faResult } from "@bitwarden/auth/angular"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; +// TODO: add tests export class WebTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { private duo2faResult$: Observable; From 7b5a751f07a9af219810f59fce208eba9a6afda8 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Fri, 24 Jan 2025 16:50:13 -0500 Subject: [PATCH 094/113] PM-8113 - TwoFactorAuthComponent Tests - fix tests by removing mock of removed method. --- .../angular/two-factor-auth/two-factor-auth.component.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index af12c54371d..deb95ed2cdb 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -381,10 +381,6 @@ describe("TwoFactorComponent", () => { }); describe("Trusted Device Encryption scenarios", () => { - beforeEach(() => { - mockTwoFactorAuthCompService.closeWindow = undefined; - }); - describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => { beforeEach(() => { selectedUserDecryptionOptions.next( From 08a13c021caa1c71a00642681915faea15891ca2 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Sat, 25 Jan 2025 12:08:36 -0500 Subject: [PATCH 095/113] PM-8113 - Revert changes to login strategies to avoid scope creep for the sake of typescript strictness. --- .../angular/two-factor-auth/two-factor-auth.component.spec.ts | 1 + .../src/angular/two-factor-auth/two-factor-auth.component.ts | 1 + libs/auth/src/common/abstractions/login-strategy.service.ts | 2 +- .../common/services/login-strategies/login-strategy.service.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index deb95ed2cdb..9d09782b5b2 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -260,6 +260,7 @@ describe("TwoFactorComponent", () => { // Assert expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith( new TokenTwoFactorRequest(component.selectedProviderType, token, remember), + "", ); }); diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 9a509e8a4fe..08090187188 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -230,6 +230,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { try { this.formPromise = this.loginStrategyService.logInTwoFactor( new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember), + "", // TODO: PM-15162 - deprecate captchaResponse ); const authResult: AuthResult = await this.formPromise; this.logService.info("Successfully submitted two factor token"); diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index 3a433e0d310..e9fa780b0fe 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -67,7 +67,7 @@ export abstract class LoginStrategyServiceAbstraction { logInTwoFactor: ( twoFactor: TokenTwoFactorRequest, // TODO: PM-15162 - deprecate captchaResponse - captchaResponse?: string, + captchaResponse: string, ) => Promise; /** * Creates a master key from the provided master password and email. diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 5c0c8c98250..e3a20fcfe72 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -244,7 +244,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { async logInTwoFactor( twoFactor: TokenTwoFactorRequest, - captchaResponse?: string, + captchaResponse: string, ): Promise { if (!(await this.isSessionValid())) { throw new Error(this.i18nService.t("sessionTimeout")); From c4e3585b8a44f1a67bed91aac7a052e873f5c60b Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Sat, 25 Jan 2025 21:11:38 -0500 Subject: [PATCH 096/113] PM-8113 - ExtensionTwoFactorAuthComponentService tests --- ...-two-factor-auth-component.service.spec.ts | 171 ++++++++++++++++++ ...nsion-two-factor-auth-component.service.ts | 1 - 2 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/auth/services/extension-two-factor-auth-component.service.spec.ts diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.spec.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.spec.ts new file mode 100644 index 00000000000..3b92bfc1623 --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.spec.ts @@ -0,0 +1,171 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +// Must mock modules before importing +jest.mock("../popup/utils/auth-popout-window", () => { + const originalModule = jest.requireActual("../popup/utils/auth-popout-window"); + + return { + ...originalModule, // avoid losing the original module's exports + closeSsoAuthResultPopout: jest.fn(), + closeTwoFactorAuthWebAuthnPopout: jest.fn(), + closeTwoFactorAuthEmailPopout: jest.fn(), + }; +}); + +jest.mock("../../platform/popup/browser-popup-utils", () => ({ + inSingleActionPopout: jest.fn(), +})); + +import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; + +import { BrowserApi } from "../../platform/browser/browser-api"; +import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; +import { + AuthPopoutType, + closeSsoAuthResultPopout, + closeTwoFactorAuthEmailPopout, + closeTwoFactorAuthWebAuthnPopout, +} from "../popup/utils/auth-popout-window"; + +import { ExtensionTwoFactorAuthComponentService } from "./extension-two-factor-auth-component.service"; + +describe("ExtensionTwoFactorAuthComponentService", () => { + let extensionTwoFactorAuthComponentService: ExtensionTwoFactorAuthComponentService; + let window: MockProxy; + + beforeEach(() => { + jest.clearAllMocks(); + + window = mock(); + document.body.className = ""; // Reset any added classes between tests. + + extensionTwoFactorAuthComponentService = new ExtensionTwoFactorAuthComponentService(window); + }); + + describe("shouldCheckForWebAuthnQueryParamResponse", () => { + it("should return true for the extension", () => { + expect( + extensionTwoFactorAuthComponentService.shouldCheckForWebAuthnQueryParamResponse(), + ).toBe(true); + }); + }); + + describe("extendPopupWidthIfRequired", () => { + it("should add linux-webauthn class to body if selected2faProviderType is WebAuthn and isLinux is true", async () => { + jest + .spyOn(extensionTwoFactorAuthComponentService as unknown as any, "isLinux") + .mockResolvedValue(true); + + await extensionTwoFactorAuthComponentService.extendPopupWidthIfRequired( + TwoFactorProviderType.WebAuthn, + ); + expect(document.body.classList).toContain("linux-webauthn"); + }); + + it("should not add linux-webauthn class to body if selected2faProviderType is WebAuthn and isLinux is false", async () => { + jest + .spyOn(extensionTwoFactorAuthComponentService as unknown as any, "isLinux") + .mockResolvedValue(false); + + await extensionTwoFactorAuthComponentService.extendPopupWidthIfRequired( + TwoFactorProviderType.WebAuthn, + ); + expect(document.body.classList).not.toContain("linux-webauthn"); + }); + + it.each([ + [true, TwoFactorProviderType.Email], + [false, TwoFactorProviderType.Email], + ])( + "should not add linux-webauthn class to body if selected2faProviderType is not WebAuthn and isLinux is %s", + async (isLinux, selected2faProviderType) => { + jest + .spyOn(extensionTwoFactorAuthComponentService as unknown as any, "isLinux") + .mockResolvedValue(isLinux); + + await extensionTwoFactorAuthComponentService.extendPopupWidthIfRequired( + selected2faProviderType, + ); + + expect(document.body.classList).not.toContain("linux-webauthn"); + }, + ); + }); + + describe("removePopupWidthExtension", () => { + it("should remove linux-webauthn class from body", () => { + document.body.classList.add("linux-webauthn"); + extensionTwoFactorAuthComponentService.removePopupWidthExtension(); + expect(document.body.classList).not.toContain("linux-webauthn"); + }); + }); + + describe("handle2faSuccess", () => { + it("should call reload open windows (exempting current) and not close any popouts", async () => { + const reloadOpenWindowsSpy = jest.spyOn(BrowserApi, "reloadOpenWindows").mockImplementation(); + + const inSingleActionPopoutSpy = jest + .spyOn(BrowserPopupUtils, "inSingleActionPopout") + .mockReturnValue(false); + + await extensionTwoFactorAuthComponentService.handle2faSuccess(); + + expect(reloadOpenWindowsSpy).toHaveBeenCalledWith(true); + + expect(inSingleActionPopoutSpy).toHaveBeenCalledTimes(3); + + // Ensure none of the close popout methods were called + expect(closeSsoAuthResultPopout).not.toHaveBeenCalled(); + expect(closeTwoFactorAuthWebAuthnPopout).not.toHaveBeenCalled(); + expect(closeTwoFactorAuthEmailPopout).not.toHaveBeenCalled(); + }); + + it("should call closeSsoAuthResultPopout if in SSO auth result popout", async () => { + const reloadOpenWindowsSpy = jest.spyOn(BrowserApi, "reloadOpenWindows").mockImplementation(); + + const inSingleActionPopoutSpy = jest + .spyOn(BrowserPopupUtils, "inSingleActionPopout") + .mockImplementation((_, key) => { + return key === AuthPopoutType.ssoAuthResult; + }); + + await extensionTwoFactorAuthComponentService.handle2faSuccess(); + + expect(reloadOpenWindowsSpy).toHaveBeenCalledWith(true); + expect(inSingleActionPopoutSpy).toHaveBeenCalledTimes(1); + expect(closeSsoAuthResultPopout).toHaveBeenCalled(); + }); + + it("should call closeTwoFactorAuthWebAuthnPopout if in two factor auth webauthn popout", async () => { + const reloadOpenWindowsSpy = jest.spyOn(BrowserApi, "reloadOpenWindows").mockImplementation(); + + const inSingleActionPopoutSpy = jest + .spyOn(BrowserPopupUtils, "inSingleActionPopout") + .mockImplementation((_, key) => { + return key === AuthPopoutType.twoFactorAuthWebAuthn; + }); + + await extensionTwoFactorAuthComponentService.handle2faSuccess(); + + expect(reloadOpenWindowsSpy).toHaveBeenCalledWith(true); + expect(inSingleActionPopoutSpy).toHaveBeenCalledTimes(2); + expect(closeTwoFactorAuthWebAuthnPopout).toHaveBeenCalled(); + }); + + it("should call closeTwoFactorAuthEmailPopout if in two factor auth email popout", async () => { + const reloadOpenWindowsSpy = jest.spyOn(BrowserApi, "reloadOpenWindows").mockImplementation(); + + const inSingleActionPopoutSpy = jest + .spyOn(BrowserPopupUtils, "inSingleActionPopout") + .mockImplementation((_, key) => { + return key === AuthPopoutType.twoFactorAuthEmail; + }); + + await extensionTwoFactorAuthComponentService.handle2faSuccess(); + + expect(reloadOpenWindowsSpy).toHaveBeenCalledWith(true); + expect(inSingleActionPopoutSpy).toHaveBeenCalledTimes(3); + expect(closeTwoFactorAuthEmailPopout).toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts index 220feeeb60e..ee2cc76c7b0 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-component.service.ts @@ -13,7 +13,6 @@ import { closeTwoFactorAuthWebAuthnPopout, } from "../popup/utils/auth-popout-window"; -// TODO: add tests export class ExtensionTwoFactorAuthComponentService extends DefaultTwoFactorAuthComponentService implements TwoFactorAuthComponentService From d954801929ce28429b6f712771f817f04e18fcb2 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 17:47:00 -0500 Subject: [PATCH 097/113] PM-8113 - Test ExtensionTwoFactorAuthDuoComponentService --- ...-factor-auth-duo-component.service.spec.ts | 93 +++++++++++++++++++ ...n-two-factor-auth-duo-component.service.ts | 1 - 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.spec.ts diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.spec.ts b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.spec.ts new file mode 100644 index 00000000000..89a26aad27d --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.spec.ts @@ -0,0 +1,93 @@ +import { MockProxy, mock } from "jest-mock-extended"; +import { BehaviorSubject, firstValueFrom } from "rxjs"; + +import { + Environment, + EnvironmentService, +} from "@bitwarden/common/platform/abstractions/environment.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; +import I18nService from "../../platform/services/i18n.service"; + +import { ExtensionTwoFactorAuthDuoComponentService } from "./extension-two-factor-auth-duo-component.service"; + +describe("ExtensionTwoFactorAuthDuoComponentService", () => { + let extensionTwoFactorAuthDuoComponentService: ExtensionTwoFactorAuthDuoComponentService; + let browserMessagingApi: MockProxy; + let environmentService: MockProxy; + let i18nService: MockProxy; + let platformUtilsService: MockProxy; + + beforeEach(() => { + jest.clearAllMocks(); + + browserMessagingApi = mock(); + environmentService = mock(); + i18nService = mock(); + platformUtilsService = mock(); + + extensionTwoFactorAuthDuoComponentService = new ExtensionTwoFactorAuthDuoComponentService( + browserMessagingApi, + environmentService, + i18nService, + platformUtilsService, + ); + }); + + describe("listenForDuo2faResult$", () => { + it("should return an observable that emits a duo 2FA result when a duo result message is received", async () => { + const message = { + command: "duoResult", + code: "123456", + state: "abcdef", + }; + const expectedDuo2faResult = { + code: message.code, + state: message.state, + token: `${message.code}|${message.state}`, + }; + + const messsageStream$ = new BehaviorSubject(message); + browserMessagingApi.messageListener$.mockReturnValue(messsageStream$); + + const duo2faResult = await firstValueFrom( + extensionTwoFactorAuthDuoComponentService.listenForDuo2faResult$(), + ); + expect(duo2faResult).toEqual(expectedDuo2faResult); + }); + }); + + describe("launchDuoFrameless", () => { + it("should launch the duo frameless url", async () => { + // Arrange + const duoFramelessUrl = "https://duoFramelessUrl"; + const webVaultUrl = "https://webVaultUrl"; + + i18nService.t.mockImplementation((key) => key); + + const launchUrl = `${webVaultUrl}/duo-redirect-connector.html?duoFramelessUrl=${encodeURIComponent( + duoFramelessUrl, + )}&handOffMessage=${encodeURIComponent( + JSON.stringify({ + title: "youSuccessfullyLoggedIn", + message: "youMayCloseThisWindow", + isCountdown: false, + }), + )}`; + + const mockEnvironment = { + getWebVaultUrl: () => webVaultUrl, + } as unknown as Environment; + + const environmentBSubject = new BehaviorSubject(mockEnvironment); + environmentService.environment$ = environmentBSubject.asObservable(); + + // Act + await extensionTwoFactorAuthDuoComponentService.launchDuoFrameless(duoFramelessUrl); + + // Assert + expect(platformUtilsService.launchUri).toHaveBeenCalledWith(launchUrl); + }); + }); +}); diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts index 8d0f3f7cc23..87925185874 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-duo-component.service.ts @@ -12,7 +12,6 @@ interface Message { code: string; state: string; } -// TODO: add tests export class ExtensionTwoFactorAuthDuoComponentService implements TwoFactorAuthDuoComponentService { constructor( From b7cd1f83d8fe26ebcbe9245cf436c3fb51940268 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 18:00:50 -0500 Subject: [PATCH 098/113] PM-8113 - ExtensionTwoFactorAuthEmailComponentService - add tests --- ...actor-auth-email-component.service.spec.ts | 93 +++++++++++++++++++ ...two-factor-auth-email-component.service.ts | 2 - 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts new file mode 100644 index 00000000000..223375fd903 --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.spec.ts @@ -0,0 +1,93 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { DialogService } from "@bitwarden/components"; + +// Must mock modules before importing +jest.mock("../popup/utils/auth-popout-window", () => { + const originalModule = jest.requireActual("../popup/utils/auth-popout-window"); + + return { + ...originalModule, // avoid losing the original module's exports + openTwoFactorAuthEmailPopout: jest.fn(), + }; +}); + +jest.mock("../../platform/popup/browser-popup-utils", () => ({ + inPopup: jest.fn(), +})); + +import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout-window"; +import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; + +import { ExtensionTwoFactorAuthEmailComponentService } from "./extension-two-factor-auth-email-component.service"; + +describe("ExtensionTwoFactorAuthEmailComponentService", () => { + let extensionTwoFactorAuthEmailComponentService: ExtensionTwoFactorAuthEmailComponentService; + + let dialogService: MockProxy; + let window: MockProxy; + + beforeEach(() => { + jest.clearAllMocks(); + + dialogService = mock(); + window = mock(); + + extensionTwoFactorAuthEmailComponentService = new ExtensionTwoFactorAuthEmailComponentService( + dialogService, + window, + ); + }); + + describe("openPopoutIfApprovedForEmail2fa", () => { + it("should open a popout if the user confirms the warning to popout the extension when in the popup", async () => { + // Arrange + dialogService.openSimpleDialog.mockResolvedValue(true); + + jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true); + + // Act + await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa(); + + // Assert + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "warning" }, + content: { key: "popup2faCloseMessage" }, + type: "warning", + }); + + expect(openTwoFactorAuthEmailPopout).toHaveBeenCalled(); + }); + + it("should not open a popout if the user cancels the warning to popout the extension when in the popup", async () => { + // Arrange + dialogService.openSimpleDialog.mockResolvedValue(false); + + jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(true); + + // Act + await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa(); + + // Assert + expect(dialogService.openSimpleDialog).toHaveBeenCalledWith({ + title: { key: "warning" }, + content: { key: "popup2faCloseMessage" }, + type: "warning", + }); + + expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled(); + }); + + it("should not open a popout if not in the popup", async () => { + // Arrange + jest.spyOn(BrowserPopupUtils, "inPopup").mockReturnValue(false); + + // Act + await extensionTwoFactorAuthEmailComponentService.openPopoutIfApprovedForEmail2fa(); + + // Assert + expect(dialogService.openSimpleDialog).not.toHaveBeenCalled(); + expect(openTwoFactorAuthEmailPopout).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts index 304e48eaa57..f09b7a147fd 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-email-component.service.ts @@ -8,8 +8,6 @@ import { openTwoFactorAuthEmailPopout } from "../../auth/popup/utils/auth-popout import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; // TODO: popup state persistence should eventually remove the need for this service -// TODO: add tests - export class ExtensionTwoFactorAuthEmailComponentService extends DefaultTwoFactorAuthEmailComponentService implements TwoFactorAuthEmailComponentService From fd1cb3c51bfd42fb141ba20024c9a52c85ca40ac Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 18:05:22 -0500 Subject: [PATCH 099/113] PM-8113 - Test ExtensionTwoFactorAuthWebAuthnComponentService --- ...or-auth-webauthn-component.service.spec.ts | 44 +++++++++++++++++++ ...-factor-auth-webauthn-component.service.ts | 1 - 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.spec.ts diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.spec.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.spec.ts new file mode 100644 index 00000000000..b53f05172ce --- /dev/null +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.spec.ts @@ -0,0 +1,44 @@ +import { MockProxy, mock } from "jest-mock-extended"; + +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { ExtensionTwoFactorAuthWebAuthnComponentService } from "./extension-two-factor-auth-webauthn-component.service"; + +describe("ExtensionTwoFactorAuthWebAuthnComponentService", () => { + let extensionTwoFactorAuthWebAuthnComponentService: ExtensionTwoFactorAuthWebAuthnComponentService; + + let platformUtilsService: MockProxy; + + beforeEach(() => { + jest.clearAllMocks(); + + platformUtilsService = mock(); + + extensionTwoFactorAuthWebAuthnComponentService = + new ExtensionTwoFactorAuthWebAuthnComponentService(platformUtilsService); + }); + + describe("shouldOpenWebAuthnInNewTab", () => { + it("should return false if the browser is Chrome", () => { + // Arrange + platformUtilsService.isChrome.mockReturnValue(true); + + // Act + const result = extensionTwoFactorAuthWebAuthnComponentService.shouldOpenWebAuthnInNewTab(); + + // Assert + expect(result).toBe(false); + }); + + it("should return true if the browser is not Chrome", () => { + // Arrange + platformUtilsService.isChrome.mockReturnValue(false); + + // Act + const result = extensionTwoFactorAuthWebAuthnComponentService.shouldOpenWebAuthnInNewTab(); + + // Assert + expect(result).toBe(true); + }); + }); +}); diff --git a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts index 96697e0363b..84a54cbc12e 100644 --- a/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts +++ b/apps/browser/src/auth/services/extension-two-factor-auth-webauthn-component.service.ts @@ -4,7 +4,6 @@ import { } from "@bitwarden/auth/angular"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -// TODO: add tests export class ExtensionTwoFactorAuthWebAuthnComponentService extends DefaultTwoFactorAuthWebAuthnComponentService implements TwoFactorAuthWebAuthnComponentService From 94f151779b7006888f22ed86d50250c28f73e7b7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 18:52:50 -0500 Subject: [PATCH 100/113] PM-8113 - Add 2fa icons (icons need tweaking still) --- .../angular/icons/two-factor-auth/index.ts | 4 ++ .../two-factor-auth-authenticator.icon.ts | 40 ++++++++++++++ .../two-factor-auth-email.icon.ts | 19 +++++++ .../two-factor-auth-webauthn.icon.ts | 41 ++++++++++++++ .../two-factor-auth-yubikey.icon.ts | 53 +++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 libs/auth/src/angular/icons/two-factor-auth/index.ts create mode 100644 libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts create mode 100644 libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts create mode 100644 libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts create mode 100644 libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts diff --git a/libs/auth/src/angular/icons/two-factor-auth/index.ts b/libs/auth/src/angular/icons/two-factor-auth/index.ts new file mode 100644 index 00000000000..f1d1914817d --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/index.ts @@ -0,0 +1,4 @@ +export * from "./two-factor-auth-authenticator.icon"; +export * from "./two-factor-auth-email.icon"; +export * from "./two-factor-auth-webauthn.icon"; +export * from "./two-factor-auth-yubikey.icon"; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts new file mode 100644 index 00000000000..e634d4cacd3 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -0,0 +1,40 @@ +import { svgIcon } from "@bitwarden/components"; + +// TODO: replace with icon with viewbox then change colors per docs. +export const TwoFactorAuthAuthenticatorIcon = svgIcon` + + + + + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts new file mode 100644 index 00000000000..c42e92d7198 --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -0,0 +1,19 @@ +import { svgIcon } from "@bitwarden/components"; + +// TODO: replace with icon with viewbox then change colors per docs. +export const TwoFactorAuthEmailIcon = svgIcon` + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts new file mode 100644 index 00000000000..43a43c6ff8c --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -0,0 +1,41 @@ +import { svgIcon } from "@bitwarden/components"; + +// TODO: replace with icon with viewbox then change colors per docs. +export const TwoFactorAuthWebAuthnIcon = svgIcon` + + + + + + + + + + + + + + +`; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts new file mode 100644 index 00000000000..6844a8f1d5d --- /dev/null +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts @@ -0,0 +1,53 @@ +import { svgIcon } from "@bitwarden/components"; + +// TODO: replace with icon with viewbox then change colors per docs. +export const TwoFactorAuthYubikeyIcon = svgIcon` + + + + + + + + + + + + + + + + + + + + + + + + + + +`; From ab38b3bd89a99c37a1ca58a8168c542d18fa2259 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 19:07:37 -0500 Subject: [PATCH 101/113] PM-8113 - TwoFactorAuthComponent - add setAnonLayoutDataByTwoFactorProviderType and handle email case as POC --- apps/browser/src/_locales/en/messages.json | 3 ++ apps/desktop/src/locales/en/messages.json | 3 ++ apps/web/src/locales/en/messages.json | 3 ++ .../two-factor-auth.component.ts | 34 +++++++++++++------ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index c7dca5ff1f7..7c3f865708a 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -860,6 +860,9 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, "restartRegistration": { "message": "Restart registration" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 4f18a88d994..2e13b92fd24 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -652,6 +652,9 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, "logInWithPasskey": { "message": "Log in with passkey" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 9e5b25a4abf..321adb9af06 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1179,6 +1179,9 @@ "logInToBitwarden": { "message": "Log in to Bitwarden" }, + "enterTheCodeSentToYourEmail": { + "message": "Enter the code sent to your email" + }, "authenticationTimeout": { "message": "Authentication timeout" }, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 08090187188..381376500fe 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -37,6 +37,9 @@ import { ToastService, } from "@bitwarden/components"; +import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; +import { TwoFactorAuthEmailIcon } from "../icons/two-factor-auth"; + import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component"; import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component"; import { TwoFactorAuthEmailComponent } from "./child-components/two-factor-auth-email/two-factor-auth-email.component"; @@ -104,8 +107,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { remember: [false], }); - title = ""; - formPromise: Promise | undefined; submitForm = async () => { @@ -134,6 +135,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private twoFactorAuthComponentService: TwoFactorAuthComponentService, private syncService: SyncService, private destroyRef: DestroyRef, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, ) {} async ngOnInit() { @@ -146,7 +148,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { await this.setSelected2faProviderType(); await this.set2faProviderData(); - await this.setTitleByTwoFactorProviderType(); + await this.setAnonLayoutDataByTwoFactorProviderType(); this.form.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => { if (value.token) { @@ -261,7 +263,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { }); this.providerData = providerData; this.selectedProviderType = response.type; - await this.setTitleByTwoFactorProviderType(); + await this.setAnonLayoutDataByTwoFactorProviderType(); } } @@ -294,13 +296,25 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { return true; } - async setTitleByTwoFactorProviderType() { - if (this.selectedProviderType == null) { - this.title = this.i18nService.t("loginUnavailable"); - return; - } + async setAnonLayoutDataByTwoFactorProviderType() { + switch (this.selectedProviderType) { + // case TwoFactorProviderType.Authenticator: + // this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + // pageTitle: this.i18nService.t("ADD ME"), + // pageSubtitle: this.i18nService.t("twoFactorAuthenticatorSubtitle"), + // pageIcon: TwoFactorAuthAuthenticatorIcon, + // }); + // break; + case TwoFactorProviderType.Email: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("enterTheCodeSentToYourEmail"), + pageIcon: TwoFactorAuthEmailIcon, + }); + break; - this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; + default: + break; + } } private async handleAuthResult(authResult: AuthResult) { From 3732e02e092c3ac5fbb5b6df4a853bf2abeb25a7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Mon, 27 Jan 2025 19:10:48 -0500 Subject: [PATCH 102/113] PM-8113 - TwoFactorEmailComp - work on converting to new design --- .../two-factor-auth-email.component.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html index c9d0901bcae..44fcf903bdd 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html @@ -1,8 +1,6 @@ -

- {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }} -

{{ "verificationCode" | i18n }} + - {{ "sendVerificationCodeEmailAgain" | i18n }} + {{ "resendCode" | i18n }} From 6b7f98eb719f0c5a91d5760ff9d1d986601fffa7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 13:35:47 -0500 Subject: [PATCH 103/113] PM-8113 - Update icons with proper svg with scaling via viewbox --- .../two-factor-auth-authenticator.icon.ts | 6 +++--- .../two-factor-auth-email.icon.ts | 16 ++++++++-------- .../two-factor-auth-webauthn.icon.ts | 16 ++++++++-------- .../two-factor-auth-yubikey.icon.ts | 12 ++++++------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts index e634d4cacd3..cf2c7f6293a 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -2,7 +2,7 @@ import { svgIcon } from "@bitwarden/components"; // TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthAuthenticatorIcon = svgIcon` - + - - diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts index c42e92d7198..414f1ff3ef5 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -2,18 +2,18 @@ import { svgIcon } from "@bitwarden/components"; // TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthEmailIcon = svgIcon` - + - - - - - - - + + + + + + `; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts index 43a43c6ff8c..46800a45c88 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -2,28 +2,28 @@ import { svgIcon } from "@bitwarden/components"; // TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthWebAuthnIcon = svgIcon` - + - - - - - - - + - - - - - From 115514da2756843e64aa6cffd3d6a2df4e75c1cd Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 13:40:24 -0500 Subject: [PATCH 104/113] PM-8113 - Update icons to use proper classes --- .../two-factor-auth-authenticator.icon.ts | 19 +++++++------- .../two-factor-auth-email.icon.ts | 17 ++++++------- .../two-factor-auth-webauthn.icon.ts | 25 +++++++++---------- .../two-factor-auth-yubikey.icon.ts | 23 ++++++++--------- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts index cf2c7f6293a..daef1f94dca 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-authenticator.icon.ts @@ -1,34 +1,33 @@ import { svgIcon } from "@bitwarden/components"; -// TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthAuthenticatorIcon = svgIcon` - - - - - - - - - diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts index 414f1ff3ef5..833ab3f8e98 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-email.icon.ts @@ -1,19 +1,18 @@ import { svgIcon } from "@bitwarden/components"; -// TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthEmailIcon = svgIcon` - - - - - - - - + + + + + + `; diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts index 46800a45c88..e82b1ed1799 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-webauthn.icon.ts @@ -1,40 +1,39 @@ import { svgIcon } from "@bitwarden/components"; -// TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthWebAuthnIcon = svgIcon` - - - - - - - - - - - - diff --git a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts index 6d62a8ba717..ca4b16cf630 100644 --- a/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts +++ b/libs/auth/src/angular/icons/two-factor-auth/two-factor-auth-yubikey.icon.ts @@ -1,43 +1,42 @@ import { svgIcon } from "@bitwarden/components"; -// TODO: replace with icon with viewbox then change colors per docs. export const TwoFactorAuthYubikeyIcon = svgIcon` - - - - - - - - - - - From af9d132cae03d3b75f99d6fbe8d754ae3b396cc7 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 14:53:27 -0500 Subject: [PATCH 105/113] PM-8113 - 2FA Auth Comp - Progress on implementing design changes --- apps/browser/src/_locales/en/messages.json | 12 ++++++- apps/desktop/src/locales/en/messages.json | 10 ++++++ apps/web/src/locales/en/messages.json | 10 ++++++ .../two-factor-auth.component.html | 36 +++++++++++++------ .../two-factor-auth.component.ts | 7 ++++ 5 files changed, 63 insertions(+), 12 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7c3f865708a..d4a56fe8f6b 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1352,12 +1352,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" - }, + }, + "selectAnotherMethod": { + "message": "Select another method", + "desccription": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 2e13b92fd24..7b773d26c2a 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -849,12 +849,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "desccription": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 321adb9af06..880fea3ca7d 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1434,12 +1434,22 @@ "rememberMe": { "message": "Remember me" }, + "dontAskAgainOnThisDeviceFor30Days": { + "message": "Don't ask again on this device for 30 days" + }, "sendVerificationCodeEmailAgain": { "message": "Send verification code email again" }, "useAnotherTwoStepMethod": { "message": "Use another two-step login method" }, + "selectAnotherMethod": { + "message": "Select another method", + "desccription": "Select another two-step login method" + }, + "useYourRecoveryCode": { + "message": "Use your recovery code" + }, "insertYubiKey": { "message": "Insert your YubiKey into your computer's USB port, then touch its button." }, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html index 93c1b8a6f20..0e156fc14c1 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.html @@ -32,7 +32,7 @@ #duoComponent /> - {{ "rememberMe" | i18n }} + {{ "dontAskAgainOnThisDeviceFor30Days" | i18n }} @@ -40,7 +40,7 @@

{{ "noTwoStepProviders2" | i18n }}

-
+
+ - - {{ "cancel" | i18n }} - -
-
- {{ - "useAnotherTwoStepMethod" | i18n - }} +

{{ "or" | i18n }}

+ + + +
diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 381376500fe..42cc0d60a97 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -465,6 +465,13 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { ); } + async use2faRecoveryCode() { + // TODO: should this just directly navigate on web? probably + // const env = await firstValueFrom(this.environmentService.environment$); + // const webVault = env.getWebVaultUrl(); + // this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); + } + async ngOnDestroy() { this.twoFactorAuthComponentService.removePopupWidthExtension?.(); } From 5df561c1a318be57b7b5b195338bdebc52e0684b Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 14:53:54 -0500 Subject: [PATCH 106/113] PM-8113 - TwoFactorOptionsComponent - add todos --- .../angular/two-factor-auth/two-factor-options.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts index 61f225cc21c..ef9dae7fd89 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-options.component.ts @@ -4,7 +4,6 @@ import { Component, EventEmitter, OnInit, Output } from "@angular/core"; import { firstValueFrom } from "rxjs"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { ClientType } from "@bitwarden/common/enums"; @@ -12,6 +11,8 @@ import { EnvironmentService } from "@bitwarden/common/platform/abstractions/envi import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { ButtonModule, DialogModule, DialogService, TypographyModule } from "@bitwarden/components"; +// TODO: rename all other options components to v1. +// TODO: deprecate recovery code approach per design export enum TwoFactorOptionsDialogResult { Provider = "Provider selected", Recover = "Recover selected", @@ -27,7 +28,7 @@ export type TwoFactorOptionsDialogResultType = { selector: "app-two-factor-options", templateUrl: "two-factor-options.component.html", imports: [CommonModule, JslibModule, DialogModule, ButtonModule, TypographyModule], - providers: [I18nPipe], + providers: [], }) export class TwoFactorOptionsComponent implements OnInit { @Output() onProviderSelected = new EventEmitter(); From 1066c1cb421875f853cb5d1c581dac5ebf511a96 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 17:53:36 -0500 Subject: [PATCH 107/113] PM-8113 - 2fa Email Comp - add style changes per discussion with design --- .../two-factor-auth-email.component.html | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html index 44fcf903bdd..3199bbf26e6 100644 --- a/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html +++ b/libs/auth/src/angular/two-factor-auth/child-components/two-factor-auth-email/two-factor-auth-email.component.html @@ -1,17 +1,26 @@ - + {{ "verificationCode" | i18n }} - + - - - {{ "resendCode" | i18n }} - + + From 01cae160f0aad7624b5eaf53e794228b82562d97 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 17:56:05 -0500 Subject: [PATCH 108/113] PM-8113 - TwoFactorAuthComponent - use2faRecoveryCode - build out method per discussion with design --- .../two-factor-auth/two-factor-auth.component.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 42cc0d60a97..ae78bd3589a 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -24,6 +24,7 @@ import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.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"; @@ -136,6 +137,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { private syncService: SyncService, private destroyRef: DestroyRef, private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, + private environmentService: EnvironmentService, ) {} async ngOnInit() { @@ -466,10 +468,11 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async use2faRecoveryCode() { - // TODO: should this just directly navigate on web? probably - // const env = await firstValueFrom(this.environmentService.environment$); - // const webVault = env.getWebVaultUrl(); - // this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); + // TODO: eventually we should have a consolidated recover-2fa component as a follow up + // so that we don't have to always open a new tab for non-web clients. + const env = await firstValueFrom(this.environmentService.environment$); + const webVault = env.getWebVaultUrl(); + this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); } async ngOnDestroy() { From 1ff50d22bae25f2bb4b171e286647a287854d71d Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 18:04:51 -0500 Subject: [PATCH 109/113] PM-8113 - TwoFactorAuthComp - fix comp tests --- .../two-factor-auth/two-factor-auth.component.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 9d09782b5b2..dc8f3218730 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -27,6 +27,7 @@ import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/for import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/identity-token/token-two-factor.request"; import { FakeMasterPasswordService } from "@bitwarden/common/auth/services/master-password/fake-master-password.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.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"; @@ -36,6 +37,8 @@ import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/sp import { UserId } from "@bitwarden/common/types/guid"; import { DialogService, ToastService } from "@bitwarden/components"; +import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; + import { TwoFactorAuthComponentService } from "./two-factor-auth-component.service"; import { TwoFactorAuthComponent } from "./two-factor-auth.component"; @@ -69,6 +72,8 @@ describe("TwoFactorComponent", () => { let mockToastService: MockProxy; let mockTwoFactorAuthCompService: MockProxy; let mockSyncService: MockProxy; + let anonLayoutWrapperDataService: MockProxy; + let mockEnvService: MockProxy; let mockUserDecryptionOpts: { noMasterPassword: UserDecryptionOptions; @@ -107,6 +112,9 @@ describe("TwoFactorComponent", () => { mockSyncService = mock(); + mockEnvService = mock(); + anonLayoutWrapperDataService = mock(); + mockUserDecryptionOpts = { noMasterPassword: new UserDecryptionOptions({ hasMasterPassword: false, @@ -187,6 +195,8 @@ describe("TwoFactorComponent", () => { { provide: ToastService, useValue: mockToastService }, { provide: TwoFactorAuthComponentService, useValue: mockTwoFactorAuthCompService }, { provide: SyncService, useValue: mockSyncService }, + { provide: EnvironmentService, useValue: mockEnvService }, + { provide: AnonLayoutWrapperDataService, useValue: anonLayoutWrapperDataService }, ], }); From 196ff0a6161586410840b6771c459f72ac543aee Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 18:21:27 -0500 Subject: [PATCH 110/113] PM-8113 - TwoFactorAuthComp - progress on adding 2fa provider page icons and subtitles --- apps/browser/src/_locales/en/messages.json | 9 ++++++ apps/desktop/src/locales/en/messages.json | 9 ++++++ apps/web/src/locales/en/messages.json | 9 ++++++ .../two-factor-auth.component.ts | 32 ++++++++++++++----- 4 files changed, 51 insertions(+), 8 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index d4a56fe8f6b..7d98ab3aae2 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -863,6 +863,15 @@ "enterTheCodeSentToYourEmail": { "message": "Enter the code sent to your email" }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Popout the extension to complete login." + }, "restartRegistration": { "message": "Restart registration" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 7b773d26c2a..71eaa2063d4 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -655,6 +655,12 @@ "enterTheCodeSentToYourEmail": { "message": "Enter the code sent to your email" }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "logInWithPasskey": { "message": "Log in with passkey" }, @@ -3180,6 +3186,9 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Launch Duo and follow the steps to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 880fea3ca7d..70d753c5405 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1182,6 +1182,12 @@ "enterTheCodeSentToYourEmail": { "message": "Enter the code sent to your email" }, + "enterTheCodeFromYourAuthenticatorApp": { + "message": "Enter the code from your authenticator app" + }, + "pressYourYubiKeyToAuthenticate": { + "message": "Press your YubiKey to authenticate" + }, "authenticationTimeout": { "message": "Authentication timeout" }, @@ -7146,6 +7152,9 @@ "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, + "duoTwoFactorRequiredPageSubtitle": { + "message": "Duo two-step login is required for your account. Launch Duo and follow the steps to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index ae78bd3589a..e2c80531328 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -39,7 +39,11 @@ import { } from "@bitwarden/components"; import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper-data.service"; -import { TwoFactorAuthEmailIcon } from "../icons/two-factor-auth"; +import { + TwoFactorAuthAuthenticatorIcon, + TwoFactorAuthEmailIcon, + TwoFactorAuthYubikeyIcon, +} from "../icons/two-factor-auth"; import { TwoFactorAuthAuthenticatorComponent } from "./child-components/two-factor-auth-authenticator.component"; import { TwoFactorAuthDuoComponent } from "./child-components/two-factor-auth-duo/two-factor-auth-duo.component"; @@ -299,20 +303,32 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async setAnonLayoutDataByTwoFactorProviderType() { + // TODO: finish adding all provider types switch (this.selectedProviderType) { - // case TwoFactorProviderType.Authenticator: - // this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ - // pageTitle: this.i18nService.t("ADD ME"), - // pageSubtitle: this.i18nService.t("twoFactorAuthenticatorSubtitle"), - // pageIcon: TwoFactorAuthAuthenticatorIcon, - // }); - // break; + case TwoFactorProviderType.Authenticator: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("enterTheCodeFromYourAuthenticatorApp"), + pageIcon: TwoFactorAuthAuthenticatorIcon, + }); + break; case TwoFactorProviderType.Email: this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ pageSubtitle: this.i18nService.t("enterTheCodeSentToYourEmail"), pageIcon: TwoFactorAuthEmailIcon, }); break; + case TwoFactorProviderType.Duo: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("duoTwoFactorRequiredPageSubtitle"), + pageIcon: TwoFactorAuthAuthenticatorIcon, + }); + break; + case TwoFactorProviderType.Yubikey: + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("pressYourYubiKeyToAuthenticate"), + pageIcon: TwoFactorAuthYubikeyIcon, + }); + break; default: break; From d0c372c7f1dd7122400f41202e868eecdcdb0132 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 22:35:51 -0500 Subject: [PATCH 111/113] PM-8113 - Browser Translations - update duoTwoFactorRequiredPageSubtitle to match design discussion --- apps/browser/src/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 7d98ab3aae2..e2fdc59e058 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -870,7 +870,7 @@ "message": "Press your YubiKey to authenticate" }, "duoTwoFactorRequiredPageSubtitle": { - "message": "Duo two-step login is required for your account. Popout the extension to complete login." + "message": "Duo two-step login is required for your account. Popout the extension to finish logging in." }, "restartRegistration": { "message": "Restart registration" From f7f2fa603701cc1770361adc0303962ea02ae1c9 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 23:14:38 -0500 Subject: [PATCH 112/113] PM-8113 - TwoFactorAuthComp - more work on getting page title / icons working --- apps/browser/src/_locales/en/messages.json | 3 +++ apps/desktop/src/locales/en/messages.json | 3 +++ apps/web/src/locales/en/messages.json | 3 +++ .../two-factor-auth.component.ts | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index e2fdc59e058..f8972cfa675 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -872,6 +872,9 @@ "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Popout the extension to finish logging in." }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "restartRegistration": { "message": "Restart registration" }, diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 71eaa2063d4..3ee9aa69a03 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -3189,6 +3189,9 @@ "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Launch Duo and follow the steps to finish logging in." }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo in Browser" }, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 70d753c5405..716324f35d1 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7155,6 +7155,9 @@ "duoTwoFactorRequiredPageSubtitle": { "message": "Duo two-step login is required for your account. Launch Duo and follow the steps to finish logging in." }, + "followTheStepsBelowToFinishLoggingIn": { + "message": "Follow the steps below to finish logging in." + }, "launchDuo": { "message": "Launch Duo" }, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index e2c80531328..2a9a0047465 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -42,6 +42,7 @@ import { AnonLayoutWrapperDataService } from "../anon-layout/anon-layout-wrapper import { TwoFactorAuthAuthenticatorIcon, TwoFactorAuthEmailIcon, + TwoFactorAuthWebAuthnIcon, TwoFactorAuthYubikeyIcon, } from "../icons/two-factor-auth"; @@ -303,7 +304,6 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async setAnonLayoutDataByTwoFactorProviderType() { - // TODO: finish adding all provider types switch (this.selectedProviderType) { case TwoFactorProviderType.Authenticator: this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ @@ -318,9 +318,11 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { }); break; case TwoFactorProviderType.Duo: + case TwoFactorProviderType.OrganizationDuo: this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ pageSubtitle: this.i18nService.t("duoTwoFactorRequiredPageSubtitle"), - pageIcon: TwoFactorAuthAuthenticatorIcon, + // TODO: figure out logo + // pageIcon: , }); break; case TwoFactorProviderType.Yubikey: @@ -329,8 +331,18 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { pageIcon: TwoFactorAuthYubikeyIcon, }); break; - + case TwoFactorProviderType.WebAuthn: + // TODO: figure out browser extension title implementation. + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageSubtitle: this.i18nService.t("followTheStepsBelowToFinishLoggingIn"), + pageIcon: TwoFactorAuthWebAuthnIcon, + }); + break; default: + this.logService.error( + "setAnonLayoutDataByTwoFactorProviderType: Unhandled 2FA provider type", + this.selectedProviderType, + ); break; } } From cbcc2fa8f0c490d3c477fc5875cd917a296b4576 Mon Sep 17 00:00:00 2001 From: Jared Snider Date: Tue, 28 Jan 2025 23:33:41 -0500 Subject: [PATCH 113/113] PM-8113 - Add todo --- .../src/angular/two-factor-auth/two-factor-auth.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts index 2a9a0047465..a3c33d9507b 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.ts @@ -496,7 +496,7 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy { } async use2faRecoveryCode() { - // TODO: eventually we should have a consolidated recover-2fa component as a follow up + // TODO: PM-17696 eventually we should have a consolidated recover-2fa component as a follow up // so that we don't have to always open a new tab for non-web clients. const env = await firstValueFrom(this.environmentService.environment$); const webVault = env.getWebVaultUrl();