From d5f033efa2a50e82780dd140b0431eb7bb455def Mon Sep 17 00:00:00 2001 From: Alec Rippberger <127791530+alec-livefront@users.noreply.github.com> Date: Fri, 28 Mar 2025 12:51:20 -0500 Subject: [PATCH] refactor(auth): [PM-9179] remove deprecated TwoFactorComponents Remove deprecated TwoFactorComponentsV1 and TwoFactorOptionsComponentV1 components, related functionality (unauthUiRefreshSwap) and orphaned styles/translation messages. --- apps/browser/src/_locales/en/messages.json | 36 -- .../two-factor-options-v1.component.html | 29 - .../popup/two-factor-options-v1.component.ts | 56 -- .../auth/popup/two-factor-v1.component.html | 196 ------- .../src/auth/popup/two-factor-v1.component.ts | 260 --------- apps/browser/src/popup/app-routing.module.ts | 53 +- apps/browser/src/popup/app.module.ts | 4 - apps/browser/src/popup/scss/misc.scss | 10 - apps/desktop/src/app/app-routing.module.ts | 39 +- apps/desktop/src/app/app.module.ts | 4 - .../auth/two-factor-options-v1.component.html | 33 -- .../auth/two-factor-options-v1.component.ts | 24 - .../src/auth/two-factor-v1.component.html | 175 ------ .../src/auth/two-factor-v1.component.ts | 190 ------- apps/desktop/src/locales/en/messages.json | 27 - apps/desktop/src/scss/pages.scss | 2 - .../auth/two-factor-options-v1.component.html | 45 -- .../auth/two-factor-options-v1.component.ts | 52 -- .../src/app/auth/two-factor-v1.component.html | 106 ---- .../src/app/auth/two-factor-v1.component.ts | 164 ------ apps/web/src/app/oss-routing.module.ts | 69 +-- .../src/app/shared/loose-components.module.ts | 6 - apps/web/src/locales/en/messages.json | 28 +- .../two-factor-options-v1.component.ts | 44 -- .../two-factor-v1.component.spec.ts | 505 ----------------- .../components/two-factor-v1.component.ts | 514 ------------------ .../functions/unauth-ui-refresh-route-swap.ts | 36 -- 27 files changed, 55 insertions(+), 2652 deletions(-) delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.html delete mode 100644 apps/browser/src/auth/popup/two-factor-options-v1.component.ts delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.html delete mode 100644 apps/browser/src/auth/popup/two-factor-v1.component.ts delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.html delete mode 100644 apps/desktop/src/auth/two-factor-options-v1.component.ts delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.html delete mode 100644 apps/desktop/src/auth/two-factor-v1.component.ts delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.html delete mode 100644 apps/web/src/app/auth/two-factor-options-v1.component.ts delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.html delete mode 100644 apps/web/src/app/auth/two-factor-v1.component.ts delete mode 100644 libs/angular/src/auth/components/two-factor-options-v1.component.ts delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.spec.ts delete mode 100644 libs/angular/src/auth/components/two-factor-v1.component.ts delete mode 100644 libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 2c940ccdf5a..8c47db0d331 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -1391,24 +1391,12 @@ "premiumRequiredDesc": { "message": "A Premium membership is required to use this feature." }, - "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." - }, "authenticationTimeout": { "message": "Authentication timeout" }, "authenticationSessionTimedOut": { "message": "The authentication session timed out. Please restart the login process." }, - "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", - "placeholders": { - "email": { - "content": "$1", - "example": "example@gmail.com" - } - } - }, "verificationCodeEmailSent": { "message": "Verification email sent to $EMAIL$.", "placeholders": { @@ -1418,18 +1406,9 @@ } } }, - "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", "description": "Select another two-step login method" @@ -1437,18 +1416,9 @@ "useYourRecoveryCode": { "message": "Use your recovery code" }, - "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." - }, "insertU2f": { "message": "Insert your security key into your computer's USB port. If it has a button, touch it." }, - "webAuthnNewTab": { - "message": "To start the WebAuthn 2FA verification. Click the button below to open a new tab and follow the instructions provided in the new tab." - }, - "webAuthnNewTabOpen": { - "message": "Open new tab" - }, "openInNewTab": { "message": "Open in new tab" }, @@ -3848,15 +3818,9 @@ "duoHealthCheckResultsInNullAuthUrlError": { "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, - "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." - }, "duoRequiredForAccount": { "message": "Duo two-step login is required for your account." }, - "popoutTheExtensionToCompleteLogin": { - "message": "Popout the extension to complete login." - }, "popoutExtension": { "message": "Popout extension" }, diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.html b/apps/browser/src/auth/popup/two-factor-options-v1.component.html deleted file mode 100644 index f25944aba65..00000000000 --- a/apps/browser/src/auth/popup/two-factor-options-v1.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- -
-

- {{ "twoStepOptions" | i18n }} -

-
-
-
-
-
- - -
-
-
diff --git a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts b/apps/browser/src/auth/popup/two-factor-options-v1.component.ts deleted file mode 100644 index 0c71421fc04..00000000000 --- a/apps/browser/src/auth/popup/two-factor-options-v1.component.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Component } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; - -import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-options-v1.component"; -import { - TwoFactorProviderDetails, - TwoFactorService, -} from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - -@Component({ - selector: "app-two-factor-options", - templateUrl: "two-factor-options-v1.component.html", -}) -export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponent { - constructor( - twoFactorService: TwoFactorService, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - private activatedRoute: ActivatedRoute, - ) { - super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService); - } - - close() { - this.navigateTo2FA(); - } - - override async choose(p: TwoFactorProviderDetails) { - await super.choose(p); - await this.twoFactorService.setSelectedProvider(p.type); - - this.navigateTo2FA(); - } - - navigateTo2FA() { - const sso = this.activatedRoute.snapshot.queryParamMap.get("sso") === "true"; - - if (sso) { - // Persist SSO flag back to the 2FA comp if it exists - // in order for successful login logic to work properly for - // SSO + 2FA in browser extension - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["2fa"], { queryParams: { sso: true } }); - } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["2fa"]); - } - } -} diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.html b/apps/browser/src/auth/popup/two-factor-v1.component.html deleted file mode 100644 index 126b0ea5a99..00000000000 --- a/apps/browser/src/auth/popup/two-factor-v1.component.html +++ /dev/null @@ -1,196 +0,0 @@ -
-
-
- -
-

- {{ title }} -

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

{{ "insertYubiKey" | i18n }}

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

{{ "webAuthnNewTab" | i18n }}

- -
-
- - -
-

- {{ "duoRequiredForAccount" | i18n }} -

- -

- {{ "popoutTheExtensionToCompleteLogin" | i18n }} -

- - -

{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}

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

{{ "noTwoStepProviders" | i18n }}

-

{{ "noTwoStepProviders2" | i18n }}

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

- -

