[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:
Andreas Coroiu 2023-11-01 09:26:41 +01:00 committed by GitHub
parent 22a138a46f
commit 317d652088
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 41 additions and 40 deletions

View File

@ -9,12 +9,12 @@
<p bitTypography="body1">
{{ "passkeyEnterMasterPassword" | i18n }}
</p>
<bit-form-field disableMargin formGroupName="userVerification">
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<input type="password" bitInput formControlName="masterPassword" appAutofocus />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
<ng-container formGroupName="userVerification">
<app-user-verification
formControlName="secret"
[(invalidSecret)]="invalidSecret"
></app-user-verification>
</ng-container>
</ng-container>
<div *ngIf="currentStep === 'credentialCreation'" class="tw-flex tw-flex-col tw-items-center">

View File

@ -3,11 +3,11 @@ import { Component, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
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 { 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 { Verification } from "@bitwarden/common/types/verification";
import { DialogService } from "@bitwarden/components";
import { WebauthnLoginService } from "../../../core";
@ -35,9 +35,10 @@ export class CreateCredentialDialogComponent implements OnInit {
protected readonly Icons = { CreatePasskeyIcon, CreatePasskeyFailedIcon };
protected currentStep: Step = "userVerification";
protected invalidSecret = false;
protected formGroup = this.formBuilder.group({
userVerification: this.formBuilder.group({
masterPassword: ["", [Validators.required]],
secret: [null as Verification | null, Validators.required],
}),
credentialNaming: this.formBuilder.group({
name: ["", Validators.maxLength(50)],
@ -89,20 +90,19 @@ export class CreateCredentialDialogComponent implements OnInit {
}
try {
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions({
type: VerificationType.MasterPassword,
secret: this.formGroup.value.userVerification.masterPassword,
});
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions(
this.formGroup.value.userVerification.secret
);
} catch (error) {
if (error instanceof ErrorResponse && error.statusCode === 400) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
);
this.invalidSecret = true;
} else {
this.logService?.error(error);
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
this.platformUtilsService.showToast(
"error",
this.i18nService.t("unexpectedError"),
error.message
);
}
return;
}
@ -141,7 +141,11 @@ export class CreateCredentialDialogComponent implements OnInit {
);
} catch (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;
}

View File

@ -14,12 +14,10 @@
<ng-container *ngIf="credential">
<p bitTypography="body1">{{ "removePasskeyInfo" | i18n }}</p>
<bit-form-field disableMargin>
<bit-label>{{ "masterPassword" | i18n }}</bit-label>
<input type="password" bitInput formControlName="masterPassword" />
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
<bit-hint>{{ "confirmIdentity" | i18n }}</bit-hint>
</bit-form-field>
<app-user-verification
formControlName="secret"
[(invalidSecret)]="invalidSecret"
></app-user-verification>
</ng-container>
</ng-container>
<ng-container bitDialogFooter>

View File

@ -1,13 +1,13 @@
import { DialogConfig, DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
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 { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { ErrorResponse } from "@bitwarden/common/models/response/error.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 { Verification } from "@bitwarden/common/types/verification";
import { DialogService } from "@bitwarden/components";
import { WebauthnLoginService } from "../../../core";
@ -23,8 +23,9 @@ export interface DeleteCredentialDialogParams {
export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
protected invalidSecret = false;
protected formGroup = this.formBuilder.group({
masterPassword: ["", [Validators.required]],
secret: null as Verification | null,
});
protected credential?: WebauthnCredentialView;
protected loading$ = this.webauthnService.loading$;
@ -53,21 +54,18 @@ export class DeleteCredentialDialogComponent implements OnInit, OnDestroy {
this.dialogRef.disableClose = true;
try {
await this.webauthnService.deleteCredential(this.credential.id, {
type: VerificationType.MasterPassword,
secret: this.formGroup.value.masterPassword,
});
await this.webauthnService.deleteCredential(this.credential.id, this.formGroup.value.secret);
this.platformUtilsService.showToast("success", null, this.i18nService.t("passkeyRemoved"));
} catch (error) {
if (error instanceof ErrorResponse && error.statusCode === 400) {
this.invalidSecret = true;
} else {
this.logService?.error(error);
this.platformUtilsService.showToast(
"error",
this.i18nService.t("error"),
this.i18nService.t("invalidMasterPassword")
this.i18nService.t("unexpectedError"),
error.message
);
} else {
this.logService.error(error);
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
}
return false;
} finally {

View File

@ -2,13 +2,14 @@ import { NgModule } from "@angular/core";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { SharedModule } from "../../../shared/shared.module";
import { UserVerificationModule } from "../../shared/components/user-verification";
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.component";
@NgModule({
imports: [SharedModule, FormsModule, ReactiveFormsModule],
imports: [SharedModule, FormsModule, ReactiveFormsModule, UserVerificationModule],
declarations: [
WebauthnLoginSettingsComponent,
CreateCredentialDialogComponent,

View File

@ -51,8 +51,8 @@ export class UserVerificationComponent implements ControlValueAccessor, OnInit,
return {
invalidSecret: {
message: this.hasMasterPassword
? this.i18nService.t("incorrectCode")
: this.i18nService.t("incorrectPassword"),
? this.i18nService.t("incorrectPassword")
: this.i18nService.t("incorrectCode"),
},
};
}