1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-14 15:23:33 +00:00

[PM-7084] 2/6: Add shared two-factor-auth orchestrator component, and TOTP two-factor component (#9768)

* Add shared two-factor-options component

* Add new refactored two-factor-auth component and totp auth componnet behind feature flag

* Fix default value for twofactorcomponentrefactor featureflag
This commit is contained in:
Bernd Schoolmann
2024-07-09 16:19:04 +02:00
committed by GitHub
parent 830c1297f2
commit 7e2b4d9652
13 changed files with 1367 additions and 10 deletions

View File

@@ -0,0 +1,151 @@
import { CommonModule } from "@angular/common";
import { Component, Inject, OnInit } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { TwoFactorAuthAuthenticatorComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "@bitwarden/angular/auth/components/two-factor-auth/two-factor-options.component";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
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 { 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 { SyncService } from "@bitwarden/common/platform/sync";
import {
ButtonModule,
FormFieldModule,
AsyncActionsModule,
CheckboxModule,
DialogModule,
LinkModule,
TypographyModule,
DialogService,
} from "@bitwarden/components";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
} from "../../../../../libs/auth/src/common/abstractions";
import { BrowserApi } from "../../platform/browser/browser-api";
import BrowserPopupUtils from "../../platform/popup/browser-popup-utils";
@Component({
standalone: true,
templateUrl:
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent implements OnInit {
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
dialogService: DialogService,
protected route: ActivatedRoute,
logService: LogService,
protected twoFactorService: TwoFactorService,
loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window,
private syncService: SyncService,
private messagingService: MessagingService,
) {
super(
loginStrategyService,
router,
i18nService,
platformUtilsService,
environmentService,
dialogService,
route,
logService,
twoFactorService,
loginEmailService,
userDecryptionOptionsService,
ssoLoginService,
configService,
masterPasswordService,
accountService,
formBuilder,
win,
);
super.onSuccessfulLoginTdeNavigate = async () => {
this.win.close();
};
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
async ngOnInit(): Promise<void> {
await super.ngOnInit();
if (this.route.snapshot.paramMap.has("webAuthnResponse")) {
// WebAuthn fallback response
this.selectedProviderType = TwoFactorProviderType.WebAuthn;
this.token = this.route.snapshot.paramMap.get("webAuthnResponse");
super.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.submit();
return;
}
if (await BrowserPopupUtils.inPopout(this.win)) {
this.selectedProviderType = TwoFactorProviderType.Email;
}
// 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");
}
}
async ngOnDestroy() {
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && (await this.isLinux())) {
document.body.classList.remove("linux-webauthn");
}
}
async isLinux() {
return (await BrowserApi.getPlatformInfo()).os === "linux";
}
}

View File

@@ -19,6 +19,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { fido2AuthGuard } from "../auth/guards/fido2-auth.guard";
import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component";
import { EnvironmentComponent } from "../auth/popup/environment.component";
@@ -33,6 +34,7 @@ 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 { SsoComponent } from "../auth/popup/sso.component";
import { TwoFactorAuthComponent } from "../auth/popup/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../auth/popup/two-factor-options.component";
import { TwoFactorComponent } from "../auth/popup/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/popup/update-temp-password.component";
@@ -137,12 +139,26 @@ const routes: Routes = [
canActivate: [lockGuard()],
data: { state: "lock", doNotSaveUrl: true },
},
{
path: "2fa",
component: TwoFactorComponent,
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
},
...twofactorRefactorSwap(
TwoFactorComponent,
AnonLayoutWrapperComponent,
{
path: "2fa",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
},
{
path: "2fa",
canActivate: [unauthGuardFn(unauthRouteOverrides)],
data: { state: "2fa" },
children: [
{
path: "",
component: TwoFactorAuthComponent,
},
],
},
),
{
path: "2fa-options",
component: TwoFactorOptionsComponent,

View File

@@ -19,6 +19,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { AccessibilityCookieComponent } from "../auth/accessibility-cookie.component";
import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard";
import { HintComponent } from "../auth/hint.component";
@@ -30,6 +31,7 @@ import { RegisterComponent } from "../auth/register.component";
import { RemovePasswordComponent } from "../auth/remove-password.component";
import { SetPasswordComponent } from "../auth/set-password.component";
import { SsoComponent } from "../auth/sso.component";
import { TwoFactorAuthComponent } from "../auth/two-factor-auth.component";
import { TwoFactorComponent } from "../auth/two-factor.component";
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
import { VaultComponent } from "../vault/app/vault/vault.component";
@@ -61,7 +63,24 @@ const routes: Routes = [
path: "admin-approval-requested",
component: LoginViaAuthRequestComponent,
},
{ path: "2fa", component: TwoFactorComponent },
...twofactorRefactorSwap(
TwoFactorComponent,
AnonLayoutWrapperComponent,
{
path: "2fa",
},
{
path: "2fa",
component: AnonLayoutWrapperComponent,
children: [
{
path: "",
component: TwoFactorAuthComponent,
canActivate: [unauthGuardFn()],
},
],
},
),
{
path: "login-initiated",
component: LoginDecryptionOptionsComponent,

View File

@@ -0,0 +1,41 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { RouterLink } from "@angular/router";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
import { JslibModule } from "../../../../libs/angular/src/jslib.module";
import { AsyncActionsModule } from "../../../../libs/components/src/async-actions";
import { ButtonModule } from "../../../../libs/components/src/button";
import { CheckboxModule } from "../../../../libs/components/src/checkbox";
import { FormFieldModule } from "../../../../libs/components/src/form-field";
import { LinkModule } from "../../../../libs/components/src/link";
import { I18nPipe } from "../../../../libs/components/src/shared/i18n.pipe";
import { TypographyModule } from "../../../../libs/components/src/typography";
@Component({
standalone: true,
templateUrl:
"../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {}

View File

@@ -0,0 +1,107 @@
import { DialogModule } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/auth/abstractions/master-password.service.abstraction";
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 { 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 { LinkModule, TypographyModule, CheckboxModule, DialogService } from "@bitwarden/components";
import { TwoFactorAuthAuthenticatorComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth-authenticator.component";
import { TwoFactorAuthComponent as BaseTwoFactorAuthComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component";
import { TwoFactorOptionsComponent } from "../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-options.component";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
} from "../../../../../libs/auth/src/common/abstractions";
import { AsyncActionsModule } from "../../../../../libs/components/src/async-actions";
import { ButtonModule } from "../../../../../libs/components/src/button";
import { FormFieldModule } from "../../../../../libs/components/src/form-field";
@Component({
standalone: true,
templateUrl:
"../../../../../libs/angular/src/auth/components/two-factor-auth/two-factor-auth.component.html",
selector: "app-two-factor-auth",
imports: [
CommonModule,
JslibModule,
DialogModule,
ButtonModule,
LinkModule,
TypographyModule,
ReactiveFormsModule,
FormFieldModule,
AsyncActionsModule,
RouterLink,
CheckboxModule,
TwoFactorOptionsComponent,
TwoFactorAuthAuthenticatorComponent,
],
providers: [I18nPipe],
})
export class TwoFactorAuthComponent extends BaseTwoFactorAuthComponent {
constructor(
protected loginStrategyService: LoginStrategyServiceAbstraction,
protected router: Router,
i18nService: I18nService,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
dialogService: DialogService,
protected route: ActivatedRoute,
logService: LogService,
protected twoFactorService: TwoFactorService,
loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService,
formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window,
) {
super(
loginStrategyService,
router,
i18nService,
platformUtilsService,
environmentService,
dialogService,
route,
logService,
twoFactorService,
loginEmailService,
userDecryptionOptionsService,
ssoLoginService,
configService,
masterPasswordService,
accountService,
formBuilder,
win,
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
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;
}
}

View File

@@ -20,6 +20,7 @@ import {
} from "@bitwarden/auth/angular";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { twofactorRefactorSwap } from "../../../../libs/angular/src/utils/two-factor-component-refactor-route-swap";
import { flagEnabled, Flags } from "../utils/flags";
import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component";
@@ -46,6 +47,7 @@ import { EmergencyAccessViewComponent } from "./auth/settings/emergency-access/v
import { SecurityRoutingModule } from "./auth/settings/security/security-routing.module";
import { SsoComponent } from "./auth/sso.component";
import { TrialInitiationComponent } from "./auth/trial-initiation/trial-initiation.component";
import { TwoFactorAuthComponent } from "./auth/two-factor-auth.component";
import { TwoFactorComponent } from "./auth/two-factor.component";
import { UpdatePasswordComponent } from "./auth/update-password.component";
import { UpdateTempPasswordComponent } from "./auth/update-temp-password.component";
@@ -248,10 +250,9 @@ const routes: Routes = [
path: "2fa",
canActivate: [unauthGuardFn()],
children: [
{
...twofactorRefactorSwap(TwoFactorComponent, TwoFactorAuthComponent, {
path: "",
component: TwoFactorComponent,
},
}),
{
path: "",
component: EnvironmentSelectorComponent,