-
-
-
diff --git a/apps/browser/src/auth/popup/two-factor-v1.component.ts b/apps/browser/src/auth/popup/two-factor-v1.component.ts deleted file mode 100644 index 884e42bf73a..00000000000 --- a/apps/browser/src/auth/popup/two-factor-v1.component.ts +++ /dev/null @@ -1,260 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Inject, OnDestroy, OnInit } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, Subscription, firstValueFrom } from "rxjs"; -import { filter, first, takeUntil } from "rxjs/operators"; - -import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -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/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { BrowserApi } from "../../platform/browser/browser-api"; -import { ZonedMessageListenerService } from "../../platform/browser/zoned-message-listener.service"; -import BrowserPopupUtils from "../../platform/popup/browser-popup-utils"; - -import { closeTwoFactorAuthWebAuthnPopout } from "./utils/auth-popout-window"; - -@Component({ - selector: "app-two-factor", - templateUrl: "two-factor-v1.component.html", -}) -export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy { - private destroy$ = new Subject(); - inPopout = BrowserPopupUtils.inPopout(window); - - constructor( - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - apiService: ApiService, - platformUtilsService: PlatformUtilsService, - private syncService: SyncService, - environmentService: EnvironmentService, - stateService: StateService, - route: ActivatedRoute, - private messagingService: MessagingService, - logService: LogService, - twoFactorService: TwoFactorService, - appIdService: AppIdService, - loginEmailService: LoginEmailServiceAbstraction, - userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - configService: ConfigService, - ssoLoginService: SsoLoginServiceAbstraction, - private dialogService: DialogService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - toastService: ToastService, - @Inject(WINDOW) protected win: Window, - private browserMessagingApi: ZonedMessageListenerService, - ) { - super( - loginStrategyService, - router, - i18nService, - apiService, - platformUtilsService, - win, - environmentService, - stateService, - route, - logService, - twoFactorService, - appIdService, - loginEmailService, - userDecryptionOptionsService, - ssoLoginService, - configService, - masterPasswordService, - accountService, - toastService, - ); - this.onSuccessfulLogin = async () => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - syncService.fullSync(true); - }; - - this.onSuccessfulLoginTde = async () => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - syncService.fullSync(true); - }; - - this.onSuccessfulLoginTdeNavigate = async () => { - this.win.close(); - }; - - this.successRoute = "/tabs/vault"; - // FIXME: Chromium 110 has broken WebAuthn support in extensions via an iframe - this.webAuthnNewTab = true; - } - - async ngOnInit() { - if (this.route.snapshot.paramMap.has("webAuthnResponse")) { - // WebAuthn fallback response - this.selectedProviderType = TwoFactorProviderType.WebAuthn; - this.token = this.route.snapshot.paramMap.get("webAuthnResponse"); - this.onSuccessfulLogin = async () => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.syncService.fullSync(true); - this.messagingService.send("reloadPopup"); - window.close(); - }; - this.remember = this.route.snapshot.paramMap.get("remember") === "true"; - await this.doSubmit(); - return; - } - - await super.ngOnInit(); - if (this.selectedProviderType == null) { - return; - } - - // WebAuthn prompt appears inside the popup on linux, and requires a larger popup width - // than usual to avoid cutting off the dialog. - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) { - document.body.classList.add("linux-webauthn"); - } - - if ( - this.selectedProviderType === TwoFactorProviderType.Email && - BrowserPopupUtils.inPopup(window) - ) { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "warning" }, - content: { key: "popup2faCloseMessage" }, - type: "warning", - }); - if (confirmed) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - BrowserPopupUtils.openCurrentPagePopout(window); - } - } - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - if (qParams.sso === "true") { - this.onSuccessfulLogin = async () => { - // This is not awaited so we don't pause the application while the sync is happening. - // This call is executed by the service that lives in the background script so it will continue - // the sync even if this tab closes. - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.syncService.fullSync(true); - - // Force sidebars (FF && Opera) to reload while exempting current window - // because we are just going to close the current window. - 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) - await closeTwoFactorAuthWebAuthnPopout(); - }; - } - }); - } - - async ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) { - document.body.classList.remove("linux-webauthn"); - } - super.ngOnDestroy(); - } - - anotherMethod() { - const sso = this.route.snapshot.queryParamMap.get("sso") === "true"; - - if (sso) { - // We must persist this so when the user returns to the 2FA comp, the - // proper onSuccessfulLogin logic is executed. - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["2fa-options"], { queryParams: { sso: true } }); - } else { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["2fa-options"]); - } - } - - async popoutCurrentPage() { - await BrowserPopupUtils.openCurrentPagePopout(window); - } - - async isLinux() { - return (await BrowserApi.getPlatformInfo()).os === "linux"; - } - - duoResultSubscription: Subscription; - protected override setupDuoResultListener() { - if (!this.duoResultSubscription) { - this.duoResultSubscription = this.browserMessagingApi - .messageListener$() - .pipe( - filter((msg: any) => msg.command === "duoResult"), - takeUntil(this.destroy$), - ) - .subscribe((msg: { command: string; code: string; state: string }) => { - this.token = msg.code + "|" + msg.state; - // This floating promise is intentional. We don't need to await the submit + awaiting in a subscription is not recommended. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.submit(); - }); - } - } - - override async launchDuoFrameless() { - if (this.duoFramelessUrl === null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), - }); - return; - } - - const duoHandOffMessage = { - title: this.i18nService.t("youSuccessfullyLoggedIn"), - message: this.i18nService.t("youMayCloseThisWindow"), - isCountdown: false, - }; - - // we're using the connector here as a way to set a cookie with translations - // before continuing to the duo frameless url - const env = await firstValueFrom(this.environmentService.environment$); - const launchUrl = - env.getWebVaultUrl() + - "/duo-redirect-connector.html" + - "?duoFramelessUrl=" + - encodeURIComponent(this.duoFramelessUrl) + - "&handOffMessage=" + - encodeURIComponent(JSON.stringify(duoHandOffMessage)); - this.platformUtilsService.launchUri(launchUrl); - } -} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index e73f56fa2f6..9948b450e17 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -7,7 +7,6 @@ import { EnvironmentSelectorRouteData, ExtensionDefaultOverlayPosition, } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { activeAuthGuard, authGuard, @@ -59,8 +58,6 @@ import { import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; -import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component"; -import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { Fido2Component } from "../autofill/popup/fido2/fido2.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; @@ -142,32 +139,6 @@ const routes: Routes = [ canActivate: [fido2AuthGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...unauthUiRefreshSwap( - TwoFactorComponentV1, - ExtensionAnonLayoutWrapperComponent, - { - path: "2fa", - canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { elevation: 1 } satisfies RouteDataProperties, - }, - { - path: "2fa", - canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard], - children: [ - { - path: "", - component: TwoFactorAuthComponent, - }, - ], - data: { - elevation: 1, - pageTitle: { - key: "verifyYourIdentity", - }, - showBackButton: true, - } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, - }, - ), { path: "", component: ExtensionAnonLayoutWrapperComponent, @@ -191,12 +162,6 @@ const routes: Routes = [ }, ], }, - { - path: "2fa-options", - component: TwoFactorOptionsComponentV1, - canActivate: [unauthGuardFn(unauthRouteOverrides)], - data: { elevation: 1 } satisfies RouteDataProperties, - }, { path: "device-verification", component: ExtensionAnonLayoutWrapperComponent, @@ -371,7 +336,6 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, }, - { path: "", component: ExtensionAnonLayoutWrapperComponent, @@ -567,6 +531,23 @@ const routes: Routes = [ }, ], }, + { + path: "2fa", + canActivate: [unauthGuardFn(unauthRouteOverrides), TwoFactorAuthGuard], + children: [ + { + path: "", + component: TwoFactorAuthComponent, + }, + ], + data: { + elevation: 1, + pageTitle: { + key: "verifyYourIdentity", + }, + showBackButton: true, + } satisfies RouteDataProperties & ExtensionAnonLayoutWrapperData, + }, ], }, { diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 80cffa03b17..b2542679e06 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -25,8 +25,6 @@ import { SetPasswordComponent } from "../auth/popup/set-password.component"; import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { VaultTimeoutInputComponent } from "../auth/popup/settings/vault-timeout-input.component"; import { SsoComponentV1 } from "../auth/popup/sso-v1.component"; -import { TwoFactorOptionsComponentV1 } from "../auth/popup/two-factor-options-v1.component"; -import { TwoFactorComponentV1 } from "../auth/popup/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; @@ -93,8 +91,6 @@ import "../platform/popup/locales"; SetPasswordComponent, SsoComponentV1, TabsV2Component, - TwoFactorComponentV1, - TwoFactorOptionsComponentV1, UpdateTempPasswordComponent, UserVerificationComponent, VaultTimeoutInputComponent, diff --git a/apps/browser/src/popup/scss/misc.scss b/apps/browser/src/popup/scss/misc.scss index d1308d26180..8aace90d0a6 100644 --- a/apps/browser/src/popup/scss/misc.scss +++ b/apps/browser/src/popup/scss/misc.scss @@ -103,16 +103,6 @@ p.lead { margin: 0 !important; } -.no-vmargin { - margin-top: 0 !important; - margin-bottom: 0 !important; -} - -.no-vpad { - padding-top: 0 !important; - padding-bottom: 0 !important; -} - .display-block { display: block !important; } diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index 3bb130d321d..cd5064a87e4 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -6,7 +6,6 @@ import { DesktopDefaultOverlayPosition, EnvironmentSelectorComponent, } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { authGuard, lockGuard, @@ -53,7 +52,6 @@ import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.compo import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.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"; @@ -76,28 +74,6 @@ const routes: Routes = [ children: [], // Children lets us have an empty component. canActivate: [redirectGuard({ loggedIn: "/vault", loggedOut: "/login", locked: "/lock" })], }, - ...unauthUiRefreshSwap( - TwoFactorComponentV1, - AnonLayoutWrapperComponent, - { - path: "2fa", - }, - { - path: "2fa", - canActivate: [unauthGuardFn(), TwoFactorAuthGuard], - children: [ - { - path: "", - component: TwoFactorAuthComponent, - }, - ], - data: { - pageTitle: { - key: "verifyYourIdentity", - }, - } satisfies RouteDataProperties & AnonLayoutWrapperData, - }, - ), { path: "authentication-timeout", component: AnonLayoutWrapperComponent, @@ -360,6 +336,21 @@ const routes: Routes = [ }, } satisfies AnonLayoutWrapperData, }, + { + path: "2fa", + canActivate: [unauthGuardFn(), TwoFactorAuthGuard], + children: [ + { + path: "", + component: TwoFactorAuthComponent, + }, + ], + data: { + pageTitle: { + key: "verifyYourIdentity", + }, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, ], }, ]; diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index b1b2864af5a..b717afe4a41 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -16,8 +16,6 @@ import { LoginModule } from "../auth/login/login.module"; import { RemovePasswordComponent } from "../auth/remove-password.component"; import { SetPasswordComponent } from "../auth/set-password.component"; import { SsoComponentV1 } from "../auth/sso-v1.component"; -import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.component"; -import { TwoFactorComponentV1 } from "../auth/two-factor-v1.component"; import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component"; import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; @@ -78,9 +76,7 @@ import { SharedModule } from "./shared/shared.module"; SetPasswordComponent, SettingsComponent, ShareComponent, - TwoFactorComponentV1, SsoComponentV1, - TwoFactorOptionsComponentV1, UpdateTempPasswordComponent, VaultComponent, VaultTimeoutInputComponent, diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.html b/apps/desktop/src/auth/two-factor-options-v1.component.html deleted file mode 100644 index 6f87c666215..00000000000 --- a/apps/desktop/src/auth/two-factor-options-v1.component.html +++ /dev/null @@ -1,33 +0,0 @@ - diff --git a/apps/desktop/src/auth/two-factor-options-v1.component.ts b/apps/desktop/src/auth/two-factor-options-v1.component.ts deleted file mode 100644 index 1cb440a5f5f..00000000000 --- a/apps/desktop/src/auth/two-factor-options-v1.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; - -import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; - -@Component({ - selector: "app-two-factor-options", - templateUrl: "two-factor-options-v1.component.html", -}) -export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 { - constructor( - twoFactorService: TwoFactorService, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - ) { - super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService); - } -} diff --git a/apps/desktop/src/auth/two-factor-v1.component.html b/apps/desktop/src/auth/two-factor-v1.component.html deleted file mode 100644 index 1f65d5070f6..00000000000 --- a/apps/desktop/src/auth/two-factor-v1.component.html +++ /dev/null @@ -1,175 +0,0 @@ -
-
-
- Bitwarden -

