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" "message": "Start the Bitwarden Desktop application"
}, },
"startDesktopDesc": { "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": { "errorEnableBiometricTitle": {
"message": "Unable to enable biometrics" "message": "Unable to enable biometrics"
@ -1884,5 +1884,8 @@
"example": "name@example.com" "example": "name@example.com"
} }
} }
},
"error": {
"message": "Error"
} }
} }

View File

@ -115,13 +115,7 @@ export class NativeMessagingBackground {
break; break;
case "disconnected": case "disconnected":
if (this.connecting) { if (this.connecting) {
this.messagingService.send("showDialog", { reject("startDesktop");
text: this.i18nService.t("startDesktopDesc"),
title: this.i18nService.t("startDesktopTitle"),
confirmText: this.i18nService.t("ok"),
type: "error",
});
reject();
} }
this.connected = false; this.connected = false;
this.port.disconnect(); this.port.disconnect();
@ -192,18 +186,12 @@ export class NativeMessagingBackground {
error = chrome.runtime.lastError.message; 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.sharedSecret = null;
this.privateKey = null; this.privateKey = null;
this.connected = false; 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>
<div class="box" *ngIf="biometricLock"> <div class="box" *ngIf="biometricLock">
<div class="box-footer"> <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 }} {{ "unlockWithBiometrics" | i18n }}
</button> </button>
</div> </div>
@ -71,5 +77,9 @@
<button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button> <button type="button" appStopClick (click)="logOut()">{{ "logOut" | i18n }}</button>
</p> </p>
<app-private-mode-warning></app-private-mode-warning> <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> </content>
</form> </form>

View File

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

View File

@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, NgZone, OnInit, SecurityContext } from "@
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from "@angular/platform-browser";
import { NavigationEnd, Router, RouterOutlet } from "@angular/router"; import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
import { IndividualConfig, ToastrService } from "ngx-toastr"; 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 { AuthService } from "jslib-common/abstractions/auth.service";
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service"; import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";

View File

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

View File

@ -1,7 +1,7 @@
import { Component, ElementRef, OnInit, ViewChild } from "@angular/core"; import { Component, ElementRef, OnInit, ViewChild } from "@angular/core";
import { FormControl } from "@angular/forms"; import { FormControl } from "@angular/forms";
import { Router } from "@angular/router"; 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 { ModalService } from "jslib-angular/services/modal.service";
import { CryptoService } from "jslib-common/abstractions/crypto.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 { DeviceType } from "jslib-common/enums/deviceType";
import { BrowserApi } from "../../browser/browserApi"; import { BrowserApi } from "../../browser/browserApi";
import { BiometricErrors, BiometricErrorTypes } from "../../models/biometricErrors";
import { SetPinComponent } from "../components/set-pin.component"; import { SetPinComponent } from "../components/set-pin.component";
import { PopupUtilsService } from "../services/popup-utils.service"; import { PopupUtilsService } from "../services/popup-utils.service";
@ -269,6 +270,16 @@ export class SettingsComponent implements OnInit {
.catch((e) => { .catch((e) => {
// Handle connection errors // Handle connection errors
this.biometric = false; 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 { } else {

View File

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