1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +00:00

[PM-4956] two factor component migration (#9204)

* two factor component migration

* two factor component migration

* two factor component migration

* two factor component migration

* two factor component migration
This commit is contained in:
vinith-kovan
2024-06-11 23:25:58 +05:30
committed by GitHub
parent 832abcd955
commit 19d863c9ef
4 changed files with 172 additions and 170 deletions

View File

@@ -1,97 +1,72 @@
<form <form [bitSubmit]="submitForm" [formGroup]="formGroup" autocomplete="off">
#form <div class="tw-min-w-96">
(ngSubmit)="submit()"
[appApiAction]="formPromise"
class="container"
ngNativeValidate
autocomplete="off"
>
<div class="row justify-content-md-center mt-5">
<div
class="col-5"
[ngClass]="{
'col-9': !duoFrameless && isDuoProvider
}"
>
<p class="lead text-center mb-4">{{ title }}</p>
<div class="card d-block">
<div class="card-body">
<ng-container <ng-container
*ngIf=" *ngIf="
selectedProviderType === providerType.Email || selectedProviderType === providerType.Email ||
selectedProviderType === providerType.Authenticator selectedProviderType === providerType.Authenticator
" "
> >
<p *ngIf="selectedProviderType === providerType.Authenticator"> <p bitTypography="body1" *ngIf="selectedProviderType === providerType.Authenticator">
{{ "enterVerificationCodeApp" | i18n }} {{ "enterVerificationCodeApp" | i18n }}
</p> </p>
<p *ngIf="selectedProviderType === providerType.Email"> <p bitTypography="body1" *ngIf="selectedProviderType === providerType.Email">
{{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }} {{ "enterVerificationCodeEmail" | i18n: twoFactorEmail }}
</p> </p>
<div class="form-group"> <bit-form-field>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label> <bit-label>{{ "verificationCode" | i18n }}</bit-label>
<input <input bitInput type="text" formControlName="token" appAutofocus appInputVerbatim />
id="code" <bit-hint *ngIf="selectedProviderType === providerType.Email">
type="text"
name="Code"
class="form-control"
[(ngModel)]="token"
required
appAutofocus
inputmode="tel"
appInputVerbatim
/>
<small class="form-text" *ngIf="selectedProviderType === providerType.Email">
<a <a
bitLink
href="#" href="#"
appStopClick appStopClick
(click)="sendEmail(true)" (click)="sendEmail(true)"
[appApiAction]="emailPromise"
*ngIf="selectedProviderType === providerType.Email" *ngIf="selectedProviderType === providerType.Email"
> >
{{ "sendVerificationCodeEmailAgain" | i18n }} {{ "sendVerificationCodeEmailAgain" | i18n }}
</a> </a></bit-hint
</small> >
</div> </bit-form-field>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.Yubikey"> <ng-container *ngIf="selectedProviderType === providerType.Yubikey">
<p class="text-center">{{ "insertYubiKey" | i18n }}</p> <p bitTypography="body1" class="tw-text-center">{{ "insertYubiKey" | i18n }}</p>
<picture> <picture>
<source srcset="../../images/yubikey.avif" type="image/avif" /> <source srcset="../../images/yubikey.avif" type="image/avif" />
<source srcset="../../images/yubikey.webp" type="image/webp" /> <source srcset="../../images/yubikey.webp" type="image/webp" />
<img src="../../images/yubikey.jpg" class="rounded img-fluid mb-3" alt="" /> <img src="../../images/yubikey.jpg" class="tw-rounded img-fluid tw-mb-3" alt="" />
</picture> </picture>
<div class="form-group"> <bit-form-field>
<label for="code" class="sr-only">{{ "verificationCode" | i18n }}</label> <bit-label class="tw-sr-only">{{ "verificationCode" | i18n }}</bit-label>
<input <input
id="code" type="text"
type="password" bitInput
name="Code" formControlName="token"
class="form-control"
[(ngModel)]="token"
required
appAutofocus appAutofocus
appInputVerbatim appInputVerbatim
autocomplete="new-password" autocomplete="new-password"
/> />
</div> </bit-form-field>
</ng-container> </ng-container>
<ng-container *ngIf="selectedProviderType === providerType.WebAuthn"> <ng-container *ngIf="selectedProviderType === providerType.WebAuthn">
<div id="web-authn-frame" class="mb-3"> <div id="web-authn-frame" class="tw-mb-3">
<iframe id="webauthn_iframe" sandbox="allow-scripts allow-same-origin"></iframe> <iframe id="webauthn_iframe" sandbox="allow-scripts allow-same-origin"></iframe>
</div> </div>
</ng-container> </ng-container>
<!-- Duo --> <!-- Duo -->
<ng-container *ngIf="isDuoProvider"> <ng-container *ngIf="isDuoProvider">
<ng-container *ngIf="duoFrameless"> <ng-container *ngIf="duoFrameless">
<p *ngIf="selectedProviderType === providerType.OrganizationDuo" class="tw-mb-0"> <p
bitTypography="body1"
*ngIf="selectedProviderType === providerType.OrganizationDuo"
class="tw-mb-0"
>
{{ "duoRequiredByOrgForAccount" | i18n }} {{ "duoRequiredByOrgForAccount" | i18n }}
</p> </p>
<p>{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}</p> <p bitTypography="body1">{{ "launchDuoAndFollowStepsToFinishLoggingIn" | i18n }}</p>
</ng-container> </ng-container>
<ng-container *ngIf="!duoFrameless"> <ng-container *ngIf="!duoFrameless">
<div id="duo-frame" class="mb-3"> <div id="duo-frame" class="tw-mb-3">
<iframe <iframe
id="duo_iframe" id="duo_iframe"
sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox" sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox"
@@ -99,75 +74,51 @@
</div> </div>
</ng-container> </ng-container>
</ng-container> </ng-container>
<i <bit-form-control *ngIf="selectedProviderType != null">
class="bwi bwi-spinner text-muted bwi-spin pull-right" <bit-label>{{ "rememberMe" | i18n }}</bit-label>
title="{{ 'loading' | i18n }}" <input type="checkbox" bitCheckbox formControlName="remember" />
*ngIf="form.loading && selectedProviderType === providerType.WebAuthn" </bit-form-control>
aria-hidden="true"
></i>
<div class="form-check" *ngIf="selectedProviderType != null">
<input
id="remember"
type="checkbox"
name="Remember"
class="form-check-input"
[(ngModel)]="remember"
/>
<label for="remember" class="form-check-label">{{ "rememberMe" | i18n }}</label>
</div>
<ng-container *ngIf="selectedProviderType == null"> <ng-container *ngIf="selectedProviderType == null">
<p>{{ "noTwoStepProviders" | i18n }}</p> <p bitTypography="body1">{{ "noTwoStepProviders" | i18n }}</p>
<p>{{ "noTwoStepProviders2" | i18n }}</p> <p bitTypography="body1">{{ "noTwoStepProviders2" | i18n }}</p>
</ng-container> </ng-container>
<hr /> <hr />
<div [hidden]="!showCaptcha()"> <div [hidden]="!showCaptcha()">
<iframe <iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
id="hcaptcha_iframe"
height="80"
sandbox="allow-scripts allow-same-origin"
></iframe>
</div> </div>
<!-- Buttons --> <!-- Buttons -->
<div class="tw-flex tw-flex-col tw-mb-3"> <div class="tw-flex tw-flex-col tw-space-y-2.5 tw-mb-3">
<button <button
type="submit" type="submit"
class="btn btn-primary btn-block btn-submit" buttonType="primary"
[disabled]="form.loading" bitButton
bitFormButton
*ngIf=" *ngIf="
selectedProviderType != null && selectedProviderType != null &&
!isDuoProvider && !isDuoProvider &&
selectedProviderType !== providerType.WebAuthn selectedProviderType !== providerType.WebAuthn
" "
> >
<span> <span> <i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }} </span>
<i class="bwi bwi-sign-in" aria-hidden="true"></i> {{ "continue" | i18n }}
</span>
<i
class="bwi bwi-spinner bwi-spin"
title="{{ 'loading' | i18n }}"
aria-hidden="true"
></i>
</button> </button>
<button <button
(click)="launchDuoFrameless()" (click)="launchDuoFrameless()"
type="button" type="button"
class="btn btn-primary btn-block" buttonType="primary"
[disabled]="form.loading" bitButton
bitFormButton
*ngIf="duoFrameless && isDuoProvider" *ngIf="duoFrameless && isDuoProvider"
> >
<span> {{ "launchDuo" | i18n }} </span> <span> {{ "launchDuo" | i18n }} </span>
</button> </button>
<a routerLink="/login" class="btn btn-outline-secondary btn-block"> <a routerLink="/login" bitButton buttonType="secondary">
{{ "cancel" | i18n }} {{ "cancel" | i18n }}
</a> </a>
</div> </div>
<div class="text-center"> <div class="text-center">
<a href="#" appStopClick (click)="anotherMethod()">{{ <a bitLink href="#" appStopClick (click)="anotherMethod()">{{
"useAnotherTwoStepMethod" | i18n "useAnotherTwoStepMethod" | i18n
}}</a> }}</a>
</div> </div>
</div> </div>
</div>
</div>
</div>
</form> </form>

