mirror of
https://github.com/bitwarden/browser.git
synced 2025-01-24 21:41:33 +01:00
[PM-2014] feat: add support for creation token
This commit is contained in:
parent
92e97d722b
commit
607c585dbf
@ -0,0 +1,13 @@
|
||||
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
import { BaseResponse } from "@bitwarden/common/models/response/base.response";
|
||||
|
||||
export class CredentialCreateOptionsResponse extends BaseResponse {
|
||||
options: ChallengeResponse;
|
||||
token: string;
|
||||
|
||||
constructor(response: unknown) {
|
||||
super(response);
|
||||
this.options = new ChallengeResponse(this.getResponseProperty("options"));
|
||||
this.token = this.getResponseProperty("token");
|
||||
}
|
||||
}
|
@ -2,11 +2,12 @@ import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
import { CoreAuthModule } from "../../core.module";
|
||||
|
||||
import { CredentialCreateOptionsResponse } from "./response/credential-create-options.response";
|
||||
|
||||
@Injectable({ providedIn: CoreAuthModule })
|
||||
export class WebauthnApiService {
|
||||
constructor(
|
||||
@ -14,9 +15,11 @@ export class WebauthnApiService {
|
||||
private userVerificationService: UserVerificationService
|
||||
) {}
|
||||
|
||||
async getChallenge(verification: Verification): Promise<ChallengeResponse> {
|
||||
async getCredentialCreateOptions(
|
||||
verification: Verification
|
||||
): Promise<CredentialCreateOptionsResponse> {
|
||||
const request = await this.userVerificationService.buildRequest(verification);
|
||||
const response = await this.apiService.send("POST", "/webauthn/options", request, true, true);
|
||||
return new ChallengeResponse(response);
|
||||
return new CredentialCreateOptionsResponse(response);
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
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 { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
|
||||
|
||||
import { CredentialCreateOptionsResponse } from "./response/credential-create-options.response";
|
||||
import { WebauthnApiService } from "./webauthn-api.service";
|
||||
import { WebauthnService } from "./webauthn.service";
|
||||
|
||||
@ -33,30 +33,32 @@ describe("WebauthnService", () => {
|
||||
|
||||
describe("getNewCredentialOptions", () => {
|
||||
it("should return undefined and show toast when api service call throws", async () => {
|
||||
apiService.getChallenge.mockRejectedValue(new Error("Mock error"));
|
||||
apiService.getCredentialCreateOptions.mockRejectedValue(new Error("Mock error"));
|
||||
const verification = createVerification();
|
||||
|
||||
const result = await webauthnService.getNewCredentialOptions(verification);
|
||||
const result = await webauthnService.getCredentialCreateOptions(verification);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(platformUtilsService.showToast).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return options when api service call is successfull", async () => {
|
||||
const challenge: ChallengeResponse = Symbol() as any;
|
||||
apiService.getChallenge.mockResolvedValue(challenge);
|
||||
const options = Symbol() as any;
|
||||
const token = Symbol() as any;
|
||||
const response = { options, token } as CredentialCreateOptionsResponse;
|
||||
apiService.getCredentialCreateOptions.mockResolvedValue(response);
|
||||
const verification = createVerification();
|
||||
|
||||
const result = await webauthnService.getNewCredentialOptions(verification);
|
||||
const result = await webauthnService.getCredentialCreateOptions(verification);
|
||||
|
||||
expect(result).toEqual({ challenge });
|
||||
expect(result).toEqual({ options, token });
|
||||
});
|
||||
});
|
||||
|
||||
describe("createCredential", () => {
|
||||
it("should return undefined when navigator.credentials throws", async () => {
|
||||
credentials.create.mockRejectedValue(new Error("Mocked error"));
|
||||
const options = createNewCredentialOptions();
|
||||
const options = createCredentialCreateOptions();
|
||||
|
||||
const result = await webauthnService.createCredential(options);
|
||||
|
||||
@ -66,7 +68,7 @@ describe("WebauthnService", () => {
|
||||
it("should return credential when navigator.credentials does not throw", async () => {
|
||||
const credential: Credential = Symbol() as any;
|
||||
credentials.create.mockResolvedValue(credential);
|
||||
const options = createNewCredentialOptions();
|
||||
const options = createCredentialCreateOptions();
|
||||
|
||||
const result = await webauthnService.createCredential(options);
|
||||
|
||||
@ -82,6 +84,6 @@ function createVerification(): Verification {
|
||||
};
|
||||
}
|
||||
|
||||
function createNewCredentialOptions(): NewCredentialOptionsView {
|
||||
return new NewCredentialOptionsView(Symbol() as any);
|
||||
function createCredentialCreateOptions(): CredentialCreateOptionsView {
|
||||
return new CredentialCreateOptionsView(Symbol() as any, Symbol() as any);
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { Injectable, Optional } from "@angular/core";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
import { CoreAuthModule } from "../../core.module";
|
||||
import { NewCredentialOptionsView } from "../../views/new-credential-options.view";
|
||||
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
|
||||
|
||||
import { WebauthnApiService } from "./webauthn-api.service";
|
||||
|
||||
@ -20,17 +21,19 @@ export class WebauthnService {
|
||||
private apiService: WebauthnApiService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
@Optional() credentials: CredentialsContainer
|
||||
@Optional() credentials?: CredentialsContainer,
|
||||
@Optional() private logService?: LogService
|
||||
) {
|
||||
// Default parameters don't work when used with Angular DI
|
||||
this.credentials = credentials ?? navigator.credentials;
|
||||
}
|
||||
|
||||
async getNewCredentialOptions(
|
||||
async getCredentialCreateOptions(
|
||||
verification: Verification
|
||||
): Promise<NewCredentialOptionsView | undefined> {
|
||||
): Promise<CredentialCreateOptionsView | undefined> {
|
||||
try {
|
||||
return { challenge: await this.apiService.getChallenge(verification) };
|
||||
const response = await this.apiService.getCredentialCreateOptions(verification);
|
||||
return new CredentialCreateOptionsView(response.options, response.token);
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
||||
this.platformUtilsService.showToast(
|
||||
@ -39,6 +42,7 @@ export class WebauthnService {
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
} else {
|
||||
this.logService?.error(error);
|
||||
this.platformUtilsService.showToast("error", null, this.i18nService.t("unexpectedError"));
|
||||
}
|
||||
return undefined;
|
||||
@ -46,10 +50,10 @@ export class WebauthnService {
|
||||
}
|
||||
|
||||
async createCredential(
|
||||
credentialOptions: NewCredentialOptionsView
|
||||
credentialOptions: CredentialCreateOptionsView
|
||||
): Promise<WebauthnCredentialView | undefined> {
|
||||
const nativeOptions: CredentialCreationOptions = {
|
||||
publicKey: credentialOptions.challenge,
|
||||
publicKey: credentialOptions.options,
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -0,0 +1,5 @@
|
||||
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
|
||||
export class CredentialCreateOptionsView {
|
||||
constructor(readonly options: ChallengeResponse, readonly token: string) {}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { ChallengeResponse } from "@bitwarden/common/auth/models/response/two-factor-web-authn.response";
|
||||
|
||||
export class NewCredentialOptionsView {
|
||||
constructor(readonly challenge: ChallengeResponse) {}
|
||||
}
|
@ -6,7 +6,7 @@ import { DialogServiceAbstraction } from "@bitwarden/angular/services/dialog";
|
||||
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
|
||||
|
||||
import { WebauthnService } from "../../../core";
|
||||
import { NewCredentialOptionsView } from "../../../core/views/new-credential-options.view";
|
||||
import { CredentialCreateOptionsView } from "../../../core/views/credential-create-options.view";
|
||||
|
||||
import { CreatePasskeyFailedIcon } from "./create-passkey-failed.icon";
|
||||
import { CreatePasskeyIcon } from "./create-passkey.icon";
|
||||
@ -39,7 +39,7 @@ export class CreateCredentialDialogComponent {
|
||||
name: ["", Validators.maxLength(50)],
|
||||
}),
|
||||
});
|
||||
protected credentialOptions?: NewCredentialOptionsView;
|
||||
protected credentialOptions?: CredentialCreateOptionsView;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
@ -57,7 +57,7 @@ export class CreateCredentialDialogComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.credentialOptions = await this.webauthnService.getNewCredentialOptions({
|
||||
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions({
|
||||
type: VerificationType.MasterPassword,
|
||||
secret: this.formGroup.value.userVerification.masterPassword,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user