[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"> <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">

View File

@ -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;
} }

View File

@ -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>

View File

@ -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 {

View File

@ -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,

View File

@ -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"),
}, },
}; };
} }