View File

@@ -1,6 +1,7 @@
import { Component, Inject, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core"; import { Component, Inject, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { lastValueFrom } from "rxjs"; import { Subject, takeUntil, lastValueFrom } from "rxjs";
import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component"; import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular/auth/components/two-factor.component";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
@@ -38,7 +39,17 @@ import {
export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDestroy { export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDestroy {
@ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true }) @ViewChild("twoFactorOptions", { read: ViewContainerRef, static: true })
twoFactorOptionsModal: ViewContainerRef; twoFactorOptionsModal: ViewContainerRef;
formGroup = this.formBuilder.group({
token: [
"",
{
validators: [Validators.required],
updateOn: "submit",
},
],
remember: [false],
});
private destroy$ = new Subject<void>();
constructor( constructor(
loginStrategyService: LoginStrategyServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction,
router: Router, router: Router,
@@ -58,6 +69,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
configService: ConfigService, configService: ConfigService,
masterPasswordService: InternalMasterPasswordServiceAbstraction, masterPasswordService: InternalMasterPasswordServiceAbstraction,
accountService: AccountService, accountService: AccountService,
private formBuilder: FormBuilder,
@Inject(WINDOW) protected win: Window, @Inject(WINDOW) protected win: Window,
) { ) {
super( super(
@@ -82,6 +94,16 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
); );
this.onSuccessfulLoginNavigate = this.goAfterLogIn; 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() { async anotherMethod() {
const dialogRef = TwoFactorOptionsComponent.open(this.dialogService); const dialogRef = TwoFactorOptionsComponent.open(this.dialogService);

View File

@@ -82,7 +82,6 @@ const routes: Routes = [
component: LoginViaAuthRequestComponent, component: LoginViaAuthRequestComponent,
data: { titleId: "adminApprovalRequested" } satisfies DataProperties, data: { titleId: "adminApprovalRequested" } satisfies DataProperties,
}, },
{ path: "2fa", component: TwoFactorComponent, canActivate: [UnauthGuard] },
{ {
path: "login-initiated", path: "login-initiated",
component: LoginDecryptionOptionsComponent, component: LoginDecryptionOptionsComponent,
@@ -189,6 +188,33 @@ const routes: Routes = [
path: "", path: "",
component: AnonLayoutWrapperComponent, component: AnonLayoutWrapperComponent,
children: [ children: [
{
path: "2fa",
component: TwoFactorComponent,
canActivate: [unauthGuardFn()],
data: {
pageTitle: "verifyIdentity",
},
},
{
path: "recover-2fa",
canActivate: [unauthGuardFn()],
children: [
{
path: "",
component: RecoverTwoFactorComponent,
},
{
path: "",
component: EnvironmentSelectorComponent,
outlet: "environment-selector",
},
],
data: {
pageTitle: "recoverAccountTwoStep",
titleId: "recoverAccountTwoStep",
} satisfies DataProperties & AnonLayoutWrapperData,
},
{ {
path: "accept-emergency", path: "accept-emergency",
canActivate: [deepLinkGuard()], canActivate: [deepLinkGuard()],

View File

@@ -722,6 +722,9 @@
"logIn": { "logIn": {
"message": "Log in" "message": "Log in"
}, },
"verifyIdentity": {
"message": "Verify your Identity"
},
"logInInitiated": { "logInInitiated": {
"message": "Log in initiated" "message": "Log in initiated"
}, },