[PM-3722] Use `UserVerificationPrompt` in passkey registration dialog (#6422)
* [PM-3722] fix: wrong translation bug * [PM-3722] feat: use user verification component during creation * [PM-3722] feat: use user verification component during deletion * [PM-3722] feat: improve error handling
This commit is contained in:
parent
22a138a46f
commit
317d652088
|
@ -9,12 +9,12 @@
|
||||||
<p bitTypography="body1">
|
<p bitTypography="body1">
|
||||||
{{ "passkeyEnterMasterPassword" | i18n }}
|
{{ "passkeyEnterMasterPassword" | i18n }}
|
||||||
</p>
|
</p>
|
||||||
<bit-form-field disableMargin formGroupName="userVerification">
|
<ng-container formGroupName="userVerification">
|
||||||
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
|
<app-user-verification
|
||||||
<input type="password" bitInput formControlName="masterPassword" appAutofocus />
|
formControlName="secret"
|
||||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
[(invalidSecret)]="invalidSecret"
|
||||||
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
></app-user-verification>
|
||||||
</bit-form-field>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div *ngIf="currentStep === 'credentialCreation'" class="tw-flex tw-flex-col tw-items-center">
|
<div *ngIf="currentStep === 'credentialCreation'" class="tw-flex tw-flex-col tw-items-center">
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { Component, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder, Validators } from "@angular/forms";
|
||||||
import { firstValueFrom, map, Observable } from "rxjs";
|
import { firstValueFrom, map, Observable } from "rxjs";
|
||||||
|
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { WebauthnLoginService } from "../../../core";
|
import { WebauthnLoginService } from "../../../core";
|
||||||
|
@ -35,9 +35,10 @@ export class CreateCredentialDialogComponent implements OnInit {
|
||||||
protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon };
|
protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon };
|
||||||
|
|
||||||
protected currentStep: Step = "userVerification";
|
protected currentStep: Step = "userVerification";
|
||||||
|
protected invalidSecret = false;
|
||||||
protected formGroup = this.formBuilder.group({
|
protected formGroup = this.formBuilder.group({
|
||||||
userVerification: this.formBuilder.group({
|
userVerification: this.formBuilder.group({
|
||||||
masterPassword: ["", [Validators.required]],
|
secret: [null as Verification | null, Validators.required],
|
||||||
}),
|
}),
|
||||||
credentialNaming: this.formBuilder.group({
|
credentialNaming: this.formBuilder.group({
|
||||||
name: ["", Validators.maxLength(50)],
|
name: ["", Validators.maxLength(50)],
|
||||||
|
@ -89,20 +90,19 @@ export class CreateCredentialDialogComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions({
|
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions(
|
||||||
type: VerificationType.MasterPassword,
|
this.formGroup.value.userVerification.secret
|
||||||
secret: this.formGroup.value.userVerification.masterPassword,
|
);
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
||||||
this.platformUtilsService.showToast(
|
this.invalidSecret = true;
|
||||||
"error",
|
|
||||||
this.i18nService.t("error"),
|
|
||||||
this.i18nService.t("invalidMasterPassword")
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.logService?.error(error);
|
this.logService?.error(error);
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("unexpectedError"),
|
||||||
|
error.message
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,11 @@ export class CreateCredentialDialogComponent implements OnInit {
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logService?.error(error);
|
this.logService?.error(error);
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
this.platformUtilsService.showToast(
|
||||||
|
"error",
|
||||||
|
this.i18nService.t("unexpectedError"),
|
||||||
|
error.message
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,10 @@
|
||||||
<ng-container *ngIf="credential">
|
<ng-container *ngIf="credential">
|
||||||
<p bitTypography="body1">{{ "removePasskeyInfo" | i18n }}</p>
|
<p bitTypography="body1">{{ "removePasskeyInfo" | i18n }}</p>
|
||||||
|
|
||||||
<bit-form-field disableMargin>
|
<app-user-verification
|
||||||
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
|
formControlName="secret"
|
||||||
<input type="password" bitInput formControlName="masterPassword" />
|
[(invalidSecret)]="invalidSecret"
|
||||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
></app-user-verification>
|
||||||
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
|
|
||||||
</bit-form-field>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container bitDialogFooter>
|
<ng-container bitDialogFooter>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
|
||||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, Validators } from "@angular/forms";
|
import { FormBuilder } from "@angular/forms";
|
||||||
import { Subject, takeUntil } from "rxjs";
|
import { Subject, takeUntil } from "rxjs";
|
||||||
|
|
||||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
|
||||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||||
|
import { Verification } from "@bitwarden/common/types/verification";
|
||||||
import { DialogService } from "@bitwarden/components";
|
import { DialogService } from "@bitwarden/components";
|
||||||
|
|
||||||
import { WebauthnLoginService } from "../../../core";
|
import { WebauthnLoginService } from "../../../core";
|
||||||
|
@ -23,8 +23,9 @@ export interface DeleteCredentialDialogParams {
|
||||||
export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
|
export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
|
||||||
private destroy$ = new Subject<void>();
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
|
protected invalidSecret = false;
|
||||||
protected formGroup = this.formBuilder.group({
|
protected formGroup = this.formBuilder.group({
|
||||||
masterPassword: ["", [Validators.required]],
|
secret: null as Verification | null,
|
||||||
});
|
});
|
||||||
protected credential?: WebauthnCredentialView;
|
protected credential?: WebauthnCredentialView;
|
||||||
protected loading$ = this.webauthnService.loading$;
|
protected loading$ = this.webauthnService.loading$;
|
||||||
|
@ -53,21 +54,18 @@ export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.dialogRef.disableClose = true;
|
this.dialogRef.disableClose = true;
|
||||||
try {
|
try {
|
||||||
await this.webauthnService.deleteCredential(this.credential.id, {
|
await this.webauthnService.deleteCredential(this.credential.id, this.formGroup.value.secret);
|
||||||
type: VerificationType.MasterPassword,
|
|
||||||
secret: this.formGroup.value.masterPassword,
|
|
||||||
});
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("passkeyRemoved"));
|
this.platformUtilsService.showToast("success", null, this.i18nService.t("passkeyRemoved"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
||||||
|
this.invalidSecret = true;
|
||||||
|
} else {
|
||||||
|
this.logService?.error(error);
|
||||||
this.platformUtilsService.showToast(
|
this.platformUtilsService.showToast(
|
||||||
"error",
|
"error",
|
||||||
this.i18nService.t("error"),
|
this.i18nService.t("unexpectedError"),
|
||||||
this.i18nService.t("invalidMasterPassword")
|
error.message
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
this.logService.error(error);
|
|
||||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -2,13 +2,14 @@ import { NgModule } from "@angular/core";
|
||||||
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
|
||||||
|
|
||||||
import { SharedModule } from "../../../shared/shared.module";
|
import { SharedModule } from "../../../shared/shared.module";
|
||||||
|
import { UserVerificationModule } from "../../shared/components/user-verification";
|
||||||
|
|
||||||
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
|
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
|
||||||
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
|
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
|
||||||
import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.component";
|
import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.component";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [SharedModule, FormsModule, ReactiveFormsModule],
|
imports: [SharedModule, FormsModule, ReactiveFormsModule, UserVerificationModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
WebauthnLoginSettingsComponent,
|
WebauthnLoginSettingsComponent,
|
||||||
CreateCredentialDialogComponent,
|
CreateCredentialDialogComponent,
|
||||||
|
|
|
@ -51,8 +51,8 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit,
|
||||||
return {
|
return {
|
||||||
invalidSecret: {
|
invalidSecret: {
|
||||||
message: this.hasMasterPassword
|
message: this.hasMasterPassword
|
||||||
? this.i18nService.t("incorrectCode")
|
? this.i18nService.t("incorrectPassword")
|
||||||
: this.i18nService.t("incorrectPassword"),
|
: this.i18nService.t("incorrectCode"),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue