Add support for no intrusive biometric (#2431)
This commit is contained in:
parent
20f475be62
commit
d5334360dd
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
},
|
||||||
|
};
|
|
@ -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>
|
||||||
|
|
|
@ -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,24 +77,22 @@ 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
|
this.biometricError = this.i18nService.t(error.description);
|
||||||
if (success) {
|
|
||||||
Swal.close();
|
|
||||||
}
|
}
|
||||||
|
this.pendingBiometric = false;
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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/"),
|
||||||
|
|
Loading…
Reference in New Issue