[PM-4168] Enable encryption for registered passkeys (#7074)
* Added enable encryption * various updates and tests added. * fixing linter errors * updated spec file
This commit is contained in:
parent
180d3a99e3
commit
7051f255ed
|
@ -0,0 +1,25 @@
|
|||
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
||||
|
||||
/**
|
||||
* Request sent to the server to save a newly created prf key set for a credential.
|
||||
*/
|
||||
export class EnableCredentialEncryptionRequest {
|
||||
/**
|
||||
* The response received from the authenticator.
|
||||
*/
|
||||
deviceResponse: WebAuthnLoginAssertionResponseRequest;
|
||||
|
||||
/**
|
||||
* An encrypted token containing information the server needs to verify the credential.
|
||||
*/
|
||||
token: string;
|
||||
|
||||
/** Used for vault encryption. See {@link RotateableKeySet.encryptedUserKey } */
|
||||
encryptedUserKey?: string;
|
||||
|
||||
/** Used for vault encryption. See {@link RotateableKeySet.encryptedPublicKey } */
|
||||
encryptedPublicKey?: string;
|
||||
|
||||
/** Used for vault encryption. See {@link RotateableKeySet.encryptedPrivateKey } */
|
||||
encryptedPrivateKey?: string;
|
||||
}
|
|
@ -3,7 +3,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|||
import { WebauthnLoginAuthenticatorResponseRequest } from "./webauthn-login-authenticator-response.request";
|
||||
|
||||
/**
|
||||
* The response received from an authentiator after a successful attestation.
|
||||
* The response received from an authenticator after a successful attestation.
|
||||
* This request is used to save newly created webauthn login credentials to the server.
|
||||
*/
|
||||
export class WebauthnLoginAttestationResponseRequest extends WebauthnLoginAuthenticatorResponseRequest {
|
||||
|
|
|
@ -2,8 +2,10 @@ import { Injectable } from "@angular/core";
|
|||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
|
||||
import { CredentialAssertionOptionsResponse } from "@bitwarden/common/auth/services/webauthn-login/response/credential-assertion-options.response";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
|
||||
import { EnableCredentialEncryptionRequest } from "./request/enable-credential-encryption.request";
|
||||
import { SaveCredentialRequest } from "./request/save-credential.request";
|
||||
import { WebauthnLoginCredentialCreateOptionsResponse } from "./response/webauthn-login-credential-create-options.response";
|
||||
import { WebauthnLoginCredentialResponse } from "./response/webauthn-login-credential.response";
|
||||
|
@ -15,10 +17,29 @@ export class WebAuthnLoginAdminApiService {
|
|||
async getCredentialCreateOptions(
|
||||
request: SecretVerificationRequest,
|
||||
): Promise<WebauthnLoginCredentialCreateOptionsResponse> {
|
||||
const response = await this.apiService.send("POST", "/webauthn/options", request, true, true);
|
||||
const response = await this.apiService.send(
|
||||
"POST",
|
||||
"/webauthn/attestation-options",
|
||||
request,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new WebauthnLoginCredentialCreateOptionsResponse(response);
|
||||
}
|
||||
|
||||
async getCredentialAssertionOptions(
|
||||
request: SecretVerificationRequest,
|
||||
): Promise<CredentialAssertionOptionsResponse> {
|
||||
const response = await this.apiService.send(
|
||||
"POST",
|
||||
"/webauthn/assertion-options",
|
||||
request,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
return new CredentialAssertionOptionsResponse(response);
|
||||
}
|
||||
|
||||
async saveCredential(request: SaveCredentialRequest): Promise<boolean> {
|
||||
await this.apiService.send("POST", "/webauthn", request, true, true);
|
||||
return true;
|
||||
|
@ -31,4 +52,8 @@ export class WebAuthnLoginAdminApiService {
|
|||
async deleteCredential(credentialId: string, request: SecretVerificationRequest): Promise<void> {
|
||||
await this.apiService.send("POST", `/webauthn/${credentialId}/delete`, request, true, true);
|
||||
}
|
||||
|
||||
async updateCredential(request: EnableCredentialEncryptionRequest): Promise<void> {
|
||||
await this.apiService.send("PUT", `/webauthn`, request, true, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import { randomBytes } from "crypto";
|
||||
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { RotateableKeySet } from "@bitwarden/auth";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { WebAuthnLoginPrfCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-crypto.service.abstraction";
|
||||
import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view";
|
||||
import { WebAuthnLoginAssertionResponseRequest } from "@bitwarden/common/auth/services/webauthn-login/request/webauthn-login-assertion-response.request";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
import { PrfKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { CredentialCreateOptionsView } from "../../views/credential-create-options.view";
|
||||
import { PendingWebauthnLoginCredentialView } from "../../views/pending-webauthn-login-credential.view";
|
||||
import { RotateableKeySetService } from "../rotateable-key-set.service";
|
||||
|
||||
import { EnableCredentialEncryptionRequest } from "./request/enable-credential-encryption.request";
|
||||
import { WebAuthnLoginAdminApiService } from "./webauthn-login-admin-api.service";
|
||||
import { WebauthnLoginAdminService } from "./webauthn-login-admin.service";
|
||||
|
||||
|
@ -18,10 +27,13 @@ describe("WebauthnAdminService", () => {
|
|||
let credentials: MockProxy<CredentialsContainer>;
|
||||
let service!: WebauthnLoginAdminService;
|
||||
|
||||
let originalAuthenticatorAssertionResponse!: AuthenticatorAssertionResponse | any;
|
||||
|
||||
beforeAll(() => {
|
||||
// Polyfill missing class
|
||||
window.PublicKeyCredential = class {} as any;
|
||||
window.AuthenticatorAttestationResponse = class {} as any;
|
||||
window.AuthenticatorAssertionResponse = class {} as any;
|
||||
apiService = mock<WebAuthnLoginAdminApiService>();
|
||||
userVerificationService = mock<UserVerificationService>();
|
||||
rotateableKeySetService = mock<RotateableKeySetService>();
|
||||
|
@ -34,6 +46,20 @@ describe("WebauthnAdminService", () => {
|
|||
webAuthnLoginPrfCryptoService,
|
||||
credentials,
|
||||
);
|
||||
|
||||
// Save original global class
|
||||
originalAuthenticatorAssertionResponse = global.AuthenticatorAssertionResponse;
|
||||
// Mock the global AuthenticatorAssertionResponse class b/c the class is only available in secure contexts
|
||||
global.AuthenticatorAssertionResponse = MockAuthenticatorAssertionResponse;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Restore global after all tests are done
|
||||
global.AuthenticatorAssertionResponse = originalAuthenticatorAssertionResponse;
|
||||
});
|
||||
|
||||
describe("createCredential", () => {
|
||||
|
@ -70,6 +96,94 @@ describe("WebauthnAdminService", () => {
|
|||
expect(result.supportsPrf).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("enableCredentialEncryption", () => {
|
||||
it("should call the necessary methods to update the credential", async () => {
|
||||
// Arrange
|
||||
const response = new MockPublicKeyCredential();
|
||||
const prfKeySet = new RotateableKeySet<PrfKey>(
|
||||
new EncString("test_encryptedUserKey"),
|
||||
new EncString("test_encryptedPublicKey"),
|
||||
new EncString("test_encryptedPrivateKey"),
|
||||
);
|
||||
|
||||
const assertionOptions: WebAuthnLoginCredentialAssertionView =
|
||||
new WebAuthnLoginCredentialAssertionView(
|
||||
"enable_credential_encryption_test_token",
|
||||
new WebAuthnLoginAssertionResponseRequest(response),
|
||||
{} as PrfKey,
|
||||
);
|
||||
|
||||
const request = new EnableCredentialEncryptionRequest();
|
||||
request.token = assertionOptions.token;
|
||||
request.deviceResponse = assertionOptions.deviceResponse;
|
||||
request.encryptedUserKey = prfKeySet.encryptedUserKey.encryptedString;
|
||||
request.encryptedPublicKey = prfKeySet.encryptedPublicKey.encryptedString;
|
||||
request.encryptedPrivateKey = prfKeySet.encryptedPrivateKey.encryptedString;
|
||||
|
||||
// Mock the necessary methods and services
|
||||
const createKeySetMock = jest
|
||||
.spyOn(rotateableKeySetService, "createKeySet")
|
||||
.mockResolvedValue(prfKeySet);
|
||||
const updateCredentialMock = jest.spyOn(apiService, "updateCredential").mockResolvedValue();
|
||||
|
||||
// Act
|
||||
await service.enableCredentialEncryption(assertionOptions);
|
||||
|
||||
// Assert
|
||||
expect(createKeySetMock).toHaveBeenCalledWith(assertionOptions.prfKey);
|
||||
expect(updateCredentialMock).toHaveBeenCalledWith(request);
|
||||
});
|
||||
|
||||
it("should throw error when PRF Key is undefined", async () => {
|
||||
// Arrange
|
||||
const response = new MockPublicKeyCredential();
|
||||
|
||||
const assertionOptions: WebAuthnLoginCredentialAssertionView =
|
||||
new WebAuthnLoginCredentialAssertionView(
|
||||
"enable_credential_encryption_test_token",
|
||||
new WebAuthnLoginAssertionResponseRequest(response),
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Mock the necessary methods and services
|
||||
const createKeySetMock = jest
|
||||
.spyOn(rotateableKeySetService, "createKeySet")
|
||||
.mockResolvedValue(null);
|
||||
const updateCredentialMock = jest.spyOn(apiService, "updateCredential").mockResolvedValue();
|
||||
|
||||
// Act
|
||||
try {
|
||||
await service.enableCredentialEncryption(assertionOptions);
|
||||
} catch (error) {
|
||||
// Assert
|
||||
expect(error).toEqual(new Error("invalid credential"));
|
||||
expect(createKeySetMock).not.toHaveBeenCalled();
|
||||
expect(updateCredentialMock).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw error when WehAuthnLoginCredentialAssertionView is undefined", async () => {
|
||||
// Arrange
|
||||
const assertionOptions: WebAuthnLoginCredentialAssertionView = undefined;
|
||||
|
||||
// Mock the necessary methods and services
|
||||
const createKeySetMock = jest
|
||||
.spyOn(rotateableKeySetService, "createKeySet")
|
||||
.mockResolvedValue(null);
|
||||
const updateCredentialMock = jest.spyOn(apiService, "updateCredential").mockResolvedValue();
|
||||
|
||||
// Act
|
||||
try {
|
||||
await service.enableCredentialEncryption(assertionOptions);
|
||||
} catch (error) {
|
||||
// Assert
|
||||
expect(error).toEqual(new Error("invalid credential"));
|
||||
expect(createKeySetMock).not.toHaveBeenCalled();
|
||||
expect(updateCredentialMock).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createCredentialCreateOptions(): CredentialCreateOptionsView {
|
||||
|
@ -115,3 +229,58 @@ function createDeviceResponse({ prf = false }: { prf?: boolean } = {}): PublicKe
|
|||
|
||||
return credential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks for the PublicKeyCredential and AuthenticatorAssertionResponse classes copied from webauthn-login.service.spec.ts
|
||||
*/
|
||||
|
||||
// AuthenticatorAssertionResponse && PublicKeyCredential are only available in secure contexts
|
||||
// so we need to mock them and assign them to the global object to make them available
|
||||
// for the tests
|
||||
class MockAuthenticatorAssertionResponse implements AuthenticatorAssertionResponse {
|
||||
clientDataJSON: ArrayBuffer = randomBytes(32).buffer;
|
||||
authenticatorData: ArrayBuffer = randomBytes(196).buffer;
|
||||
signature: ArrayBuffer = randomBytes(72).buffer;
|
||||
userHandle: ArrayBuffer = randomBytes(16).buffer;
|
||||
|
||||
clientDataJSONB64Str = Utils.fromBufferToUrlB64(this.clientDataJSON);
|
||||
authenticatorDataB64Str = Utils.fromBufferToUrlB64(this.authenticatorData);
|
||||
signatureB64Str = Utils.fromBufferToUrlB64(this.signature);
|
||||
userHandleB64Str = Utils.fromBufferToUrlB64(this.userHandle);
|
||||
}
|
||||
|
||||
class MockPublicKeyCredential implements PublicKeyCredential {
|
||||
authenticatorAttachment = "cross-platform";
|
||||
id = "mockCredentialId";
|
||||
type = "public-key";
|
||||
rawId: ArrayBuffer = randomBytes(32).buffer;
|
||||
rawIdB64Str = Utils.fromBufferToUrlB64(this.rawId);
|
||||
|
||||
response: MockAuthenticatorAssertionResponse = new MockAuthenticatorAssertionResponse();
|
||||
|
||||
// Use random 64 character hex string (32 bytes - matters for symmetric key creation)
|
||||
// to represent the prf key binary data and convert to ArrayBuffer
|
||||
// Creating the array buffer from a known hex value allows us to
|
||||
// assert on the value in tests
|
||||
private prfKeyArrayBuffer: ArrayBuffer = Utils.hexStringToArrayBuffer(
|
||||
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
);
|
||||
|
||||
getClientExtensionResults(): any {
|
||||
return {
|
||||
prf: {
|
||||
results: {
|
||||
first: this.prfKeyArrayBuffer,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static isConditionalMediationAvailable(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
static isUserVerifyingPlatformAuthenticatorAvailable(): Promise<boolean> {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import { BehaviorSubject, filter, from, map, Observable, shareReplay, switchMap,
|
|||
import { PrfKeySet } from "@bitwarden/auth";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { WebAuthnLoginPrfCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login-prf-crypto.service.abstraction";
|
||||
import { WebAuthnLoginCredentialAssertionOptionsView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
|
||||
import { WebAuthnLoginCredentialAssertionView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion.view";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
|
||||
|
@ -12,6 +14,7 @@ import { PendingWebauthnLoginCredentialView } from "../../views/pending-webauthn
|
|||
import { WebauthnLoginCredentialView } from "../../views/webauthn-login-credential.view";
|
||||
import { RotateableKeySetService } from "../rotateable-key-set.service";
|
||||
|
||||
import { EnableCredentialEncryptionRequest } from "./request/enable-credential-encryption.request";
|
||||
import { SaveCredentialRequest } from "./request/save-credential.request";
|
||||
import { WebauthnLoginAttestationResponseRequest } from "./request/webauthn-login-attestation-response.request";
|
||||
import { WebAuthnLoginAdminApiService } from "./webauthn-login-admin-api.service";
|
||||
|
@ -52,14 +55,31 @@ export class WebauthnLoginAdminService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the credential attestation options needed for initiating the WebAuthnLogin credentail creation process.
|
||||
* Get the credential assertion options needed for initiating the WebAuthnLogin credential update process.
|
||||
* The options contains assertion options and other data for the authenticator.
|
||||
* This method requires user verification.
|
||||
*
|
||||
* @param verification User verification data to be used for the request.
|
||||
* @returns The credential assertion options and a token to be used for the credential update request.
|
||||
*/
|
||||
async getCredentialAssertOptions(
|
||||
verification: Verification,
|
||||
): Promise<WebAuthnLoginCredentialAssertionOptionsView> {
|
||||
const request = await this.userVerificationService.buildRequest(verification);
|
||||
const response = await this.apiService.getCredentialAssertionOptions(request);
|
||||
return new WebAuthnLoginCredentialAssertionOptionsView(response.options, response.token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the credential attestation options needed for initiating the WebAuthnLogin credential creation process.
|
||||
* The options contains a challenge and other data for the authenticator.
|
||||
* This method requires user verification.
|
||||
*
|
||||
* @param verification User verification data to be used for the request.
|
||||
* @returns The credential attestation options and a token to be used for the credential creation request.
|
||||
*/
|
||||
async getCredentialCreateOptions(
|
||||
|
||||
async getCredentialAttestationOptions(
|
||||
verification: Verification,
|
||||
): Promise<CredentialCreateOptionsView> {
|
||||
const request = await this.userVerificationService.buildRequest(verification);
|
||||
|
@ -169,6 +189,36 @@ export class WebauthnLoginAdminService {
|
|||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable encryption for a credential that has already been saved to the server.
|
||||
* This will update the KeySet associated with the credential in the database.
|
||||
* We short circuit the process here incase the WebAuthnLoginCredential doesn't support PRF or
|
||||
* if there was a problem with the Credential Assertion.
|
||||
*
|
||||
* @param assertionOptions Options received from the server using `getCredentialAssertOptions`.
|
||||
* @returns void
|
||||
*/
|
||||
async enableCredentialEncryption(
|
||||
assertionOptions: WebAuthnLoginCredentialAssertionView,
|
||||
): Promise<void> {
|
||||
if (assertionOptions === undefined || assertionOptions?.prfKey === undefined) {
|
||||
throw new Error("invalid credential");
|
||||
}
|
||||
|
||||
const prfKeySet: PrfKeySet = await this.rotateableKeySetService.createKeySet(
|
||||
assertionOptions.prfKey,
|
||||
);
|
||||
|
||||
const request = new EnableCredentialEncryptionRequest();
|
||||
request.token = assertionOptions.token;
|
||||
request.deviceResponse = assertionOptions.deviceResponse;
|
||||
request.encryptedUserKey = prfKeySet.encryptedUserKey.encryptedString;
|
||||
request.encryptedPublicKey = prfKeySet.encryptedPublicKey.encryptedString;
|
||||
request.encryptedPrivateKey = prfKeySet.encryptedPrivateKey.encryptedString;
|
||||
await this.apiService.updateCredential(request);
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of webauthn credentials saved on the server.
|
||||
*
|
||||
|
|
|
@ -94,7 +94,7 @@ export class CreateCredentialDialogComponent implements OnInit {
|
|||
}
|
||||
|
||||
try {
|
||||
this.credentialOptions = await this.webauthnService.getCredentialCreateOptions(
|
||||
this.credentialOptions = await this.webauthnService.getCredentialAttestationOptions(
|
||||
this.formGroup.value.userVerification.secret,
|
||||
);
|
||||
} catch (error) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<form [formGroup]="formGroup" [bitSubmit]="submit">
|
||||
<bit-dialog dialogSize="large" [loading]="loading$ | async">
|
||||
<span bitDialogTitle
|
||||
>{{ "enablePasskeyEncryption" | i18n }}
|
||||
<span *ngIf="credential" class="tw-text-sm tw-normal-case tw-text-muted">{{
|
||||
credential.name
|
||||
}}</span>
|
||||
</span>
|
||||
<ng-container bitDialogContent>
|
||||
<ng-container *ngIf="!credential">
|
||||
<i class="bwi bwi-spinner bwi-spin tw-ml-1" aria-hidden="true"></i>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="credential">
|
||||
<p bitTypography="body1">{{ "useForVaultEncryptionInfo" | i18n }}</p>
|
||||
|
||||
<ng-container formGroupName="userVerification">
|
||||
<app-user-verification
|
||||
formControlName="secret"
|
||||
[(invalidSecret)]="invalidSecret"
|
||||
></app-user-verification>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-container bitDialogFooter>
|
||||
<button type="submit" bitButton bitFormButton buttonType="primary">
|
||||
{{ "submit" | i18n }}
|
||||
</button>
|
||||
<button type="button" bitButton bitFormButton buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
|
@ -0,0 +1,91 @@
|
|||
import { DIALOG_DATA, DialogConfig, DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Subject } from "rxjs";
|
||||
import { takeUntil } from "rxjs/operators";
|
||||
|
||||
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
|
||||
import { WebAuthnLoginCredentialAssertionOptionsView } from "@bitwarden/common/auth/models/view/webauthn-login/webauthn-login-credential-assertion-options.view";
|
||||
import { Verification } from "@bitwarden/common/auth/types/verification";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { DialogService } from "@bitwarden/components/src/dialog/dialog.service";
|
||||
|
||||
import { WebauthnLoginAdminService } from "../../../core/services/webauthn-login/webauthn-login-admin.service";
|
||||
import { WebauthnLoginCredentialView } from "../../../core/views/webauthn-login-credential.view";
|
||||
|
||||
export interface EnableEncryptionDialogParams {
|
||||
credentialId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
templateUrl: "enable-encryption-dialog.component.html",
|
||||
})
|
||||
export class EnableEncryptionDialogComponent implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
protected invalidSecret = false;
|
||||
protected formGroup = this.formBuilder.group({
|
||||
userVerification: this.formBuilder.group({
|
||||
secret: [null as Verification | null, Validators.required],
|
||||
}),
|
||||
});
|
||||
|
||||
protected credential?: WebauthnLoginCredentialView;
|
||||
protected credentialOptions?: WebAuthnLoginCredentialAssertionOptionsView;
|
||||
protected loading$ = this.webauthnService.loading$;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) private params: EnableEncryptionDialogParams,
|
||||
private formBuilder: FormBuilder,
|
||||
private dialogRef: DialogRef,
|
||||
private webauthnService: WebauthnLoginAdminService,
|
||||
private webauthnLoginService: WebAuthnLoginServiceAbstraction,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.webauthnService
|
||||
.getCredential$(this.params.credentialId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((credential: any) => (this.credential = credential));
|
||||
}
|
||||
|
||||
submit = async () => {
|
||||
if (this.credential === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialogRef.disableClose = true;
|
||||
try {
|
||||
this.credentialOptions = await this.webauthnService.getCredentialAssertOptions(
|
||||
this.formGroup.value.userVerification.secret,
|
||||
);
|
||||
await this.webauthnService.enableCredentialEncryption(
|
||||
await this.webauthnLoginService.assertCredential(this.credentialOptions),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorResponse && error.statusCode === 400) {
|
||||
this.invalidSecret = true;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.dialogRef.close();
|
||||
};
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strongly typed helper to open a EnableEncryptionDialogComponent
|
||||
* @param dialogService Instance of the dialog service that will be used to open the dialog
|
||||
* @param config Configuration for the dialog
|
||||
*/
|
||||
export const openEnableCredentialDialogComponent = (
|
||||
dialogService: DialogService,
|
||||
config: DialogConfig<EnableEncryptionDialogParams>,
|
||||
) => {
|
||||
return dialogService.open<unknown>(EnableEncryptionDialogComponent, config);
|
||||
};
|
|
@ -39,8 +39,16 @@
|
|||
<span bitTypography="body1" class="tw-text-muted">{{ "usedForEncryption" | i18n }}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="credential.prfStatus === WebauthnLoginCredentialPrfStatus.Supported">
|
||||
<i class="bwi bwi-lock-encrypted"></i>
|
||||
<span bitTypography="body1" class="tw-text-muted">{{ "encryptionNotEnabled" | i18n }}</span>
|
||||
<button
|
||||
type="button"
|
||||
bitLink
|
||||
[disabled]="loading"
|
||||
[attr.aria-label]="('enablePasskeyEncryption' | i18n) + ' ' + credential.name"
|
||||
(click)="enableEncryption(credential.id)"
|
||||
>
|
||||
<i class="bwi bwi-lock-encrypted"></i>
|
||||
{{ "enablePasskeyEncryption" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
<span
|
||||
*ngIf="credential.prfStatus === WebauthnLoginCredentialPrfStatus.Unsupported"
|
||||
|
|
|
@ -11,6 +11,7 @@ import { WebauthnLoginCredentialView } from "../../core/views/webauthn-login-cre
|
|||
|
||||
import { openCreateCredentialDialog } from "./create-credential-dialog/create-credential-dialog.component";
|
||||
import { openDeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
|
||||
import { openEnableCredentialDialogComponent } from "./enable-encryption-dialog/enable-encryption-dialog.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-webauthn-login-settings",
|
||||
|
@ -83,4 +84,8 @@ export class WebauthnLoginSettingsComponent implements OnInit, OnDestroy {
|
|||
protected deleteCredential(credentialId: string) {
|
||||
openDeleteCredentialDialogComponent(this.dialogService, { data: { credentialId } });
|
||||
}
|
||||
|
||||
protected enableEncryption(credentialId: string) {
|
||||
openEnableCredentialDialogComponent(this.dialogService, { data: { credentialId } });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { UserVerificationModule } from "../../shared/components/user-verificatio
|
|||
|
||||
import { CreateCredentialDialogComponent } from "./create-credential-dialog/create-credential-dialog.component";
|
||||
import { DeleteCredentialDialogComponent } from "./delete-credential-dialog/delete-credential-dialog.component";
|
||||
import { EnableEncryptionDialogComponent } from "./enable-encryption-dialog/enable-encryption-dialog.component";
|
||||
import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.component";
|
||||
|
||||
@NgModule({
|
||||
|
@ -16,6 +17,7 @@ import { WebauthnLoginSettingsComponent } from "./webauthn-login-settings.compon
|
|||
WebauthnLoginSettingsComponent,
|
||||
CreateCredentialDialogComponent,
|
||||
DeleteCredentialDialogComponent,
|
||||
EnableEncryptionDialogComponent,
|
||||
],
|
||||
exports: [WebauthnLoginSettingsComponent],
|
||||
})
|
||||
|
|
|
@ -674,8 +674,8 @@
|
|||
"encryptionNotSupported": {
|
||||
"message": "Encryption not supported"
|
||||
},
|
||||
"encryptionNotEnabled": {
|
||||
"message": "Encryption not enabled"
|
||||
"enablePasskeyEncryption": {
|
||||
"message": "Set up encryption"
|
||||
},
|
||||
"usedForEncryption": {
|
||||
"message": "Used for encryption"
|
||||
|
|
Loading…
Reference in New Issue