{{ title }}

- -
- - -

- {{ "enterVerificationCodeApp" | i18n }} -

-

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

-
-
- - -
-
- - - {{ "sendVerificationCodeEmailAgain" | i18n }} - - -
- - - -

{{ "insertYubiKey" | i18n }}

- - - - - -
-
-
- - -
-
-
-
- - - -
-
- -
-
-
- - - -
- - {{ "duoRequiredByOrgForAccount" | i18n }} - - {{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }} -
-
-
- -
-
-
-

{{ "noTwoStepProviders" | i18n }}

-

{{ "noTwoStepProviders2" | i18n }}

-
-
-
- -
-
-
- - -
-
-
- - -
- -
- - -
-
- -
-
- -
-
- -
-
-
- -
-
-
-
- diff --git a/apps/desktop/src/auth/two-factor-v1.component.ts b/apps/desktop/src/auth/two-factor-v1.component.ts deleted file mode 100644 index 13c7d0a452b..00000000000 --- a/apps/desktop/src/auth/two-factor-v1.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Inject, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -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 { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.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"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ToastService } from "@bitwarden/components"; - -import { TwoFactorOptionsComponentV1 } from "./two-factor-options-v1.component"; - -const BroadcasterSubscriptionId = "TwoFactorComponent"; - -@Component({ - selector: "app-two-factor", - templateUrl: "two-factor-v1.component.html", -}) -export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnDestroy { - @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) - twoFactorOptionsModal: ViewContainerRef; - - showingModal = false; - duoCallbackSubscriptionEnabled: boolean = false; - - constructor( - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - apiService: ApiService, - platformUtilsService: PlatformUtilsService, - syncService: SyncService, - environmentService: EnvironmentService, - private broadcasterService: BroadcasterService, - private modalService: ModalService, - stateService: StateService, - private ngZone: NgZone, - route: ActivatedRoute, - logService: LogService, - twoFactorService: TwoFactorService, - appIdService: AppIdService, - loginEmailService: LoginEmailServiceAbstraction, - userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - configService: ConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - toastService: ToastService, - @Inject(WINDOW) protected win: Window, - ) { - super( - loginStrategyService, - router, - i18nService, - apiService, - platformUtilsService, - win, - environmentService, - stateService, - route, - logService, - twoFactorService, - appIdService, - loginEmailService, - userDecryptionOptionsService, - ssoLoginService, - configService, - masterPasswordService, - accountService, - toastService, - ); - this.onSuccessfulLogin = async () => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - syncService.fullSync(true); - }; - - this.onSuccessfulLoginTde = async () => { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - syncService.fullSync(true); - }; - } - - async anotherMethod() { - const [modal, childComponent] = await this.modalService.openViewRef( - TwoFactorOptionsComponentV1, - this.twoFactorOptionsModal, - ); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - modal.onShown.subscribe(() => { - this.showingModal = true; - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - modal.onClosed.subscribe(() => { - this.showingModal = false; - }); - - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - childComponent.onProviderSelected.subscribe(async (provider: TwoFactorProviderType) => { - modal.close(); - this.selectedProviderType = provider; - await this.init(); - }); - // eslint-disable-next-line rxjs-angular/prefer-takeuntil - childComponent.onRecoverSelected.subscribe(() => { - modal.close(); - }); - } - - async submit() { - await super.submit(); - if (this.captchaSiteKey) { - const content = document.getElementById("content") as HTMLDivElement; - content.setAttribute("style", "width:335px"); - } - } - - protected override setupDuoResultListener() { - if (!this.duoCallbackSubscriptionEnabled) { - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - await this.ngZone.run(async () => { - if (message.command === "duoCallback") { - this.token = message.code + "|" + message.state; - await this.submit(); - } - }); - }); - this.duoCallbackSubscriptionEnabled = true; - } - } - - override async launchDuoFrameless() { - if (this.duoFramelessUrl === null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), - }); - return; - } - - const duoHandOffMessage = { - title: this.i18nService.t("youSuccessfullyLoggedIn"), - message: this.i18nService.t("youMayCloseThisWindow"), - isCountdown: false, - }; - - // we're using the connector here as a way to set a cookie with translations - // before continuing to the duo frameless url - const env = await firstValueFrom(this.environmentService.environment$); - const launchUrl = - env.getWebVaultUrl() + - "/duo-redirect-connector.html" + - "?duoFramelessUrl=" + - encodeURIComponent(this.duoFramelessUrl) + - "&handOffMessage=" + - encodeURIComponent(JSON.stringify(duoHandOffMessage)); - this.platformUtilsService.launchUri(launchUrl); - } - - ngOnDestroy(): void { - if (this.duoCallbackSubscriptionEnabled) { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.duoCallbackSubscriptionEnabled = false; - } - } -} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 07404b37fcd..42eebd98223 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -833,18 +833,6 @@ "continue": { "message": "Continue" }, - "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." - }, - "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", - "placeholders": { - "email": { - "content": "$1", - "example": "example@gmail.com" - } - } - }, "verificationCodeEmailSent": { "message": "Verification email sent to $EMAIL$.", "placeholders": { @@ -854,18 +842,9 @@ } } }, - "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", "description": "Select another two-step login method" @@ -873,9 +852,6 @@ "useYourRecoveryCode": { "message": "Use your recovery code" }, - "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." - }, "insertU2f": { "message": "Insert your security key into your computer's USB port. If it has a button, touch it." }, @@ -3224,9 +3200,6 @@ "duoHealthCheckResultsInNullAuthUrlError": { "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, - "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." - }, "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, diff --git a/apps/desktop/src/scss/pages.scss b/apps/desktop/src/scss/pages.scss index b9559e13a26..ecb36aae662 100644 --- a/apps/desktop/src/scss/pages.scss +++ b/apps/desktop/src/scss/pages.scss @@ -27,7 +27,6 @@ #accessibility-cookie-page, #register-page, #hint-page, -#two-factor-page, #update-temp-password-page, #remove-password-page { padding-top: 20px; @@ -48,7 +47,6 @@ #accessibility-cookie-page, #register-page, #hint-page, -#two-factor-page, #lock-page, #update-temp-password-page { .content { diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.html b/apps/web/src/app/auth/two-factor-options-v1.component.html deleted file mode 100644 index 43c054060ea..00000000000 --- a/apps/web/src/app/auth/two-factor-options-v1.component.html +++ /dev/null @@ -1,45 +0,0 @@ - - - {{ "twoStepOptions" | i18n }} - - -
-
-
- -
-
-

{{ p.name }}

-

{{ p.description }}

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

{{ "recoveryCodeTitle" | i18n }}

-

{{ "recoveryCodeDesc" | i18n }}

-
-
- -
-
-
-
- - - -
diff --git a/apps/web/src/app/auth/two-factor-options-v1.component.ts b/apps/web/src/app/auth/two-factor-options-v1.component.ts deleted file mode 100644 index 08665dcfcdd..00000000000 --- a/apps/web/src/app/auth/two-factor-options-v1.component.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { DialogRef } from "@angular/cdk/dialog"; -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; - -import { TwoFactorOptionsComponentV1 as BaseTwoFactorOptionsComponentV1 } from "@bitwarden/angular/auth/components/two-factor-options-v1.component"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -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"; -import { DialogService } from "@bitwarden/components"; - -export enum TwoFactorOptionsDialogResult { - Provider = "Provider selected", - Recover = "Recover selected", -} - -export type TwoFactorOptionsDialogResultType = { - result: TwoFactorOptionsDialogResult; - type: TwoFactorProviderType; -}; - -@Component({ - selector: "app-two-factor-options", - templateUrl: "two-factor-options-v1.component.html", -}) -export class TwoFactorOptionsComponentV1 extends BaseTwoFactorOptionsComponentV1 { - constructor( - twoFactorService: TwoFactorService, - router: Router, - i18nService: I18nService, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - private dialogRef: DialogRef, - ) { - super(twoFactorService, router, i18nService, platformUtilsService, window, environmentService); - } - - async choose(p: any) { - await super.choose(p); - this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Provider, type: p.type }); - } - - async recover() { - await super.recover(); - this.dialogRef.close({ result: TwoFactorOptionsDialogResult.Recover }); - } - - static open(dialogService: DialogService) { - return dialogService.open(TwoFactorOptionsComponentV1); - } -} diff --git a/apps/web/src/app/auth/two-factor-v1.component.html b/apps/web/src/app/auth/two-factor-v1.component.html deleted file mode 100644 index b78747e04c2..00000000000 --- a/apps/web/src/app/auth/two-factor-v1.component.html +++ /dev/null @@ -1,106 +0,0 @@ -
-
- -

- {{ "enterVerificationCodeApp" | i18n }} -

-

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

- - {{ "verificationCode" | i18n }} - - - - {{ "sendVerificationCodeEmailAgain" | i18n }} - - -
- -

{{ "insertYubiKey" | i18n }}

- - - - - - - {{ "verificationCode" | i18n }} - - -
- -
- -
-
- - -

- {{ "duoRequiredByOrgForAccount" | i18n }} -

-

{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}

-
- - {{ "rememberMe" | i18n }} - - - -

{{ "noTwoStepProviders" | i18n }}

-

{{ "noTwoStepProviders2" | i18n }}

-
-
-
- -
- -
- - - - {{ "cancel" | i18n }} - -
- -
-
diff --git a/apps/web/src/app/auth/two-factor-v1.component.ts b/apps/web/src/app/auth/two-factor-v1.component.ts deleted file mode 100644 index 9a9fab02de3..00000000000 --- a/apps/web/src/app/auth/two-factor-v1.component.ts +++ /dev/null @@ -1,164 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, takeUntil, lastValueFrom } from "rxjs"; - -import { TwoFactorComponentV1 as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor-v1.component"; -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { DialogService, ToastService } from "@bitwarden/components"; - -import { - TwoFactorOptionsDialogResult, - TwoFactorOptionsComponentV1, - TwoFactorOptionsDialogResultType, -} from "./two-factor-options-v1.component"; - -@Component({ - selector: "app-two-factor", - templateUrl: "two-factor-v1.component.html", -}) -export class TwoFactorComponentV1 extends BaseTwoFactorComponent implements OnInit, OnDestroy { - @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) - twoFactorOptionsModal: ViewContainerRef; - formGroup = this.formBuilder.group({ - token: [ - "", - { - validators: [Validators.required], - updateOn: "submit", - }, - ], - remember: [false], - }); - private destroy$ = new Subject(); - constructor( - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - apiService: ApiService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - environmentService: EnvironmentService, - private dialogService: DialogService, - route: ActivatedRoute, - logService: LogService, - twoFactorService: TwoFactorService, - appIdService: AppIdService, - loginEmailService: LoginEmailServiceAbstraction, - userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - configService: ConfigService, - masterPasswordService: InternalMasterPasswordServiceAbstraction, - accountService: AccountService, - toastService: ToastService, - private formBuilder: FormBuilder, - @Inject(WINDOW) protected win: Window, - ) { - super( - loginStrategyService, - router, - i18nService, - apiService, - platformUtilsService, - win, - environmentService, - stateService, - route, - logService, - twoFactorService, - appIdService, - loginEmailService, - userDecryptionOptionsService, - ssoLoginService, - configService, - masterPasswordService, - accountService, - toastService, - ); - this.onSuccessfulLoginNavigate = this.goAfterLogIn; - } - async ngOnInit() { - await super.ngOnInit(); - this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => { - this.token = value.token; - this.remember = value.remember; - }); - } - submitForm = async () => { - await this.submit(); - }; - - async anotherMethod() { - const dialogRef = TwoFactorOptionsComponentV1.open(this.dialogService); - const response: TwoFactorOptionsDialogResultType = await lastValueFrom(dialogRef.closed); - if (response.result === TwoFactorOptionsDialogResult.Provider) { - this.selectedProviderType = response.type; - await this.init(); - } - } - - protected override handleMigrateEncryptionKey(result: AuthResult): boolean { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate(["migrate-legacy-encryption"]); - return true; - } - - goAfterLogIn = async () => { - this.loginEmailService.clearValues(); - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.successRoute], { - queryParams: { - identifier: this.orgIdentifier, - }, - }); - }; - - private duoResultChannel: BroadcastChannel; - - protected override setupDuoResultListener() { - if (!this.duoResultChannel) { - this.duoResultChannel = new BroadcastChannel("duoResult"); - this.duoResultChannel.addEventListener("message", this.handleDuoResultMessage); - } - } - - private handleDuoResultMessage = async (msg: { data: { code: string; state: string } }) => { - this.token = msg.data.code + "|" + msg.data.state; - await this.submit(); - }; - - async ngOnDestroy() { - super.ngOnDestroy(); - - if (this.duoResultChannel) { - // clean up duo listener if it was initialized. - this.duoResultChannel.removeEventListener("message", this.handleDuoResultMessage); - this.duoResultChannel.close(); - } - } -} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index c531f358b34..0334519516a 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -2,7 +2,6 @@ import { NgModule } from "@angular/core"; import { Route, RouterModule, Routes } from "@angular/router"; import { AuthenticationTimeoutComponent } from "@bitwarden/angular/auth/components/authentication-timeout.component"; -import { unauthUiRefreshSwap } from "@bitwarden/angular/auth/functions/unauth-ui-refresh-route-swap"; import { authGuard, lockGuard, @@ -65,7 +64,6 @@ import { AccountComponent } from "./auth/settings/account/account.component"; import { EmergencyAccessComponent } from "./auth/settings/emergency-access/emergency-access.component"; import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/view/emergency-access-view.component"; import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module"; -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"; @@ -378,51 +376,28 @@ const routes: Routes = [ }, ], }, - ...unauthUiRefreshSwap( - TwoFactorComponentV1, - TwoFactorAuthComponent, - { - path: "2fa", - canActivate: [unauthGuardFn()], - children: [ - { - path: "", - component: TwoFactorComponentV1, - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - data: { - pageTitle: { - key: "verifyYourIdentity", - }, - } satisfies RouteDataProperties & AnonLayoutWrapperData, - }, - { - path: "2fa", - canActivate: [unauthGuardFn(), TwoFactorAuthGuard], - children: [ - { - path: "", - component: TwoFactorAuthComponent, - }, - { - path: "", - component: EnvironmentSelectorComponent, - outlet: "environment-selector", - }, - ], - data: { - pageTitle: { - key: "verifyYourIdentity", - }, - titleAreaMaxWidth: "md", - } satisfies RouteDataProperties & AnonLayoutWrapperData, - }, - ), + { + path: "2fa", + component: TwoFactorAuthComponent, + canActivate: [unauthGuardFn(), TwoFactorAuthGuard], + children: [ + { + path: "", + component: TwoFactorAuthComponent, + }, + { + path: "", + component: EnvironmentSelectorComponent, + outlet: "environment-selector", + }, + ], + data: { + pageTitle: { + key: "verifyYourIdentity", + }, + titleAreaMaxWidth: "md", + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, { path: "lock", canActivate: [deepLinkGuard(), lockGuard()], diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index f21a9338491..70dbf63f1f8 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -43,8 +43,6 @@ import { TwoFactorSetupComponent } from "../auth/settings/two-factor/two-factor- import { TwoFactorVerifyComponent } from "../auth/settings/two-factor/two-factor-verify.component"; import { UserVerificationModule } from "../auth/shared/components/user-verification"; import { SsoComponentV1 } from "../auth/sso-v1.component"; -import { TwoFactorOptionsComponentV1 } from "../auth/two-factor-options-v1.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"; @@ -148,12 +146,10 @@ import { SharedModule } from "./shared.module"; SetPasswordComponent, SponsoredFamiliesComponent, SponsoringOrgRowComponent, - TwoFactorComponentV1, SsoComponentV1, TwoFactorSetupAuthenticatorComponent, TwoFactorSetupDuoComponent, TwoFactorSetupEmailComponent, - TwoFactorOptionsComponentV1, TwoFactorRecoveryComponent, TwoFactorSetupComponent, TwoFactorVerifyComponent, @@ -210,12 +206,10 @@ import { SharedModule } from "./shared.module"; SetPasswordComponent, SponsoredFamiliesComponent, SponsoringOrgRowComponent, - TwoFactorComponentV1, SsoComponentV1, TwoFactorSetupAuthenticatorComponent, TwoFactorSetupDuoComponent, TwoFactorSetupEmailComponent, - TwoFactorOptionsComponentV1, TwoFactorSetupComponent, TwoFactorVerifyComponent, TwoFactorSetupWebAuthnComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index b9ead1df44d..4133062a047 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1456,18 +1456,6 @@ } } }, - "enterVerificationCodeApp": { - "message": "Enter the 6 digit verification code from your authenticator app." - }, - "enterVerificationCodeEmail": { - "message": "Enter the 6 digit verification code that was emailed to $EMAIL$.", - "placeholders": { - "email": { - "content": "$1", - "example": "example@gmail.com" - } - } - }, "verificationCodeEmailSent": { "message": "Verification email sent to $EMAIL$.", "placeholders": { @@ -1477,18 +1465,10 @@ } } }, - "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", "description": "Select another two-step login method" @@ -1496,9 +1476,6 @@ "useYourRecoveryCode": { "message": "Use your recovery code" }, - "insertYubiKey": { - "message": "Insert your YubiKey into your computer's USB port, then touch its button." - }, "insertU2f": { "message": "Insert your security key into your computer's USB port. If it has a button, touch it." }, @@ -7273,9 +7250,6 @@ "duoHealthCheckResultsInNullAuthUrlError": { "message": "Error connecting with the Duo service. Use a different two-step login method or contact Duo for assistance." }, - "launchDuoAndFollowStepsToFinishLoggingIn": { - "message": "Launch Duo and follow the steps to finish logging in." - }, "duoRequiredByOrgForAccount": { "message": "Duo two-step login is required for your account." }, diff --git a/libs/angular/src/auth/components/two-factor-options-v1.component.ts b/libs/angular/src/auth/components/two-factor-options-v1.component.ts deleted file mode 100644 index f02eabcc156..00000000000 --- a/libs/angular/src/auth/components/two-factor-options-v1.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Directive, EventEmitter, OnInit, Output } from "@angular/core"; -import { Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { - TwoFactorProviderDetails, - TwoFactorService, -} from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -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"; - -@Directive() -export class TwoFactorOptionsComponentV1 implements OnInit { - @Output() onProviderSelected = new EventEmitter(); - @Output() onRecoverSelected = new EventEmitter(); - - providers: any[] = []; - - constructor( - protected twoFactorService: TwoFactorService, - protected router: Router, - protected i18nService: I18nService, - protected platformUtilsService: PlatformUtilsService, - protected win: Window, - protected environmentService: EnvironmentService, - ) {} - - async ngOnInit() { - this.providers = await this.twoFactorService.getSupportedProviders(this.win); - } - - async choose(p: TwoFactorProviderDetails) { - this.onProviderSelected.emit(p.type); - } - - async recover() { - const env = await firstValueFrom(this.environmentService.environment$); - const webVault = env.getWebVaultUrl(); - this.platformUtilsService.launchUri(webVault + "/#/recover-2fa"); - this.onRecoverSelected.emit(); - } -} diff --git a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts b/libs/angular/src/auth/components/two-factor-v1.component.spec.ts deleted file mode 100644 index 47075acc758..00000000000 --- a/libs/angular/src/auth/components/two-factor-v1.component.spec.ts +++ /dev/null @@ -1,505 +0,0 @@ -import { Component } from "@angular/core"; -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { ActivatedRoute, convertToParamMap, Router } from "@angular/router"; -import { mock, MockProxy } from "jest-mock-extended"; -import { BehaviorSubject } from "rxjs"; - -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption, - FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption, - FakeUserDecryptionOptions as UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -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 { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/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 { 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 { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { FakeAccountService, mockAccountServiceWith } from "@bitwarden/common/spec"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; - -import { TwoFactorComponentV1 } from "./two-factor-v1.component"; - -// test component that extends the TwoFactorComponent -@Component({}) -class TestTwoFactorComponent extends TwoFactorComponentV1 {} - -interface TwoFactorComponentProtected { - trustedDeviceEncRoute: string; - changePasswordRoute: string; - forcePasswordResetRoute: string; - successRoute: string; -} - -describe("TwoFactorComponent", () => { - let component: TestTwoFactorComponent; - let _component: TwoFactorComponentProtected; - - let fixture: ComponentFixture; - const userId = "userId" as UserId; - - // Mock Services - let mockLoginStrategyService: MockProxy; - let mockRouter: MockProxy; - let mockI18nService: MockProxy; - let mockApiService: MockProxy; - let mockPlatformUtilsService: MockProxy; - let mockWin: MockProxy; - let mockEnvironmentService: MockProxy; - let mockStateService: MockProxy; - let mockLogService: MockProxy; - let mockTwoFactorService: MockProxy; - let mockAppIdService: MockProxy; - let mockLoginEmailService: MockProxy; - let mockUserDecryptionOptionsService: MockProxy; - let mockSsoLoginService: MockProxy; - let mockConfigService: MockProxy; - let mockMasterPasswordService: FakeMasterPasswordService; - let mockAccountService: FakeAccountService; - let mockToastService: MockProxy; - - let mockUserDecryptionOpts: { - noMasterPassword: UserDecryptionOptions; - withMasterPassword: UserDecryptionOptions; - withMasterPasswordAndTrustedDevice: UserDecryptionOptions; - withMasterPasswordAndTrustedDeviceWithManageResetPassword: UserDecryptionOptions; - withMasterPasswordAndKeyConnector: UserDecryptionOptions; - noMasterPasswordWithTrustedDevice: UserDecryptionOptions; - noMasterPasswordWithTrustedDeviceWithManageResetPassword: UserDecryptionOptions; - noMasterPasswordWithKeyConnector: UserDecryptionOptions; - }; - - let selectedUserDecryptionOptions: BehaviorSubject; - let authenticationSessionTimeoutSubject: BehaviorSubject; - - beforeEach(() => { - authenticationSessionTimeoutSubject = new BehaviorSubject(false); - mockLoginStrategyService = mock(); - mockLoginStrategyService.authenticationSessionTimeout$ = authenticationSessionTimeoutSubject; - mockRouter = mock(); - mockI18nService = mock(); - mockApiService = mock(); - mockPlatformUtilsService = mock(); - mockWin = mock(); - mockEnvironmentService = mock(); - mockStateService = mock(); - mockLogService = mock(); - mockTwoFactorService = mock(); - mockAppIdService = mock(); - mockLoginEmailService = mock(); - mockUserDecryptionOptionsService = mock(); - mockSsoLoginService = mock(); - mockConfigService = mock(); - mockAccountService = mockAccountServiceWith(userId); - mockToastService = mock(); - mockMasterPasswordService = new FakeMasterPasswordService(); - - mockUserDecryptionOpts = { - noMasterPassword: new UserDecryptionOptions({ - hasMasterPassword: false, - trustedDeviceOption: undefined, - keyConnectorOption: undefined, - }), - withMasterPassword: new UserDecryptionOptions({ - hasMasterPassword: true, - trustedDeviceOption: undefined, - keyConnectorOption: undefined, - }), - withMasterPasswordAndTrustedDevice: new UserDecryptionOptions({ - hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), - keyConnectorOption: undefined, - }), - withMasterPasswordAndTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ - hasMasterPassword: true, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), - keyConnectorOption: undefined, - }), - withMasterPasswordAndKeyConnector: new UserDecryptionOptions({ - hasMasterPassword: true, - trustedDeviceOption: undefined, - keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"), - }), - noMasterPasswordWithTrustedDevice: new UserDecryptionOptions({ - hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, false, false), - keyConnectorOption: undefined, - }), - noMasterPasswordWithTrustedDeviceWithManageResetPassword: new UserDecryptionOptions({ - hasMasterPassword: false, - trustedDeviceOption: new TrustedDeviceUserDecryptionOption(true, false, true, false), - keyConnectorOption: undefined, - }), - noMasterPasswordWithKeyConnector: new UserDecryptionOptions({ - hasMasterPassword: false, - trustedDeviceOption: undefined, - keyConnectorOption: new KeyConnectorUserDecryptionOption("http://example.com"), - }), - }; - - selectedUserDecryptionOptions = new BehaviorSubject( - mockUserDecryptionOpts.withMasterPassword, - ); - mockUserDecryptionOptionsService.userDecryptionOptions$ = selectedUserDecryptionOptions; - - TestBed.configureTestingModule({ - declarations: [TestTwoFactorComponent], - providers: [ - { provide: LoginStrategyServiceAbstraction, useValue: mockLoginStrategyService }, - { provide: Router, useValue: mockRouter }, - { provide: I18nService, useValue: mockI18nService }, - { provide: ApiService, useValue: mockApiService }, - { provide: PlatformUtilsService, useValue: mockPlatformUtilsService }, - { provide: WINDOW, useValue: mockWin }, - { provide: EnvironmentService, useValue: mockEnvironmentService }, - { provide: StateService, useValue: mockStateService }, - { - provide: ActivatedRoute, - useValue: { - snapshot: { - // Default to standard 2FA flow - not SSO + 2FA - queryParamMap: convertToParamMap({ sso: "false" }), - }, - }, - }, - { provide: LogService, useValue: mockLogService }, - { provide: TwoFactorService, useValue: mockTwoFactorService }, - { provide: AppIdService, useValue: mockAppIdService }, - { provide: LoginEmailServiceAbstraction, useValue: mockLoginEmailService }, - { - provide: UserDecryptionOptionsServiceAbstraction, - useValue: mockUserDecryptionOptionsService, - }, - { provide: SsoLoginServiceAbstraction, useValue: mockSsoLoginService }, - { provide: ConfigService, useValue: mockConfigService }, - { provide: InternalMasterPasswordServiceAbstraction, useValue: mockMasterPasswordService }, - { provide: AccountService, useValue: mockAccountService }, - { provide: ToastService, useValue: mockToastService }, - ], - }); - - fixture = TestBed.createComponent(TestTwoFactorComponent); - component = fixture.componentInstance; - _component = component as any; - }); - - afterEach(() => { - // Reset all mocks after each test - jest.resetAllMocks(); - }); - - it("should create", () => { - expect(component).toBeTruthy(); - }); - - // Shared tests - const testChangePasswordOnSuccessfulLogin = () => { - it("navigates to the component's defined change password route when user doesn't have a MP and key connector isn't enabled", async () => { - // Act - await component.doSubmit(); - - // Assert - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.changePasswordRoute], { - queryParams: { - identifier: component.orgIdentifier, - }, - }); - }); - }; - - const testForceResetOnSuccessfulLogin = (reasonString: string) => { - it(`navigates to the component's defined forcePasswordResetRoute route when response.forcePasswordReset is ${reasonString}`, async () => { - // Act - await component.doSubmit(); - - // expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.forcePasswordResetRoute], { - queryParams: { - identifier: component.orgIdentifier, - }, - }); - }); - }; - - describe("Standard 2FA scenarios", () => { - describe("doSubmit", () => { - const token = "testToken"; - const remember = false; - const captchaToken = "testCaptchaToken"; - - beforeEach(() => { - component.token = token; - component.remember = remember; - component.captchaToken = captchaToken; - - selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); - }); - - it("calls authService.logInTwoFactor with correct parameters when form is submitted", async () => { - // Arrange - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.doSubmit(); - - // Assert - expect(mockLoginStrategyService.logInTwoFactor).toHaveBeenCalledWith( - new TokenTwoFactorRequest(component.selectedProviderType, token, remember), - captchaToken, - ); - }); - - 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.doSubmit(); - - // Assert - expect(handleCaptchaRequiredSpy).toHaveBeenCalled(); - expect(result).toBeUndefined(); - }); - - it("calls onSuccessfulLogin when defined", async () => { - // Arrange - component.onSuccessfulLogin = jest.fn().mockResolvedValue(undefined); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.doSubmit(); - - // Assert - expect(component.onSuccessfulLogin).toHaveBeenCalled(); - }); - - it("calls loginEmailService.clearValues() when login is successful", async () => { - // Arrange - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - // spy on loginEmailService.clearValues - const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues"); - - // Act - await component.doSubmit(); - - // Assert - expect(clearValuesSpy).toHaveBeenCalled(); - }); - - describe("Set Master Password scenarios", () => { - beforeEach(() => { - const authResult = new AuthResult(); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - describe("Given user needs to set a master password", () => { - beforeEach(() => { - // Only need to test the case where the user has no master password to test the primary change mp flow here - selectedUserDecryptionOptions.next(mockUserDecryptionOpts.noMasterPassword); - }); - - testChangePasswordOnSuccessfulLogin(); - }); - - it("does not navigate to the change password route when the user has key connector even if user has no master password", async () => { - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.noMasterPasswordWithKeyConnector, - ); - - await component.doSubmit(); - - expect(mockRouter.navigate).not.toHaveBeenCalledWith([_component.changePasswordRoute], { - queryParams: { - identifier: component.orgIdentifier, - }, - }); - }); - }); - - describe("Force Master Password Reset scenarios", () => { - [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ].forEach((forceResetPasswordReason) => { - const reasonString = ForceSetPasswordReason[forceResetPasswordReason]; - - beforeEach(() => { - // use standard user with MP because this test is not concerned with password reset. - selectedUserDecryptionOptions.next(mockUserDecryptionOpts.withMasterPassword); - - const authResult = new AuthResult(); - authResult.forcePasswordReset = forceResetPasswordReason; - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - testForceResetOnSuccessfulLogin(reasonString); - }); - }); - - it("calls onSuccessfulLoginNavigate when the callback is defined", async () => { - // Arrange - component.onSuccessfulLoginNavigate = jest.fn().mockResolvedValue(undefined); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.doSubmit(); - - // Assert - expect(component.onSuccessfulLoginNavigate).toHaveBeenCalled(); - }); - - it("navigates to the component's defined success route when the login is successful and onSuccessfulLoginNavigate is undefined", async () => { - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); - - // Act - await component.doSubmit(); - - // Assert - expect(component.onSuccessfulLoginNavigate).not.toBeDefined(); - - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith([_component.successRoute], undefined); - }); - }); - }); - - describe("SSO > 2FA scenarios", () => { - beforeEach(() => { - const mockActivatedRoute = TestBed.inject(ActivatedRoute); - mockActivatedRoute.snapshot.queryParamMap.get = jest.fn().mockReturnValue("true"); - }); - - describe("doSubmit", () => { - const token = "testToken"; - const remember = false; - const captchaToken = "testCaptchaToken"; - - beforeEach(() => { - component.token = token; - component.remember = remember; - component.captchaToken = captchaToken; - }); - - describe("Trusted Device Encryption scenarios", () => { - beforeEach(() => { - mockConfigService.getFeatureFlag.mockResolvedValue(true); - }); - - describe("Given Trusted Device Encryption is enabled and user needs to set a master password", () => { - beforeEach(() => { - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.noMasterPasswordWithTrustedDeviceWithManageResetPassword, - ); - - const authResult = new AuthResult(); - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - it("navigates to the component's defined trusted device encryption route and sets correct flag when user doesn't have a MP and key connector isn't enabled", async () => { - // Act - await component.doSubmit(); - - // Assert - expect(mockMasterPasswordService.mock.setForceSetPasswordReason).toHaveBeenCalledWith( - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, - userId, - ); - - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith( - [_component.trustedDeviceEncRoute], - undefined, - ); - }); - }); - - describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is required", () => { - [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ].forEach((forceResetPasswordReason) => { - const reasonString = ForceSetPasswordReason[forceResetPasswordReason]; - - beforeEach(() => { - // use standard user with MP because this test is not concerned with password reset. - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice, - ); - - const authResult = new AuthResult(); - authResult.forcePasswordReset = forceResetPasswordReason; - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - testForceResetOnSuccessfulLogin(reasonString); - }); - }); - - describe("Given Trusted Device Encryption is enabled, user doesn't need to set a MP, and forcePasswordReset is not required", () => { - let authResult; - beforeEach(() => { - selectedUserDecryptionOptions.next( - mockUserDecryptionOpts.withMasterPasswordAndTrustedDevice, - ); - - authResult = new AuthResult(); - authResult.forcePasswordReset = ForceSetPasswordReason.None; - mockLoginStrategyService.logInTwoFactor.mockResolvedValue(authResult); - }); - - it("navigates to the component's defined trusted device encryption route when login is successful and onSuccessfulLoginTdeNavigate is undefined", async () => { - await component.doSubmit(); - - expect(mockRouter.navigate).toHaveBeenCalledTimes(1); - expect(mockRouter.navigate).toHaveBeenCalledWith( - [_component.trustedDeviceEncRoute], - undefined, - ); - }); - - it("calls onSuccessfulLoginTdeNavigate instead of router.navigate when the callback is defined", async () => { - component.onSuccessfulLoginTdeNavigate = jest.fn().mockResolvedValue(undefined); - - await component.doSubmit(); - - expect(mockRouter.navigate).not.toHaveBeenCalled(); - expect(component.onSuccessfulLoginTdeNavigate).toHaveBeenCalled(); - }); - }); - }); - }); - }); - - it("navigates to the timeout route when timeout expires", async () => { - authenticationSessionTimeoutSubject.next(true); - - expect(mockRouter.navigate).toHaveBeenCalledWith(["authentication-timeout"]); - }); -}); diff --git a/libs/angular/src/auth/components/two-factor-v1.component.ts b/libs/angular/src/auth/components/two-factor-v1.component.ts deleted file mode 100644 index 3fda2685f5e..00000000000 --- a/libs/angular/src/auth/components/two-factor-v1.component.ts +++ /dev/null @@ -1,514 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, Inject, OnInit, OnDestroy } from "@angular/core"; -import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; -import { ActivatedRoute, NavigationExtras, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; -import { first } from "rxjs/operators"; - -import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - TrustedDeviceUserDecryptionOption, - UserDecryptionOptions, - UserDecryptionOptionsServiceAbstraction, -} from "@bitwarden/auth/common"; -import { ApiService } from "@bitwarden/common/abstractions/api.service"; -import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; -import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; -import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; -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 { TwoFactorEmailRequest } from "@bitwarden/common/auth/models/request/two-factor-email.request"; -import { TwoFactorProviders } from "@bitwarden/common/auth/services/two-factor.service"; -import { WebAuthnIFrame } from "@bitwarden/common/auth/webauthn-iframe"; -import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { ToastService } from "@bitwarden/components"; - -import { CaptchaProtectedComponent } from "./captcha-protected.component"; - -@Directive() -export class TwoFactorComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy { - token = ""; - remember = false; - webAuthnReady = false; - webAuthnNewTab = false; - providers = TwoFactorProviders; - providerType = TwoFactorProviderType; - selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator; - webAuthnSupported = false; - webAuthn: WebAuthnIFrame = null; - title = ""; - twoFactorEmail: string = null; - formPromise: Promise; - emailPromise: Promise; - orgIdentifier: string = null; - - duoFramelessUrl: string = null; - duoResultListenerInitialized = false; - - onSuccessfulLogin: () => Promise; - onSuccessfulLoginNavigate: () => Promise; - - onSuccessfulLoginTde: () => Promise; - onSuccessfulLoginTdeNavigate: () => Promise; - - protected loginRoute = "login"; - - protected trustedDeviceEncRoute = "login-initiated"; - protected changePasswordRoute = "set-password"; - protected forcePasswordResetRoute = "update-temp-password"; - protected successRoute = "vault"; - protected twoFactorTimeoutRoute = "authentication-timeout"; - - get isDuoProvider(): boolean { - return ( - this.selectedProviderType === TwoFactorProviderType.Duo || - this.selectedProviderType === TwoFactorProviderType.OrganizationDuo - ); - } - - constructor( - protected loginStrategyService: LoginStrategyServiceAbstraction, - protected router: Router, - protected i18nService: I18nService, - protected apiService: ApiService, - protected platformUtilsService: PlatformUtilsService, - @Inject(WINDOW) protected win: Window, - protected environmentService: EnvironmentService, - protected stateService: StateService, - protected route: ActivatedRoute, - protected logService: LogService, - protected twoFactorService: TwoFactorService, - protected appIdService: AppIdService, - protected loginEmailService: LoginEmailServiceAbstraction, - protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected configService: ConfigService, - protected masterPasswordService: InternalMasterPasswordServiceAbstraction, - protected accountService: AccountService, - protected toastService: ToastService, - ) { - super(environmentService, i18nService, platformUtilsService, toastService); - - this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - - // Add subscription to authenticationSessionTimeout$ and navigate to twoFactorTimeoutRoute if expired - this.loginStrategyService.authenticationSessionTimeout$ - .pipe(takeUntilDestroyed()) - .subscribe(async (expired) => { - if (!expired) { - return; - } - - try { - await this.router.navigate([this.twoFactorTimeoutRoute]); - } catch (err) { - this.logService.error(`Failed to navigate to ${this.twoFactorTimeoutRoute} route`, err); - } - }); - } - - async ngOnInit() { - if (!(await this.authing()) || (await this.twoFactorService.getProviders()) == null) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.loginRoute]); - return; - } - - this.route.queryParams.pipe(first()).subscribe((qParams) => { - if (qParams.identifier != null) { - this.orgIdentifier = qParams.identifier; - } - }); - - if (await this.needsLock()) { - this.successRoute = "lock"; - } - - if (this.win != null && this.webAuthnSupported) { - const env = await firstValueFrom(this.environmentService.environment$); - const webVaultUrl = env.getWebVaultUrl(); - this.webAuthn = new WebAuthnIFrame( - this.win, - webVaultUrl, - this.webAuthnNewTab, - this.platformUtilsService, - this.i18nService, - (token: string) => { - this.token = token; - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.submit(); - }, - (error: string) => { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("webauthnCancelOrTimeout"), - }); - }, - (info: string) => { - if (info === "ready") { - this.webAuthnReady = true; - } - }, - ); - } - - this.selectedProviderType = await this.twoFactorService.getDefaultProvider( - this.webAuthnSupported, - ); - await this.init(); - } - - ngOnDestroy(): void { - this.cleanupWebAuthn(); - this.webAuthn = null; - } - - async init() { - if (this.selectedProviderType == null) { - this.title = this.i18nService.t("loginUnavailable"); - return; - } - - this.cleanupWebAuthn(); - this.title = (TwoFactorProviders as any)[this.selectedProviderType].name; - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(this.selectedProviderType); - }); - switch (this.selectedProviderType) { - case TwoFactorProviderType.WebAuthn: - if (!this.webAuthnNewTab) { - setTimeout(async () => { - await this.authWebAuthn(); - }, 500); - } - break; - case TwoFactorProviderType.Duo: - case TwoFactorProviderType.OrganizationDuo: - // Setup listener for duo-redirect.ts connector to send back the code - if (!this.duoResultListenerInitialized) { - // setup client specific duo result listener - this.setupDuoResultListener(); - this.duoResultListenerInitialized = true; - } - // flow must be launched by user so they can choose to remember the device or not. - this.duoFramelessUrl = providerData.AuthUrl; - break; - case TwoFactorProviderType.Email: - this.twoFactorEmail = providerData.Email; - if ((await this.twoFactorService.getProviders()).size > 1) { - await this.sendEmail(false); - } - break; - default: - break; - } - } - - async submit() { - await this.setupCaptcha(); - - if (this.token == null || this.token === "") { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("verificationCodeRequired"), - }); - return; - } - - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) { - if (this.webAuthn != null) { - this.webAuthn.stop(); - } else { - return; - } - } else if ( - this.selectedProviderType === TwoFactorProviderType.Email || - this.selectedProviderType === TwoFactorProviderType.Authenticator - ) { - this.token = this.token.replace(" ", "").trim(); - } - - await this.doSubmit(); - if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) { - this.webAuthn.start(); - } - } - - async doSubmit() { - this.formPromise = this.loginStrategyService.logInTwoFactor( - new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember), - this.captchaToken, - ); - const authResult: AuthResult = await this.formPromise; - - await this.handleLoginResponse(authResult); - } - - protected handleMigrateEncryptionKey(result: AuthResult): boolean { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("encryptionKeyMigrationRequired"), - }); - return true; - } - - // Each client will have own implementation - protected setupDuoResultListener(): void {} - - private async handleLoginResponse(authResult: AuthResult) { - if (this.handleCaptchaRequired(authResult)) { - return; - } else if (this.handleMigrateEncryptionKey(authResult)) { - return; - } - - // Save off the OrgSsoIdentifier for use in the TDE flows - // - TDE login decryption options component - // - Browser SSO on extension open - const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; - await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier, userId); - this.loginEmailService.clearValues(); - - // note: this flow affects both TDE & standard users - if (this.isForcePasswordResetRequired(authResult)) { - return await this.handleForcePasswordReset(this.orgIdentifier); - } - - const userDecryptionOpts = await firstValueFrom( - this.userDecryptionOptionsService.userDecryptionOptions$, - ); - - const tdeEnabled = await this.isTrustedDeviceEncEnabled(userDecryptionOpts.trustedDeviceOption); - - if (tdeEnabled) { - return await this.handleTrustedDeviceEncryptionEnabled( - authResult, - this.orgIdentifier, - userDecryptionOpts, - ); - } - - // User must set password if they don't have one and they aren't using either TDE or key connector. - const requireSetPassword = - !userDecryptionOpts.hasMasterPassword && userDecryptionOpts.keyConnectorOption === undefined; - - if (requireSetPassword || authResult.resetMasterPassword) { - // Change implies going no password -> password in this case - return await this.handleChangePasswordRequired(this.orgIdentifier); - } - - return await this.handleSuccessfulLogin(); - } - - private async isTrustedDeviceEncEnabled( - trustedDeviceOption: TrustedDeviceUserDecryptionOption, - ): Promise { - const ssoTo2faFlowActive = this.route.snapshot.queryParamMap.get("sso") === "true"; - - return ssoTo2faFlowActive && trustedDeviceOption !== undefined; - } - - private async handleTrustedDeviceEncryptionEnabled( - authResult: AuthResult, - orgIdentifier: string, - userDecryptionOpts: UserDecryptionOptions, - ): Promise { - // If user doesn't have a MP, but has reset password permission, they must set a MP - if ( - !userDecryptionOpts.hasMasterPassword && - 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; - await this.masterPasswordService.setForceSetPasswordReason( - ForceSetPasswordReason.TdeUserWithoutPasswordHasPasswordResetPermission, - userId, - ); - } - - if (this.onSuccessfulLoginTde != null) { - // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete - // before navigating to the success route. - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onSuccessfulLoginTde(); - } - - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.navigateViaCallbackOrRoute( - this.onSuccessfulLoginTdeNavigate, - // Navigate to TDE page (if user was on trusted device and TDE has decrypted - // their user key, the login-initiated guard will redirect them to the vault) - [this.trustedDeviceEncRoute], - ); - } - - private async handleChangePasswordRequired(orgIdentifier: string) { - await this.router.navigate([this.changePasswordRoute], { - queryParams: { - identifier: orgIdentifier, - }, - }); - } - - /** - * Determines if a user needs to reset their password based on certain conditions. - * Users can be forced to reset their password via an admin or org policy disallowing weak passwords. - * Note: this is different from the SSO component login flow as a user can - * login with MP and then have to pass 2FA to finish login and we can actually - * evaluate if they have a weak password at that time. - * - * @param {AuthResult} authResult - The authentication result. - * @returns {boolean} Returns true if a password reset is required, false otherwise. - */ - private isForcePasswordResetRequired(authResult: AuthResult): boolean { - const forceResetReasons = [ - ForceSetPasswordReason.AdminForcePasswordReset, - ForceSetPasswordReason.WeakMasterPassword, - ]; - - return forceResetReasons.includes(authResult.forcePasswordReset); - } - - private async handleForcePasswordReset(orgIdentifier: string) { - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.forcePasswordResetRoute], { - queryParams: { - identifier: orgIdentifier, - }, - }); - } - - private async handleSuccessfulLogin() { - if (this.onSuccessfulLogin != null) { - // Note: awaiting this will currently cause a hang on desktop & browser as they will wait for a full sync to complete - // before navigating to the success route. - // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.onSuccessfulLogin(); - } - await this.navigateViaCallbackOrRoute(this.onSuccessfulLoginNavigate, [this.successRoute]); - } - - private async navigateViaCallbackOrRoute( - callback: () => Promise, - commands: unknown[], - extras?: NavigationExtras, - ): Promise { - if (callback) { - await callback(); - } else { - await this.router.navigate(commands, extras); - } - } - - async sendEmail(doToast: boolean) { - if (this.selectedProviderType !== TwoFactorProviderType.Email) { - return; - } - - if (this.emailPromise != null) { - return; - } - - if ((await this.loginStrategyService.getEmail()) == null) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: this.i18nService.t("sessionTimeout"), - }); - return; - } - - try { - const request = new TwoFactorEmailRequest(); - request.email = await this.loginStrategyService.getEmail(); - request.masterPasswordHash = await this.loginStrategyService.getMasterPasswordHash(); - request.ssoEmail2FaSessionToken = - await this.loginStrategyService.getSsoEmail2FaSessionToken(); - request.deviceIdentifier = await this.appIdService.getAppId(); - 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, - message: this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail), - }); - } - } catch (e) { - this.logService.error(e); - } - - this.emailPromise = null; - } - - async authWebAuthn() { - const providerData = await this.twoFactorService.getProviders().then((providers) => { - return providers.get(this.selectedProviderType); - }); - - if (!this.webAuthnSupported || this.webAuthn == null) { - return; - } - - this.webAuthn.init(providerData); - } - - private cleanupWebAuthn() { - if (this.webAuthn != null) { - this.webAuthn.stop(); - this.webAuthn.cleanup(); - } - } - - private async authing(): Promise { - return (await firstValueFrom(this.loginStrategyService.currentAuthType$)) !== null; - } - - private async needsLock(): Promise { - const authType = await firstValueFrom(this.loginStrategyService.currentAuthType$); - return authType == AuthenticationType.Sso || authType == AuthenticationType.UserApiKey; - } - - async launchDuoFrameless() { - if (this.duoFramelessUrl === null) { - this.toastService.showToast({ - variant: "error", - title: null, - message: this.i18nService.t("duoHealthCheckResultsInNullAuthUrlError"), - }); - return; - } - - this.platformUtilsService.launchUri(this.duoFramelessUrl); - } -} diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts deleted file mode 100644 index b19e73a7412..00000000000 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-route-swap.ts +++ /dev/null @@ -1,36 +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 "../../utils/component-route-swap"; - -/** - * Helper function to swap between two components based on the UnauthenticatedExtensionUIRefresh feature flag. - * We need this because the auth teams's authenticated UI will be refreshed as part of the MVP but the - * unauthenticated UIs will not necessarily make the cut. - * Note: Even though this is primarily an extension refresh initiative, this will be used across clients - * as we are consolidating the unauthenticated UIs into single libs/auth components which affects all clients. - * @param defaultComponent - The current non-refreshed component to render. - * @param refreshedComponent - The new refreshed component to render. - * @param options - The shared route options to apply to both components. - * @param altOptions - The alt route options to apply to the alt component. If not provided, the base options will be used. - */ -export function unauthUiRefreshSwap( - defaultComponent: Type, - refreshedComponent: Type, - options: Route, - altOptions?: Route, -): Routes { - return componentRouteSwap( - defaultComponent, - refreshedComponent, - async () => { - const configService = inject(ConfigService); - return configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh); - }, - options, - altOptions, - ); -}