Add support for no intrusive biometric (#2431)

This commit is contained in:
Oscar Hinton 2022-03-15 13:13:39 +01:00 committed by GitHub
parent 20f475be62
commit d5334360dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 71 additions and 36 deletions

View File

@ -1494,7 +1494,7 @@
"message": "Start the Bitwarden Desktop application"
},
"startDesktopDesc": {
"message": "The Bitwarden Desktop application needs to be started before this function can be used."
"message": "The Bitwarden Desktop application needs to be started before unlock with biometrics can be used."
},
"errorEnableBiometricTitle": {
"message": "Unable to enable biometrics"
@ -1884,5 +1884,8 @@
"example": "name@example.com"
}
}
},
"error": {
"message": "Error"
}
}

View File

@ -115,13 +115,7 @@ export class NativeMessagingBackground {
break;
case "disconnected":
if (this.connecting) {
this.messagingService.send("showDialog", {
text: this.i18nService.t("startDesktopDesc"),
title: this.i18nService.t("startDesktopTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
reject();
reject("startDesktop");
}
this.connected = false;
this.port.disconnect();
@ -192,18 +186,12 @@ export class NativeMessagingBackground {
error = chrome.runtime.lastError.message;
}
if (error != null) {
this.messagingService.send("showDialog", {
text: this.i18nService.t("desktopIntegrationDisabledDesc"),
title: this.i18nService.t("desktopIntegrationDisabledTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
}
this.sharedSecret = null;
this.privateKey = null;
this.connected = false;
reject();
const reason = error != null ? "desktopIntegrationDisabled" : null;
reject(reason);
});
});
}

View File

@ -0,0 +1,17 @@
type BiometricError = {
title: string;
description: string;
};
export type BiometricErrorTypes = "startDesktop" | "desktopIntegrationDisabled";
export const BiometricErrors: Record<BiometricErrorTypes, BiometricError> = {
startDesktop: {
title: "startDesktopTitle",
description: "startDesktopDesc",
},
desktopIntegrationDisabled: {
title: "desktopIntegrationDisabledTitle",
description: "desktopIntegrationDisabledDesc",
},
};

View File

@ -62,7 +62,13 @@
</div>
<div class="box" *ngIf="biometricLock">
<div class="box-footer">
<button type="button" class="btn primary block" (click)="unlockBiometric()" appStopClick>
<button
type="button"
class="btn primary block"
(click)="unlockBiometric()"
appStopClick
[disabled]="pendingBiometric"
>
{{ "unlockWithBiometrics" | i18n }}
</button>
</div>
@ -71,5 +77,9 @@
<button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button>
</p>
<app-private-mode-warning></app-private-mode-warning>
<app-callout *ngIf="biometricError" type="error">{{ biometricError }}</app-callout>
<p class="text-center text-muted" *ngIf="pendingBiometric">
<i class="bwi bwi-spinner bwi-spin" aria-hidden="true"></i> {{ "awaitDesktop" | i18n }}
</p>
</content>
</form>

View File

@ -1,6 +1,5 @@
import { Component, NgZone } from "@angular/core";
import { Router } from "@angular/router";
import Swal from "sweetalert2";
import { LockComponent as BaseLockComponent } from "jslib-angular/components/lock.component";
import { ApiService } from "jslib-common/abstractions/api.service";
@ -14,6 +13,8 @@ import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.se
import { StateService } from "jslib-common/abstractions/state.service";
import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.service";
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
@Component({
selector: "app-lock",
templateUrl: "lock.component.html",
@ -21,6 +22,9 @@ import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.serv
export class LockComponent extends BaseLockComponent {
private isInitialLockScreen: boolean;
biometricError: string;
pendingBiometric = false;
constructor(
router: Router,
i18nService: I18nService,
@ -73,24 +77,22 @@ export class LockComponent extends BaseLockComponent {
return;
}
const div = document.createElement("div");
div.innerHTML = `<div class="swal2-text">${this.i18nService.t("awaitDesktop")}</div>`;
this.pendingBiometric = true;
this.biometricError = null;
Swal.fire({
heightAuto: false,
buttonsStyling: false,
html: div,
showCancelButton: true,
cancelButtonText: this.i18nService.t("cancel"),
showConfirmButton: false,
});
let success;
try {
success = await super.unlockBiometric();
} catch (e) {
const error = BiometricErrors[e as BiometricErrorTypes];
const success = await super.unlockBiometric();
if (error == null) {
this.logService.error("Unknown error: " + e);
}
// Avoid closing the error dialogs
if (success) {
Swal.close();
this.biometricError = this.i18nService.t(error.description);
}
this.pendingBiometric = false;
return success;
}

View File

@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } from "@
import { DomSanitizer } from "@angular/platform-browser";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { IndividualConfig, ToastrService } from "ngx-toastr";
import Swal, { SweetAlertIcon } from "sweetalert2/src/sweetalert2.js";
import Swal, { SweetAlertIcon } from "sweetalert2";
import { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";

View File

@ -1,5 +1,5 @@
@import "~ngx-toastr/toastr";
@import "~sweetalert2/src/sweetalert2.scss";
@import "~#sweetalert2";
@import "variables.scss";
@import "buttons.scss";

View File

@ -1,7 +1,7 @@
import { Component, ElementRef, OnInit, ViewChild } from "@angular/core";
import { FormControl } from "@angular/forms";
import { Router } from "@angular/router";
import Swal from "sweetalert2/src/sweetalert2.js";
import Swal from "sweetalert2";
import { ModalService } from "jslib-angular/services/modal.service";
import { CryptoService } from "jslib-common/abstractions/crypto.service";
@ -15,6 +15,7 @@ import { VaultTimeoutService } from "jslib-common/abstractions/vaultTimeout.serv
import { DeviceType } from "jslib-common/enums/deviceType";
import { BrowserApi } from "../../browser/browserApi";
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
import { SetPinComponent } from "../components/set-pin.component";
import { PopupUtilsService } from "../services/popup-utils.service";
@ -269,6 +270,16 @@ export class SettingsComponent implements OnInit {
.catch((e) => {
// Handle connection errors
this.biometric = false;
const error = BiometricErrors[e as BiometricErrorTypes];
this.platformUtilsService.showDialog(
this.i18nService.t(error.description),
this.i18nService.t(error.title),
this.i18nService.t("ok"),
null,
"error"
);
}),
]);
} else {

View File

@ -168,6 +168,10 @@ const config = {
extensions: [".ts", ".js"],
symlinks: false,
modules: [path.resolve("node_modules")],
alias: {
sweetalert2: require.resolve("sweetalert2/dist/sweetalert2.js"),
"#sweetalert2": require.resolve("sweetalert2/src/sweetalert2.scss"),
},
fallback: {
assert: false,
buffer: require.resolve("buffer/"),