mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
refactor(auth): [PM-8976] migrate two-factor setup component to Tailwind and standalone
- Remove Bootstrap styles from two-factor-setup component and replace with Tailwind equivalents - Convert two factor components to standalone components to move away from LooseComponents - Replace ul/li list with bit-item-group and bit-item components - Integrate with the bit design system --------- Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -21,11 +21,3 @@
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.recovery-code-img {
|
||||
@include themify($themes) {
|
||||
content: url("../images/two-factor/rc" + themed("mfaLogoSuffix"));
|
||||
max-width: 100px;
|
||||
max-height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -21,11 +21,3 @@
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.recovery-code-img {
|
||||
@include themify($themes) {
|
||||
content: url("../images/two-factor/rc" + themed("mfaLogoSuffix"));
|
||||
max-width: 100px;
|
||||
max-height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { ItemModule } from "@bitwarden/components";
|
||||
|
||||
import { LooseComponentsModule, SharedModule } from "../../../shared";
|
||||
import { AccountFingerprintComponent } from "../../../shared/components/account-fingerprint/account-fingerprint.component";
|
||||
import { PoliciesModule } from "../../organizations/policies";
|
||||
@@ -15,6 +17,7 @@ import { TwoFactorSetupComponent } from "./two-factor-setup.component";
|
||||
PoliciesModule,
|
||||
OrganizationSettingsRoutingModule,
|
||||
AccountFingerprintComponent,
|
||||
ItemModule,
|
||||
],
|
||||
declarations: [AccountComponent, TwoFactorSetupComponent],
|
||||
})
|
||||
|
||||
@@ -1,36 +1,50 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
import { TwoFactorRecoverResponse } from "@bitwarden/common/auth/models/response/two-factor-recover.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DIALOG_DATA, DialogConfig, DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
ButtonModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-recovery",
|
||||
templateUrl: "two-factor-recovery.component.html",
|
||||
standalone: true,
|
||||
imports: [CommonModule, DialogModule, ButtonModule, TypographyModule, I18nPipe],
|
||||
})
|
||||
export class TwoFactorRecoveryComponent {
|
||||
type = -1;
|
||||
code: string;
|
||||
authed: boolean;
|
||||
code: string = "";
|
||||
authed: boolean = false;
|
||||
twoFactorProviderType = TwoFactorProviderType;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: any,
|
||||
@Inject(DIALOG_DATA) protected data: { response: { response: TwoFactorRecoverResponse } },
|
||||
private i18nService: I18nService,
|
||||
) {
|
||||
this.auth(data.response);
|
||||
}
|
||||
|
||||
auth(authResponse: any) {
|
||||
auth(authResponse: { response: TwoFactorRecoverResponse }) {
|
||||
this.authed = true;
|
||||
this.processResponse(authResponse.response);
|
||||
}
|
||||
|
||||
print() {
|
||||
const w = window.open();
|
||||
if (!w) {
|
||||
// return early if the window is not open
|
||||
return;
|
||||
}
|
||||
w.document.write(
|
||||
'<div style="font-size: 18px; text-align: center;">' +
|
||||
"<p>" +
|
||||
@@ -47,9 +61,9 @@ export class TwoFactorRecoveryComponent {
|
||||
w.print();
|
||||
}
|
||||
|
||||
private formatString(s: string) {
|
||||
private formatString(s: string): string {
|
||||
if (s == null) {
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
return s
|
||||
.replace(/(.{4})/g, "$1 ")
|
||||
@@ -61,7 +75,13 @@ export class TwoFactorRecoveryComponent {
|
||||
this.code = this.formatString(response.code);
|
||||
}
|
||||
|
||||
static open(dialogService: DialogService, config: DialogConfig<any>) {
|
||||
static open(
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<
|
||||
{ response: { response: TwoFactorRecoverResponse } },
|
||||
DialogRef<unknown, TwoFactorRecoveryComponent>
|
||||
>,
|
||||
) {
|
||||
return dialogService.open(TwoFactorRecoveryComponent, config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
{{ "twoStepAuthenticatorInstructionSuffix" | i18n }}
|
||||
</p>
|
||||
|
||||
<p class="text-center">
|
||||
<p class="tw-text-center">
|
||||
<a
|
||||
href="https://apps.apple.com/ca/app/bitwarden-authenticator/id6497335175"
|
||||
target="_blank"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Inject, OnDestroy, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, FormControl, Validators } from "@angular/forms";
|
||||
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
@@ -18,12 +20,22 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
InputModule,
|
||||
LinkModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component";
|
||||
|
||||
@@ -44,6 +56,22 @@ declare global {
|
||||
@Component({
|
||||
selector: "app-two-factor-setup-authenticator",
|
||||
templateUrl: "two-factor-setup-authenticator.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
InputModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
CalloutModule,
|
||||
ButtonModule,
|
||||
IconModule,
|
||||
I18nPipe,
|
||||
AsyncActionsModule,
|
||||
JslibModule,
|
||||
],
|
||||
})
|
||||
export class TwoFactorSetupAuthenticatorComponent
|
||||
extends TwoFactorSetupMethodBaseComponent
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<bit-dialog [title]="'twoStepLogin' | i18n" [subtitle]="'Duo'">
|
||||
<ng-container bitDialogContent>
|
||||
<ng-container *ngIf="enabled">
|
||||
<app-callout type="success" title="{{ 'enabled' | i18n }}" icon="bwi bwi-check-circle">
|
||||
<bit-callout type="success" [title]="'enabled' | i18n" icon="bwi bwi-check-circle">
|
||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<img class="tw-float-right tw-ml-3 mfaType2" alt="Duo logo" />
|
||||
<strong>{{ "twoFactorDuoClientId" | i18n }}:</strong> {{ clientId }}
|
||||
<br />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
@@ -13,18 +12,41 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
InputModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-setup-duo",
|
||||
templateUrl: "two-factor-setup-duo.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
InputModule,
|
||||
TypographyModule,
|
||||
ButtonModule,
|
||||
IconModule,
|
||||
I18nPipe,
|
||||
ReactiveFormsModule,
|
||||
AsyncActionsModule,
|
||||
CalloutModule,
|
||||
],
|
||||
})
|
||||
export class TwoFactorSetupDuoComponent
|
||||
extends TwoFactorSetupMethodBaseComponent
|
||||
@@ -63,23 +85,23 @@ export class TwoFactorSetupDuoComponent
|
||||
);
|
||||
}
|
||||
|
||||
get clientId() {
|
||||
return this.formGroup.get("clientId").value;
|
||||
get clientId(): string {
|
||||
return this.formGroup.get("clientId")?.value || "";
|
||||
}
|
||||
get clientSecret() {
|
||||
return this.formGroup.get("clientSecret").value;
|
||||
get clientSecret(): string {
|
||||
return this.formGroup.get("clientSecret")?.value || "";
|
||||
}
|
||||
get host() {
|
||||
return this.formGroup.get("host").value;
|
||||
get host(): string {
|
||||
return this.formGroup.get("host")?.value || "";
|
||||
}
|
||||
set clientId(value: string) {
|
||||
this.formGroup.get("clientId").setValue(value);
|
||||
this.formGroup.get("clientId")?.setValue(value);
|
||||
}
|
||||
set clientSecret(value: string) {
|
||||
this.formGroup.get("clientSecret").setValue(value);
|
||||
this.formGroup.get("clientSecret")?.setValue(value);
|
||||
}
|
||||
set host(value: string) {
|
||||
this.formGroup.get("host").setValue(value);
|
||||
this.formGroup.get("host")?.setValue(value);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -147,7 +169,10 @@ export class TwoFactorSetupDuoComponent
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<TwoFactorDuoComponentConfig>,
|
||||
) => {
|
||||
return dialogService.open<boolean>(TwoFactorSetupDuoComponent, config);
|
||||
return dialogService.open<boolean, TwoFactorDuoComponentConfig>(
|
||||
TwoFactorSetupDuoComponent,
|
||||
config as DialogConfig<TwoFactorDuoComponentConfig, DialogRef<boolean>>,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Inject, OnInit, Output } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -16,19 +15,41 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
InputModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-two-factor-setup-email",
|
||||
templateUrl: "two-factor-setup-email.component.html",
|
||||
outputs: ["onUpdated"],
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
CommonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
IconModule,
|
||||
I18nPipe,
|
||||
InputModule,
|
||||
ReactiveFormsModule,
|
||||
TypographyModule,
|
||||
],
|
||||
})
|
||||
export class TwoFactorSetupEmailComponent
|
||||
extends TwoFactorSetupMethodBaseComponent
|
||||
@@ -36,8 +57,8 @@ export class TwoFactorSetupEmailComponent
|
||||
{
|
||||
@Output() onChangeStatus: EventEmitter<boolean> = new EventEmitter();
|
||||
type = TwoFactorProviderType.Email;
|
||||
sentEmail: string;
|
||||
emailPromise: Promise<unknown>;
|
||||
sentEmail: string = "";
|
||||
emailPromise: Promise<unknown> | undefined;
|
||||
override componentName = "app-two-factor-email";
|
||||
formGroup = this.formBuilder.group({
|
||||
token: ["", [Validators.required]],
|
||||
@@ -67,17 +88,17 @@ export class TwoFactorSetupEmailComponent
|
||||
toastService,
|
||||
);
|
||||
}
|
||||
get token() {
|
||||
return this.formGroup.get("token").value;
|
||||
get token(): string {
|
||||
return this.formGroup.get("token")?.value || "";
|
||||
}
|
||||
set token(value: string) {
|
||||
this.formGroup.get("token").setValue(value);
|
||||
set token(value: string | null) {
|
||||
this.formGroup.get("token")?.setValue(value || "");
|
||||
}
|
||||
get email() {
|
||||
return this.formGroup.get("email").value;
|
||||
get email(): string {
|
||||
return this.formGroup.get("email")?.value || "";
|
||||
}
|
||||
set email(value: string) {
|
||||
this.formGroup.get("email").setValue(value);
|
||||
set email(value: string | null | undefined) {
|
||||
this.formGroup.get("email")?.setValue(value || "");
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
@@ -149,6 +170,9 @@ export class TwoFactorSetupEmailComponent
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<AuthResponse<TwoFactorEmailResponse>>,
|
||||
) {
|
||||
return dialogService.open<boolean>(TwoFactorSetupEmailComponent, config);
|
||||
return dialogService.open<boolean, AuthResponse<TwoFactorEmailResponse>>(
|
||||
TwoFactorSetupEmailComponent,
|
||||
config as DialogConfig<AuthResponse<TwoFactorEmailResponse>, DialogRef<boolean>>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Directive, EventEmitter, Output } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
@@ -17,18 +15,20 @@ import { DialogService, ToastService } from "@bitwarden/components";
|
||||
/**
|
||||
* Base class for two-factor setup components (ex: email, yubikey, webauthn, duo).
|
||||
*/
|
||||
@Directive()
|
||||
@Directive({
|
||||
standalone: true,
|
||||
})
|
||||
export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
@Output() onUpdated = new EventEmitter<boolean>();
|
||||
|
||||
type: TwoFactorProviderType;
|
||||
organizationId: string;
|
||||
type: TwoFactorProviderType | undefined;
|
||||
organizationId: string | null = null;
|
||||
twoFactorProviderType = TwoFactorProviderType;
|
||||
enabled = false;
|
||||
authed = false;
|
||||
|
||||
protected hashedSecret: string;
|
||||
protected verificationType: VerificationType;
|
||||
protected hashedSecret: string | undefined;
|
||||
protected verificationType: VerificationType | undefined;
|
||||
protected componentName = "";
|
||||
|
||||
constructor(
|
||||
@@ -74,6 +74,9 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
|
||||
try {
|
||||
const request = await this.buildRequestModel(TwoFactorProviderRequest);
|
||||
if (this.type === undefined) {
|
||||
throw new Error("Two-factor provider type is required");
|
||||
}
|
||||
request.type = this.type;
|
||||
if (this.organizationId != null) {
|
||||
promise = this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request);
|
||||
@@ -84,7 +87,7 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
this.enabled = false;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
title: "",
|
||||
message: this.i18nService.t("twoStepDisabled"),
|
||||
});
|
||||
this.onUpdated.emit(false);
|
||||
@@ -105,6 +108,9 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
}
|
||||
|
||||
const request = await this.buildRequestModel(TwoFactorProviderRequest);
|
||||
if (this.type === undefined) {
|
||||
throw new Error("Two-factor provider type is required");
|
||||
}
|
||||
request.type = this.type;
|
||||
if (this.organizationId != null) {
|
||||
await this.apiService.putTwoFactorOrganizationDisable(this.organizationId, request);
|
||||
@@ -114,7 +120,7 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
this.enabled = false;
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
title: "",
|
||||
message: this.i18nService.t("twoStepDisabled"),
|
||||
});
|
||||
this.onUpdated.emit(false);
|
||||
@@ -123,6 +129,9 @@ export abstract class TwoFactorSetupMethodBaseComponent {
|
||||
protected async buildRequestModel<T extends SecretVerificationRequest>(
|
||||
requestClass: new () => T,
|
||||
) {
|
||||
if (this.hashedSecret === undefined || this.verificationType === undefined) {
|
||||
throw new Error("User verification data is missing");
|
||||
}
|
||||
return this.userVerificationService.buildRequest(
|
||||
{
|
||||
secret: this.hashedSecret,
|
||||
|
||||
@@ -5,23 +5,23 @@
|
||||
[subtitle]="'webAuthnTitle' | i18n"
|
||||
>
|
||||
<ng-container bitDialogContent>
|
||||
<app-callout
|
||||
<bit-callout
|
||||
type="success"
|
||||
title="{{ 'enabled' | i18n }}"
|
||||
icon="bwi bwi-check-circle"
|
||||
*ngIf="enabled"
|
||||
>
|
||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||
</app-callout>
|
||||
<app-callout type="warning">
|
||||
</bit-callout>
|
||||
<bit-callout type="warning">
|
||||
<p bitTypography="body1">{{ "twoFactorWebAuthnWarning1" | i18n }}</p>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<img class="tw-float-right tw-ml-5 mfaType7" alt="FIDO2 WebAuthn logo" />
|
||||
<ul class="bwi-ul">
|
||||
<li *ngFor="let k of keys; let i = index" #removeKeyBtn [appApiAction]="k.removePromise">
|
||||
<i class="bwi bwi-li bwi-key"></i>
|
||||
<span *ngIf="!k.configured || !k.name" bitTypography="body1" class="tw-font-bold">
|
||||
{{ "webAuthnkeyX" | i18n: i + 1 }}
|
||||
{{ "webAuthnkeyX" | i18n: (i + 1).toString() }}
|
||||
</span>
|
||||
<span *ngIf="k.configured && k.name" bitTypography="body1" class="tw-font-bold">
|
||||
{{ k.name }}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, NgZone } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
@@ -18,12 +18,20 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
LinkModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component";
|
||||
|
||||
@@ -38,24 +46,36 @@ interface Key {
|
||||
@Component({
|
||||
selector: "app-two-factor-setup-webauthn",
|
||||
templateUrl: "two-factor-setup-webauthn.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
CommonModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
I18nPipe,
|
||||
JslibModule,
|
||||
LinkModule,
|
||||
ReactiveFormsModule,
|
||||
TypographyModule,
|
||||
],
|
||||
})
|
||||
export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseComponent {
|
||||
type = TwoFactorProviderType.WebAuthn;
|
||||
name: string;
|
||||
keys: Key[];
|
||||
keyIdAvailable: number = null;
|
||||
name: string = "";
|
||||
keys: Key[] = [];
|
||||
keyIdAvailable: number | null = null;
|
||||
keysConfiguredCount = 0;
|
||||
webAuthnError: boolean;
|
||||
webAuthnListening: boolean;
|
||||
webAuthnResponse: PublicKeyCredential;
|
||||
challengePromise: Promise<ChallengeResponse>;
|
||||
formPromise: Promise<TwoFactorWebAuthnResponse>;
|
||||
webAuthnError: boolean = false;
|
||||
webAuthnListening: boolean = false;
|
||||
webAuthnResponse: PublicKeyCredential | null = null;
|
||||
challengePromise: Promise<ChallengeResponse> | undefined;
|
||||
formPromise: Promise<TwoFactorWebAuthnResponse> | undefined;
|
||||
|
||||
override componentName = "app-two-factor-webauthn";
|
||||
|
||||
protected formGroup = new FormGroup({
|
||||
name: new FormControl({ value: "", disabled: !this.keyIdAvailable }),
|
||||
});
|
||||
protected formGroup: FormGroup;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected data: AuthResponse<TwoFactorWebAuthnResponse>,
|
||||
@@ -78,6 +98,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
dialogService,
|
||||
toastService,
|
||||
);
|
||||
this.formGroup = new FormGroup({
|
||||
name: new FormControl({ value: "", disabled: false }),
|
||||
});
|
||||
this.auth(data);
|
||||
}
|
||||
|
||||
@@ -96,9 +119,14 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
|
||||
protected async enable() {
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest);
|
||||
|
||||
if (this.webAuthnResponse == undefined || this.keyIdAvailable == undefined) {
|
||||
throw new Error("WebAuthn response or key ID is missing");
|
||||
}
|
||||
|
||||
request.deviceResponse = this.webAuthnResponse;
|
||||
request.id = this.keyIdAvailable;
|
||||
request.name = this.formGroup.value.name;
|
||||
request.name = this.formGroup.value.name || "";
|
||||
|
||||
const response = await this.apiService.putTwoFactorWebAuthn(request);
|
||||
this.processResponse(response);
|
||||
@@ -164,10 +192,10 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
.create({
|
||||
publicKey: webAuthnChallenge,
|
||||
})
|
||||
.then((data: PublicKeyCredential) => {
|
||||
.then((data) => {
|
||||
this.ngZone.run(() => {
|
||||
this.webAuthnListening = false;
|
||||
this.webAuthnResponse = data;
|
||||
this.webAuthnResponse = data as PublicKeyCredential;
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -189,8 +217,11 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
this.resetWebAuthn();
|
||||
this.keys = [];
|
||||
this.keyIdAvailable = null;
|
||||
this.formGroup.get("name").enable();
|
||||
this.formGroup.get("name").setValue(null);
|
||||
const nameControl = this.formGroup.get("name");
|
||||
if (nameControl) {
|
||||
nameControl.enable();
|
||||
nameControl.setValue("");
|
||||
}
|
||||
this.keysConfiguredCount = 0;
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
if (response.keys != null) {
|
||||
@@ -207,7 +238,7 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
continue;
|
||||
}
|
||||
}
|
||||
this.keys.push({ id: i, name: null, configured: false, removePromise: null });
|
||||
this.keys.push({ id: i, name: "", configured: false, removePromise: null });
|
||||
if (this.keyIdAvailable == null) {
|
||||
this.keyIdAvailable = i;
|
||||
}
|
||||
@@ -220,6 +251,9 @@ export class TwoFactorSetupWebAuthnComponent extends TwoFactorSetupMethodBaseCom
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<AuthResponse<TwoFactorWebAuthnResponse>>,
|
||||
) {
|
||||
return dialogService.open<boolean>(TwoFactorSetupWebAuthnComponent, config);
|
||||
return dialogService.open<boolean, AuthResponse<TwoFactorWebAuthnResponse>>(
|
||||
TwoFactorSetupWebAuthnComponent,
|
||||
config as DialogConfig<AuthResponse<TwoFactorWebAuthnResponse>, DialogRef<boolean>>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
<form *ngIf="authed" [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [title]="'twoStepLogin' | i18n" [subtitle]="'YubiKey'">
|
||||
<ng-container bitDialogContent>
|
||||
<app-callout
|
||||
<bit-callout
|
||||
*ngIf="enabled"
|
||||
type="success"
|
||||
title="{{ 'enabled' | i18n }}"
|
||||
icon="bwi bwi-check-circle"
|
||||
>
|
||||
{{ "twoStepLoginProviderEnabled" | i18n }}
|
||||
</app-callout>
|
||||
<app-callout type="warning">
|
||||
</bit-callout>
|
||||
<bit-callout type="warning">
|
||||
<p bitTypography="body1">{{ "twoFactorYubikeyWarning" | i18n }}</p>
|
||||
<ul class="tw-mb-0" bitTypography="body1">
|
||||
<li>{{ "twoFactorYubikeySupportUsb" | i18n }}</li>
|
||||
<li>{{ "twoFactorYubikeySupportMobile" | i18n }}</li>
|
||||
</ul>
|
||||
</app-callout>
|
||||
</bit-callout>
|
||||
<img class="tw-float-right mfaType3" alt="YubiKey OTP security key logo" />
|
||||
<p bitTypography="body1">{{ "twoFactorYubikeyAdd" | i18n }}:</p>
|
||||
<ol bitTypography="body1">
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="tw-grid tw-grid-cols-12 tw-gap-4" formArrayName="formKeys">
|
||||
<div class="tw-col-span-6" *ngFor="let k of keys; let i = index">
|
||||
<div [formGroupName]="i">
|
||||
<bit-label>{{ "yubikeyX" | i18n: i + 1 }}</bit-label>
|
||||
<bit-label>{{ "yubikeyX" | i18n: (i + 1).toString() }}</bit-label>
|
||||
<bit-form-field *ngIf="!keys[i].existingKey">
|
||||
<input bitInput type="password" formControlName="key" appInputVerbatim />
|
||||
</bit-form-field>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { FormArray, FormBuilder, FormControl, FormGroup } from "@angular/forms";
|
||||
import {
|
||||
FormArray,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
} from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
@@ -12,7 +18,24 @@ import { AuthResponse } from "@bitwarden/common/auth/types/auth-response";
|
||||
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 { DIALOG_DATA, DialogConfig, DialogService, ToastService } from "@bitwarden/components";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
CalloutModule,
|
||||
CheckboxModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
InputModule,
|
||||
LinkModule,
|
||||
ToastService,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { TwoFactorSetupMethodBaseComponent } from "./two-factor-setup-method-base.component";
|
||||
|
||||
@@ -24,30 +47,49 @@ interface Key {
|
||||
@Component({
|
||||
selector: "app-two-factor-setup-yubikey",
|
||||
templateUrl: "two-factor-setup-yubikey.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
JslibModule,
|
||||
DialogModule,
|
||||
FormFieldModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
CalloutModule,
|
||||
CheckboxModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
InputModule,
|
||||
AsyncActionsModule,
|
||||
I18nPipe,
|
||||
],
|
||||
})
|
||||
export class TwoFactorSetupYubiKeyComponent
|
||||
extends TwoFactorSetupMethodBaseComponent
|
||||
implements OnInit
|
||||
{
|
||||
type = TwoFactorProviderType.Yubikey;
|
||||
keys: Key[];
|
||||
keys: Key[] = [];
|
||||
anyKeyHasNfc = false;
|
||||
|
||||
formPromise: Promise<TwoFactorYubiKeyResponse>;
|
||||
disablePromise: Promise<unknown>;
|
||||
formPromise: Promise<TwoFactorYubiKeyResponse> | undefined;
|
||||
disablePromise: Promise<unknown> | undefined;
|
||||
|
||||
override componentName = "app-two-factor-yubikey";
|
||||
formGroup: FormGroup<{
|
||||
formKeys: FormArray<FormControl<Key>>;
|
||||
anyKeyHasNfc: FormControl<boolean>;
|
||||
}>;
|
||||
formGroup:
|
||||
| FormGroup<{
|
||||
formKeys: FormArray<FormControl<Key | null>>;
|
||||
anyKeyHasNfc: FormControl<boolean | null>;
|
||||
}>
|
||||
| undefined;
|
||||
|
||||
get keysFormControl() {
|
||||
return this.formGroup.controls.formKeys.controls;
|
||||
return this.formGroup?.controls.formKeys.controls;
|
||||
}
|
||||
|
||||
get anyKeyHasNfcFormControl() {
|
||||
return this.formGroup.controls.anyKeyHasNfc;
|
||||
return this.formGroup?.controls.anyKeyHasNfc;
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -82,6 +124,9 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
}
|
||||
|
||||
refreshFormArrayData() {
|
||||
if (!this.formGroup) {
|
||||
return;
|
||||
}
|
||||
const formKeys = <FormArray>this.formGroup.get("formKeys");
|
||||
formKeys.clear();
|
||||
this.keys.forEach((val) => {
|
||||
@@ -99,6 +144,9 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if (!this.formGroup) {
|
||||
return;
|
||||
}
|
||||
this.formGroup.markAllAsTouched();
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
@@ -117,14 +165,17 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
};
|
||||
|
||||
protected async enable() {
|
||||
if (!this.formGroup) {
|
||||
return;
|
||||
}
|
||||
const keys = this.formGroup.controls.formKeys.value;
|
||||
const request = await this.buildRequestModel(UpdateTwoFactorYubikeyOtpRequest);
|
||||
request.key1 = keys != null && keys.length > 0 ? keys[0].key : null;
|
||||
request.key2 = keys != null && keys.length > 1 ? keys[1].key : null;
|
||||
request.key3 = keys != null && keys.length > 2 ? keys[2].key : null;
|
||||
request.key4 = keys != null && keys.length > 3 ? keys[3].key : null;
|
||||
request.key5 = keys != null && keys.length > 4 ? keys[4].key : null;
|
||||
request.nfc = this.formGroup.value.anyKeyHasNfc;
|
||||
request.key1 = keys != null && keys.length > 0 ? (keys[0]?.key ?? "") : "";
|
||||
request.key2 = keys != null && keys.length > 1 ? (keys[1]?.key ?? "") : "";
|
||||
request.key3 = keys != null && keys.length > 2 ? (keys[2]?.key ?? "") : "";
|
||||
request.key4 = keys != null && keys.length > 3 ? (keys[3]?.key ?? "") : "";
|
||||
request.key5 = keys != null && keys.length > 4 ? (keys[4]?.key ?? "") : "";
|
||||
request.nfc = this.formGroup.value.anyKeyHasNfc ?? false;
|
||||
|
||||
this.processResponse(await this.apiService.putTwoFactorYubiKey(request));
|
||||
this.refreshFormArrayData();
|
||||
@@ -137,12 +188,16 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
}
|
||||
|
||||
remove(pos: number) {
|
||||
this.keys[pos].key = null;
|
||||
this.keys[pos].existingKey = null;
|
||||
this.keys[pos].key = "";
|
||||
this.keys[pos].existingKey = "";
|
||||
|
||||
if (!this.keysFormControl || !this.keysFormControl[pos]) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.keysFormControl[pos].setValue({
|
||||
existingKey: null,
|
||||
key: null,
|
||||
existingKey: "",
|
||||
key: "",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,6 +228,9 @@ export class TwoFactorSetupYubiKeyComponent
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<AuthResponse<TwoFactorYubiKeyResponse>>,
|
||||
) {
|
||||
return dialogService.open<boolean>(TwoFactorSetupYubiKeyComponent, config);
|
||||
return dialogService.open<boolean, AuthResponse<TwoFactorYubiKeyResponse>>(
|
||||
TwoFactorSetupYubiKeyComponent,
|
||||
config as DialogConfig<AuthResponse<TwoFactorYubiKeyResponse>, DialogRef<boolean>>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<app-header *ngIf="organizationId != null"></app-header>
|
||||
|
||||
<bit-container>
|
||||
<div class="tabbed-header" *ngIf="organizationId == null">
|
||||
<div class="tw-mt-6 tw-mb-2 tw-pb-2.5" *ngIf="organizationId == null">
|
||||
<h1 *ngIf="!organizationId || !isEnterpriseOrg">{{ "twoStepLogin" | i18n }}</h1>
|
||||
<h1 *ngIf="organizationId && isEnterpriseOrg">{{ "twoStepLoginEnforcement" | i18n }}</h1>
|
||||
</div>
|
||||
@@ -35,7 +35,7 @@
|
||||
{{ "providers" | i18n }}
|
||||
<small *ngIf="loading">
|
||||
<i
|
||||
class="bwi bwi-spinner bwi-spin bwi-fw text-muted"
|
||||
class="bwi bwi-spinner bwi-spin bwi-fw tw-text-muted"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
@@ -45,32 +45,32 @@
|
||||
<bit-callout type="warning" *ngIf="showPolicyWarning">
|
||||
{{ "twoStepLoginPolicyUserWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<ul class="list-group list-group-2fa">
|
||||
<li *ngFor="let p of providers" class="list-group-item d-flex align-items-center">
|
||||
<div class="logo-2fa d-flex justify-content-center">
|
||||
<auth-two-factor-icon [provider]="p.type" [name]="p.name" />
|
||||
<bit-item-group [attr.aria-label]="'providers' | i18n">
|
||||
<bit-item *ngFor="let p of providers" class="tw-py-4">
|
||||
<div slot="start" class="tw-min-w-[120px] tw-flex tw-justify-center">
|
||||
<auth-two-factor-icon class="tw-flex tw-items-center" [provider]="p.type" [name]="p.name" />
|
||||
</div>
|
||||
<div class="mx-4">
|
||||
<h3 class="mb-0">
|
||||
<div bit-item-content class="tw-px-4">
|
||||
<h3 class="tw-mb-0">
|
||||
<div
|
||||
class="font-weight-semibold tw-text-base"
|
||||
class="tw-font-semibold tw-text-base"
|
||||
[style]="p.enabled || p.premium ? 'display:inline-block' : ''"
|
||||
>
|
||||
{{ p.name }}
|
||||
</div>
|
||||
<ng-container *ngIf="p.enabled">
|
||||
<i
|
||||
class="bwi bwi-check text-success bwi-fw"
|
||||
class="bwi bwi-check tw-text-success-600 bwi-fw tw-ml-2"
|
||||
title="{{ 'enabled' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span class="tw-sr-only">{{ "enabled" | i18n }}</span>
|
||||
</ng-container>
|
||||
<app-premium-badge *ngIf="p.premium"></app-premium-badge>
|
||||
<app-premium-badge class="tw-ml-2" *ngIf="p.premium"></app-premium-badge>
|
||||
</h3>
|
||||
{{ p.description }}
|
||||
<div class="tw-mt-2 tw-text-wrap">{{ p.description }}</div>
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<bit-item-action slot="end">
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
@@ -80,9 +80,9 @@
|
||||
>
|
||||
{{ "manage" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</bit-item-action>
|
||||
</bit-item>
|
||||
</bit-item-group>
|
||||
</bit-container>
|
||||
|
||||
<ng-template #duoTemplate></ng-template>
|
||||
|
||||
@@ -32,7 +32,10 @@ import { ProductTierType } from "@bitwarden/common/billing/enums";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { DialogRef, DialogService } from "@bitwarden/components";
|
||||
import { DialogRef, DialogService, ItemModule } from "@bitwarden/components";
|
||||
|
||||
import { LooseComponentsModule } from "../../../shared/loose-components.module";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
|
||||
import { TwoFactorRecoveryComponent } from "./two-factor-recovery.component";
|
||||
import { TwoFactorSetupAuthenticatorComponent } from "./two-factor-setup-authenticator.component";
|
||||
@@ -45,6 +48,8 @@ import { TwoFactorVerifyComponent } from "./two-factor-verify.component";
|
||||
@Component({
|
||||
selector: "app-two-factor-setup",
|
||||
templateUrl: "two-factor-setup.component.html",
|
||||
standalone: true,
|
||||
imports: [ItemModule, LooseComponentsModule, SharedModule],
|
||||
})
|
||||
export class TwoFactorSetupComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("yubikeyTemplate", { read: ViewContainerRef, static: true })
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, EventEmitter, Inject, Output } from "@angular/core";
|
||||
import { FormControl, FormGroup } from "@angular/forms";
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
import { UserVerificationFormInputComponent } from "@bitwarden/auth/angular";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
|
||||
@@ -13,7 +12,16 @@ import { TwoFactorResponse } from "@bitwarden/common/auth/types/two-factor-respo
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DIALOG_DATA,
|
||||
DialogConfig,
|
||||
DialogModule,
|
||||
DialogRef,
|
||||
DialogService,
|
||||
} from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
type TwoFactorVerifyDialogData = {
|
||||
type: TwoFactorProviderType;
|
||||
@@ -23,13 +31,22 @@ type TwoFactorVerifyDialogData = {
|
||||
@Component({
|
||||
selector: "app-two-factor-verify",
|
||||
templateUrl: "two-factor-verify.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogModule,
|
||||
I18nPipe,
|
||||
ReactiveFormsModule,
|
||||
UserVerificationFormInputComponent,
|
||||
],
|
||||
})
|
||||
export class TwoFactorVerifyComponent {
|
||||
type: TwoFactorProviderType;
|
||||
organizationId: string;
|
||||
@Output() onAuthed = new EventEmitter<AuthResponse<TwoFactorResponse>>();
|
||||
|
||||
formPromise: Promise<TwoFactorResponse>;
|
||||
formPromise: Promise<TwoFactorResponse> | undefined;
|
||||
|
||||
protected formGroup = new FormGroup({
|
||||
secret: new FormControl<Verification | null>(null),
|
||||
@@ -49,22 +66,25 @@ export class TwoFactorVerifyComponent {
|
||||
|
||||
submit = async () => {
|
||||
try {
|
||||
let hashedSecret: string;
|
||||
this.formPromise = this.userVerificationService
|
||||
.buildRequest(this.formGroup.value.secret)
|
||||
.then((request) => {
|
||||
hashedSecret =
|
||||
this.formGroup.value.secret.type === VerificationType.MasterPassword
|
||||
? request.masterPasswordHash
|
||||
: request.otp;
|
||||
return this.apiCall(request);
|
||||
});
|
||||
let hashedSecret = "";
|
||||
if (!this.formGroup.value.secret) {
|
||||
throw new Error("Secret is required");
|
||||
}
|
||||
|
||||
const secret = this.formGroup.value.secret!;
|
||||
this.formPromise = this.userVerificationService.buildRequest(secret).then((request) => {
|
||||
hashedSecret =
|
||||
secret.type === VerificationType.MasterPassword
|
||||
? request.masterPasswordHash
|
||||
: request.otp;
|
||||
return this.apiCall(request);
|
||||
});
|
||||
|
||||
const response = await this.formPromise;
|
||||
this.dialogRef.close({
|
||||
response: response,
|
||||
secret: hashedSecret,
|
||||
verificationType: this.formGroup.value.secret.type,
|
||||
verificationType: secret.type,
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof ErrorResponse && e.statusCode === 400) {
|
||||
@@ -88,6 +108,8 @@ export class TwoFactorVerifyComponent {
|
||||
return this.i18nService.t("authenticatorAppTitle");
|
||||
case TwoFactorProviderType.Yubikey:
|
||||
return "Yubikey";
|
||||
default:
|
||||
throw new Error(`Unknown two-factor type: ${this.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,10 +132,15 @@ export class TwoFactorVerifyComponent {
|
||||
return this.apiService.getTwoFactorAuthenticator(request);
|
||||
case TwoFactorProviderType.Yubikey:
|
||||
return this.apiService.getTwoFactorYubiKey(request);
|
||||
default:
|
||||
throw new Error(`Unknown two-factor type: ${this.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
static open(dialogService: DialogService, config: DialogConfig<TwoFactorVerifyDialogData>) {
|
||||
return dialogService.open<AuthResponse<any>>(TwoFactorVerifyComponent, config);
|
||||
return dialogService.open<AuthResponse<any>, TwoFactorVerifyDialogData>(
|
||||
TwoFactorVerifyComponent,
|
||||
config as DialogConfig<TwoFactorVerifyDialogData, DialogRef<AuthResponse<any>>>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,6 @@ import { ApiKeyComponent } from "../auth/settings/security/api-key.component";
|
||||
import { ChangeKdfModule } from "../auth/settings/security/change-kdf/change-kdf.module";
|
||||
import { SecurityKeysComponent } from "../auth/settings/security/security-keys.component";
|
||||
import { SecurityComponent } from "../auth/settings/security/security.component";
|
||||
import { TwoFactorRecoveryComponent } from "../auth/settings/two-factor/two-factor-recovery.component";
|
||||
import { TwoFactorSetupAuthenticatorComponent } from "../auth/settings/two-factor/two-factor-setup-authenticator.component";
|
||||
import { TwoFactorSetupDuoComponent } from "../auth/settings/two-factor/two-factor-setup-duo.component";
|
||||
import { TwoFactorSetupEmailComponent } from "../auth/settings/two-factor/two-factor-setup-email.component";
|
||||
import { TwoFactorSetupWebAuthnComponent } from "../auth/settings/two-factor/two-factor-setup-webauthn.component";
|
||||
import { TwoFactorSetupYubiKeyComponent } from "../auth/settings/two-factor/two-factor-setup-yubikey.component";
|
||||
import { TwoFactorSetupComponent } from "../auth/settings/two-factor/two-factor-setup.component";
|
||||
import { TwoFactorVerifyComponent } from "../auth/settings/two-factor/two-factor-verify.component";
|
||||
import { UserVerificationModule } from "../auth/shared/components/user-verification";
|
||||
import { UpdatePasswordComponent } from "../auth/update-password.component";
|
||||
import { UpdateTempPasswordComponent } from "../auth/update-temp-password.component";
|
||||
@@ -145,14 +137,6 @@ import { SharedModule } from "./shared.module";
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
TwoFactorSetupAuthenticatorComponent,
|
||||
TwoFactorSetupDuoComponent,
|
||||
TwoFactorSetupEmailComponent,
|
||||
TwoFactorRecoveryComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorSetupWebAuthnComponent,
|
||||
TwoFactorSetupYubiKeyComponent,
|
||||
UpdatePasswordComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
VerifyEmailTokenComponent,
|
||||
@@ -204,13 +188,6 @@ import { SharedModule } from "./shared.module";
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
TwoFactorSetupAuthenticatorComponent,
|
||||
TwoFactorSetupDuoComponent,
|
||||
TwoFactorSetupEmailComponent,
|
||||
TwoFactorSetupComponent,
|
||||
TwoFactorVerifyComponent,
|
||||
TwoFactorSetupWebAuthnComponent,
|
||||
TwoFactorSetupYubiKeyComponent,
|
||||
UpdateTempPasswordComponent,
|
||||
UpdatePasswordComponent,
|
||||
UserLayoutComponent,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -30,12 +30,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list-group-2fa {
|
||||
.logo-2fa {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
@each $mfaType in $mfaTypes {
|
||||
.mfaType#{$mfaType} {
|
||||
content: url("../images/two-factor/" + $mfaType + ".png");
|
||||
@@ -66,14 +60,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.recovery-code-img {
|
||||
@include themify($themes) {
|
||||
content: url("../images/two-factor/rc" + themed("mfaLogoSuffix"));
|
||||
max-width: 100px;
|
||||
max-height: 45px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress {
|
||||
@include themify($themes) {
|
||||
background-color: themed("pwStrengthBackground");
|
||||
|
||||
@@ -66,7 +66,7 @@ describe("Menu", () => {
|
||||
@Component({
|
||||
selector: "test-app",
|
||||
template: `
|
||||
<button type="button" [bitMenuTriggerFor]="testMenu" class="testclass">Open menu</button>
|
||||
<button type="button" [bitMenuTriggerFor]="testMenu">Open menu</button>
|
||||
|
||||
<bit-menu #testMenu>
|
||||
<a id="item1" bitMenuItem>Item 1</a>
|
||||
|
||||
Reference in New Issue
Block a user