2018-06-27 23:50:31 +02:00
|
|
|
import { Component, NgZone } from "@angular/core";
|
|
|
|
|
2022-06-14 17:10:53 +02:00
|
|
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
|
|
|
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 { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
|
|
|
|
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
|
|
|
|
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
|
|
|
import { UpdateTwoFactorWebAuthnDeleteRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnDeleteRequest";
|
|
|
|
import { UpdateTwoFactorWebAuthnRequest } from "@bitwarden/common/models/request/updateTwoFactorWebAuthnRequest";
|
2018-07-21 06:26:16 +02:00
|
|
|
import {
|
|
|
|
ChallengeResponse,
|
2021-03-16 17:44:31 +01:00
|
|
|
TwoFactorWebAuthnResponse,
|
2022-06-14 17:10:53 +02:00
|
|
|
} from "@bitwarden/common/models/response/twoFactorWebAuthnResponse";
|
2018-06-27 23:50:31 +02:00
|
|
|
|
2018-06-28 05:55:50 +02:00
|
|
|
import { TwoFactorBaseComponent } from "./two-factor-base.component";
|
|
|
|
|
2018-06-27 23:50:31 +02:00
|
|
|
@Component({
|
2021-03-16 17:44:31 +01:00
|
|
|
selector: "app-two-factor-webauthn",
|
|
|
|
templateUrl: "two-factor-webauthn.component.html",
|
2018-06-27 23:50:31 +02:00
|
|
|
})
|
2021-03-16 17:44:31 +01:00
|
|
|
export class TwoFactorWebAuthnComponent extends TwoFactorBaseComponent {
|
|
|
|
type = TwoFactorProviderType.WebAuthn;
|
2018-10-08 20:23:30 +02:00
|
|
|
name: string;
|
|
|
|
keys: any[];
|
|
|
|
keyIdAvailable: number = null;
|
|
|
|
keysConfiguredCount = 0;
|
2021-03-16 17:44:31 +01:00
|
|
|
webAuthnError: boolean;
|
|
|
|
webAuthnListening: boolean;
|
|
|
|
webAuthnResponse: PublicKeyCredential;
|
2018-10-08 20:23:30 +02:00
|
|
|
challengePromise: Promise<ChallengeResponse>;
|
2018-06-27 23:50:31 +02:00
|
|
|
formPromise: Promise<any>;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2018-06-28 05:55:50 +02:00
|
|
|
constructor(
|
|
|
|
apiService: ApiService,
|
|
|
|
i18nService: I18nService,
|
2021-12-07 20:41:45 +01:00
|
|
|
platformUtilsService: PlatformUtilsService,
|
2021-11-09 19:24:26 +01:00
|
|
|
private ngZone: NgZone,
|
|
|
|
logService: LogService,
|
|
|
|
userVerificationService: UserVerificationService
|
|
|
|
) {
|
2021-12-07 20:41:45 +01:00
|
|
|
super(apiService, i18nService, platformUtilsService, logService, userVerificationService);
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2018-06-28 05:55:50 +02:00
|
|
|
auth(authResponse: any) {
|
|
|
|
super.auth(authResponse);
|
|
|
|
this.processResponse(authResponse.response);
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2021-11-09 19:24:26 +01:00
|
|
|
async submit() {
|
2021-03-16 17:44:31 +01:00
|
|
|
if (this.webAuthnResponse == null || this.keyIdAvailable == null) {
|
2018-10-08 20:23:30 +02:00
|
|
|
// Should never happen.
|
|
|
|
return Promise.reject();
|
2018-06-27 23:50:31 +02:00
|
|
|
}
|
2018-06-28 05:55:50 +02:00
|
|
|
const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnRequest);
|
|
|
|
request.deviceResponse = this.webAuthnResponse;
|
2018-10-08 20:23:30 +02:00
|
|
|
request.id = this.keyIdAvailable;
|
|
|
|
request.name = this.name;
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2018-06-28 05:55:50 +02:00
|
|
|
return super.enable(async () => {
|
|
|
|
this.formPromise = this.apiService.putTwoFactorWebAuthn(request);
|
|
|
|
const response = await this.formPromise;
|
|
|
|
await this.processResponse(response);
|
2021-12-17 15:57:11 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-10-08 20:23:30 +02:00
|
|
|
disable() {
|
|
|
|
return super.disable(this.formPromise);
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2018-10-08 20:23:30 +02:00
|
|
|
async remove(key: any) {
|
2018-06-28 05:55:50 +02:00
|
|
|
if (this.keysConfiguredCount <= 1 || key.removePromise != null) {
|
2021-12-17 15:57:11 +01:00
|
|
|
return;
|
2018-06-27 23:50:31 +02:00
|
|
|
}
|
2021-03-16 17:44:31 +01:00
|
|
|
const name = key.name != null ? key.name : this.i18nService.t("webAuthnkeyX", key.id);
|
2021-11-09 19:24:26 +01:00
|
|
|
const confirmed = await this.platformUtilsService.showDialog(
|
2021-03-16 17:44:31 +01:00
|
|
|
this.i18nService.t("removeU2fConfirmation"),
|
2018-10-08 20:23:30 +02:00
|
|
|
name,
|
2021-03-16 17:44:31 +01:00
|
|
|
this.i18nService.t("yes"),
|
2018-06-28 05:55:50 +02:00
|
|
|
this.i18nService.t("no"),
|
|
|
|
"warning"
|
|
|
|
);
|
|
|
|
if (!confirmed) {
|
|
|
|
return;
|
|
|
|
}
|
2018-10-08 20:23:30 +02:00
|
|
|
const request = await this.buildRequestModel(UpdateTwoFactorWebAuthnDeleteRequest);
|
|
|
|
request.id = key.id;
|
|
|
|
try {
|
|
|
|
key.removePromise = this.apiService.deleteTwoFactorWebAuthn(request);
|
|
|
|
const response = await key.removePromise;
|
|
|
|
key.removePromise = null;
|
|
|
|
await this.processResponse(response);
|
2021-10-20 18:30:04 +02:00
|
|
|
} catch (e) {
|
|
|
|
this.logService.error(e);
|
2018-10-08 20:23:30 +02:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
2018-10-08 20:23:30 +02:00
|
|
|
|
|
|
|
async readKey() {
|
|
|
|
if (this.keyIdAvailable == null) {
|
2018-06-27 23:50:31 +02:00
|
|
|
return;
|
2018-10-08 20:23:30 +02:00
|
|
|
}
|
2021-11-09 19:24:26 +01:00
|
|
|
const request = await this.buildRequestModel(SecretVerificationRequest);
|
2018-10-08 20:23:30 +02:00
|
|
|
try {
|
2021-03-16 17:44:31 +01:00
|
|
|
this.challengePromise = this.apiService.getTwoFactorWebAuthnChallenge(request);
|
2018-10-08 20:23:30 +02:00
|
|
|
const challenge = await this.challengePromise;
|
|
|
|
this.readDevice(challenge);
|
2021-10-20 18:30:04 +02:00
|
|
|
} catch (e) {
|
|
|
|
this.logService.error(e);
|
2018-10-08 20:23:30 +02:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
private readDevice(webAuthnChallenge: ChallengeResponse) {
|
2022-02-24 12:10:07 +01:00
|
|
|
// eslint-disable-next-line
|
2018-06-27 23:50:31 +02:00
|
|
|
console.log("listening for key...");
|
2021-03-16 17:44:31 +01:00
|
|
|
this.resetWebAuthn(true);
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
navigator.credentials
|
|
|
|
.create({
|
|
|
|
publicKey: webAuthnChallenge,
|
|
|
|
})
|
|
|
|
.then((data: PublicKeyCredential) => {
|
2018-10-08 20:23:30 +02:00
|
|
|
this.ngZone.run(() => {
|
2021-03-16 17:44:31 +01:00
|
|
|
this.webAuthnListening = false;
|
|
|
|
this.webAuthnResponse = data;
|
|
|
|
});
|
2021-12-17 15:57:11 +01:00
|
|
|
})
|
2021-03-16 17:44:31 +01:00
|
|
|
.catch((err) => {
|
2022-02-24 12:10:07 +01:00
|
|
|
// eslint-disable-next-line
|
2021-03-16 17:44:31 +01:00
|
|
|
console.error(err);
|
|
|
|
this.resetWebAuthn(false);
|
|
|
|
// TODO: Should we display the actual error?
|
|
|
|
this.webAuthnError = true;
|
2021-12-17 15:57:11 +01:00
|
|
|
});
|
2018-10-08 20:23:30 +02:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
private resetWebAuthn(listening = false) {
|
|
|
|
this.webAuthnResponse = null;
|
|
|
|
this.webAuthnError = false;
|
|
|
|
this.webAuthnListening = listening;
|
2018-06-27 23:50:31 +02:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
|
2021-03-16 17:44:31 +01:00
|
|
|
private processResponse(response: TwoFactorWebAuthnResponse) {
|
|
|
|
this.resetWebAuthn();
|
2018-10-08 20:23:30 +02:00
|
|
|
this.keys = [];
|
|
|
|
this.keyIdAvailable = null;
|
|
|
|
this.name = null;
|
|
|
|
this.keysConfiguredCount = 0;
|
|
|
|
for (let i = 1; i <= 5; i++) {
|
|
|
|
if (response.keys != null) {
|
2021-02-03 18:41:33 +01:00
|
|
|
const key = response.keys.filter((k) => k.id === i);
|
2018-10-08 20:23:30 +02:00
|
|
|
if (key.length > 0) {
|
|
|
|
this.keysConfiguredCount++;
|
|
|
|
this.keys.push({
|
|
|
|
id: i,
|
|
|
|
name: key[0].name,
|
|
|
|
configured: true,
|
2021-03-16 17:44:31 +01:00
|
|
|
migrated: key[0].migrated,
|
|
|
|
removePromise: null,
|
|
|
|
});
|
|
|
|
continue;
|
2018-10-08 20:23:30 +02:00
|
|
|
}
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
2018-06-27 23:50:31 +02:00
|
|
|
this.keys.push({ id: i, name: null, configured: false, removePromise: null });
|
|
|
|
if (this.keyIdAvailable == null) {
|
|
|
|
this.keyIdAvailable = i;
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
2018-06-27 23:50:31 +02:00
|
|
|
}
|
2018-06-28 05:55:50 +02:00
|
|
|
this.enabled = response.enabled;
|
2021-12-17 15:57:11 +01:00
|
|
|
}
|
2018-06-27 23:50:31 +02:00
|
|
|
}
|