diff --git a/apps/web/src/app/auth/core/services/webauthn/webauthn.service.spec.ts b/apps/web/src/app/auth/core/services/webauthn/webauthn.service.spec.ts index 5453e8c3287..ff0f78cfbb6 100644 --- a/apps/web/src/app/auth/core/services/webauthn/webauthn.service.spec.ts +++ b/apps/web/src/app/auth/core/services/webauthn/webauthn.service.spec.ts @@ -6,6 +6,8 @@ import { VerificationType } from "@bitwarden/common/auth/enums/verification-type import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response"; import { Verification } from "@bitwarden/common/types/verification"; +import { NewCredentialOptionsView } from "../../views/new-credential-options.view"; + import { WebauthnApiService } from "./webauthn-api.service"; import { WebauthnService } from "./webauthn.service"; @@ -13,13 +15,20 @@ describe("WebauthnService", () => { let apiService!: MockProxy; let platformUtilsService!: MockProxy; let i18nService!: MockProxy; + let credentials: MockProxy; let webauthnService!: WebauthnService; beforeAll(() => { apiService = mock(); platformUtilsService = mock(); i18nService = mock(); - webauthnService = new WebauthnService(apiService, platformUtilsService, i18nService); + credentials = mock(); + webauthnService = new WebauthnService( + apiService, + platformUtilsService, + i18nService, + credentials + ); }); describe("getNewCredentialOptions", () => { @@ -43,6 +52,17 @@ describe("WebauthnService", () => { expect(result).toEqual({ challenge }); }); }); + + describe("createCredential", () => { + it("should return undefined when navigator.credentials throws", async () => { + credentials.create.mockRejectedValue(new Error("Mocked error")); + const options = createNewCredentialOptions(); + + const result = await webauthnService.createCredential(options); + + expect(result).toBeUndefined(); + }); + }); }); function createVerification(): Verification { @@ -51,3 +71,7 @@ function createVerification(): Verification { secret: "secret", }; } + +function createNewCredentialOptions(): NewCredentialOptionsView { + return new NewCredentialOptionsView(Symbol() as any); +} diff --git a/apps/web/src/app/auth/core/services/webauthn/webauthn.service.ts b/apps/web/src/app/auth/core/services/webauthn/webauthn.service.ts index a65ea868e7b..71c56f004a4 100644 --- a/apps/web/src/app/auth/core/services/webauthn/webauthn.service.ts +++ b/apps/web/src/app/auth/core/services/webauthn/webauthn.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from "@angular/core"; +import { Injectable, Optional } from "@angular/core"; import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; @@ -17,10 +17,13 @@ export class WebauthnService { constructor( private apiService: WebauthnApiService, private platformUtilsService: PlatformUtilsService, - private i18nService: I18nService + private i18nService: I18nService, + @Optional() private credentials: CredentialsContainer = navigator.credentials ) {} - async getNewCredentialOptions(verification: Verification): Promise { + async getNewCredentialOptions( + verification: Verification + ): Promise { try { return { challenge: await this.apiService.getChallenge(verification) }; } catch (error) { @@ -39,8 +42,7 @@ export class WebauthnService { async createCredential( credentialOptions: NewCredentialOptionsView - ): Promise { - await new Promise((_, reject) => setTimeout(() => reject(new Error("Not implemented")), 1000)); - return undefined; + ): Promise { + return await new Promise((resolve) => setTimeout(() => resolve(undefined), 1000)); } } diff --git a/apps/web/src/app/auth/settings/fido2-login-settings/create-credential-dialog/create-credential-dialog.component.ts b/apps/web/src/app/auth/settings/fido2-login-settings/create-credential-dialog/create-credential-dialog.component.ts index b4e7c529fa5..9c5efbf228b 100644 --- a/apps/web/src/app/auth/settings/fido2-login-settings/create-credential-dialog/create-credential-dialog.component.ts +++ b/apps/web/src/app/auth/settings/fido2-login-settings/create-credential-dialog/create-credential-dialog.component.ts @@ -71,10 +71,10 @@ export class CreateCredentialDialogComponent { } if (this.currentStep === "credentialCreation") { - try { - await this.webauthnService.createCredential(this.credentialOptions); - } catch { + const credential = await this.webauthnService.createCredential(this.credentialOptions); + if (credential === undefined) { this.currentStep = "credentialCreationFailed"; + return; } } } finally {