diff --git a/README.md b/README.md
index 0c0a95e6e8..7dd580b0b6 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ The Bitwarden desktop app is written using Electron and Angular. The application
**Requirements**
- [Node.js](https://nodejs.org/)
-- Windows users: To compile the native node modules used in the app you will need the Visual C++ toolset, available through the standard Visual Studio installer (recommended) or by installing [`windows-build-tools`](https://github.com/felixrieseberg/windows-build-tools) through `npm`. See more at [Compiling native Addon modules](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules).
+- Windows users: To compile the native node modules used in the app you will need the *Visual C++ toolset*, available through the standard Visual Studio installer. You will also need to install the *Microsoft Build Tools 2015* and *Windows 10 SDK 17134* as additional dependencies in the Visual Studio installer.
+
**Run the app**
diff --git a/package-lock.json b/package-lock.json
index bb4030057d..9162d666ad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -809,6 +809,21 @@
}
}
},
+ "@nodert-win10-rs4/windows.security.credentials.ui": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/@nodert-win10-rs4/windows.security.credentials.ui/-/windows.security.credentials.ui-0.4.4.tgz",
+ "integrity": "sha512-P+EsJw5MCQXTxp7mwXfNDvIzIYsB6ple+HNg01QjPWg/PJfAodPuxL6XM7l0sPtYHsDYnfnvoefZMdZRa2Z1ig==",
+ "requires": {
+ "nan": "^2.14.1"
+ },
+ "dependencies": {
+ "nan": {
+ "version": "2.14.1",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
+ "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
+ }
+ }
+ },
"@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
diff --git a/package.json b/package.json
index d73b185527..1e8fa107d1 100644
--- a/package.json
+++ b/package.json
@@ -277,6 +277,7 @@
"@angular/upgrade": "7.2.1",
"@microsoft/signalr": "3.1.0",
"@microsoft/signalr-protocol-msgpack": "3.1.0",
+ "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"angular2-toaster": "6.1.0",
"angulartics2": "6.3.0",
"big-integer": "1.6.36",
diff --git a/src/app/accounts/lock.component.html b/src/app/accounts/lock.component.html
index d80b2cbd5a..1f8dd4507d 100644
--- a/src/app/accounts/lock.component.html
+++ b/src/app/accounts/lock.component.html
@@ -36,5 +36,10 @@
{{'logOut' | i18n}}
+
diff --git a/src/app/accounts/lock.component.ts b/src/app/accounts/lock.component.ts
index 5dab61c361..125b2a3ed4 100644
--- a/src/app/accounts/lock.component.ts
+++ b/src/app/accounts/lock.component.ts
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
+import { ApiService } from 'jslib/abstractions/api.service';
import { CryptoService } from 'jslib/abstractions/crypto.service';
import { EnvironmentService } from 'jslib/abstractions/environment.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
@@ -22,8 +23,8 @@ export class LockComponent extends BaseLockComponent {
platformUtilsService: PlatformUtilsService, messagingService: MessagingService,
userService: UserService, cryptoService: CryptoService,
storageService: StorageService, vaultTimeoutService: VaultTimeoutService,
- environmentService: EnvironmentService, stateService: StateService) {
+ environmentService: EnvironmentService, stateService: StateService, apiService: ApiService) {
super(router, i18nService, platformUtilsService, messagingService, userService, cryptoService,
- storageService, vaultTimeoutService, environmentService, stateService);
+ storageService, vaultTimeoutService, environmentService, stateService, apiService);
}
}
diff --git a/src/app/accounts/settings.component.html b/src/app/accounts/settings.component.html
index 3056f02d36..f54b90a2d7 100644
--- a/src/app/accounts/settings.component.html
+++ b/src/app/accounts/settings.component.html
@@ -44,6 +44,14 @@
+
diff --git a/src/app/accounts/settings.component.ts b/src/app/accounts/settings.component.ts
index a178e96cd0..fafa817717 100644
--- a/src/app/accounts/settings.component.ts
+++ b/src/app/accounts/settings.component.ts
@@ -48,6 +48,9 @@ export class SettingsComponent implements OnInit {
clearClipboardOptions: any[];
enableTrayText: string;
enableTrayDescText: string;
+ supportsBiometric: boolean;
+ biometric: boolean;
+ biometricText: string;
constructor(private analytics: Angulartics2, private toasterService: ToasterService,
private i18nService: I18nService, private platformUtilsService: PlatformUtilsService,
@@ -125,6 +128,9 @@ export class SettingsComponent implements OnInit {
this.clearClipboard = await this.storageService.get(ConstantsService.clearClipboardKey);
this.minimizeOnCopyToClipboard = await this.storageService.get(
ElectronConstants.minimizeOnCopyToClipboardKey);
+ this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
+ this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
+ this.biometricText = await this.storageService.get(ConstantsService.biometricText);
}
async saveVaultTimeoutOptions() {
@@ -201,6 +207,25 @@ export class SettingsComponent implements OnInit {
}
}
+ async updateBiometric() {
+ const current = this.biometric;
+ if (this.biometric) {
+ this.biometric = false;
+ } else if (this.supportsBiometric) {
+ this.biometric = await this.platformUtilsService.authenticateBiometric();
+ }
+ if (this.biometric === current) {
+ return;
+ }
+ if (this.biometric) {
+ await this.storageService.save(ConstantsService.biometricUnlockKey, true);
+ } else {
+ await this.storageService.remove(ConstantsService.biometricUnlockKey);
+ }
+ this.vaultTimeoutService.biometricLocked = false;
+ await this.cryptoService.toggleKey();
+ }
+
async saveFavicons() {
await this.storageService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
await this.stateService.save(ConstantsService.disableFaviconKey, this.disableFavicons);
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index f05db5bb27..c09773da6b 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -231,6 +231,7 @@ export class AppComponent implements OnInit {
this.policyService.clear(userId),
]);
+ this.vaultTimeoutService.biometricLocked = true;
this.searchService.clearIndex();
this.authService.logOut(async () => {
this.analytics.eventTrack.next({ action: 'Logged Out' });
diff --git a/src/app/services.module.ts b/src/app/services.module.ts
index fa69f847c2..bd9187b374 100644
--- a/src/app/services.module.ts
+++ b/src/app/services.module.ts
@@ -89,8 +89,8 @@ const i18nService = new I18nService(window.navigator.language, './locales');
const stateService = new StateService();
const broadcasterService = new BroadcasterService();
const messagingService = new ElectronRendererMessagingService(broadcasterService);
-const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true);
const storageService: StorageServiceAbstraction = new ElectronStorageService(remote.app.getPath('userData'));
+const platformUtilsService = new ElectronPlatformUtilsService(i18nService, messagingService, true, storageService);
const secureStorageService: StorageServiceAbstraction = new ElectronRendererSecureStorageService();
const cryptoFunctionService: CryptoFunctionServiceAbstraction = new WebCryptoFunctionService(window,
platformUtilsService);
@@ -118,8 +118,8 @@ const syncService = new SyncService(userService, apiService, settingsService,
const passwordGenerationService = new PasswordGenerationService(cryptoService, storageService, policyService);
const totpService = new TotpService(storageService, cryptoFunctionService);
const containerService = new ContainerService(cryptoService);
-const authService = new AuthService(cryptoService, apiService,
- userService, tokenService, appIdService, i18nService, platformUtilsService, messagingService);
+const authService = new AuthService(cryptoService, apiService, userService, tokenService, appIdService,
+ i18nService, platformUtilsService, messagingService, vaultTimeoutService);
const exportService = new ExportService(folderService, cipherService, apiService);
const auditService = new AuditService(cryptoFunctionService, apiService);
const notificationsService = new NotificationsService(userService, syncService, appIdService,
diff --git a/src/locales/en/messages.json b/src/locales/en/messages.json
index 607f16d78b..13d6164d8d 100644
--- a/src/locales/en/messages.json
+++ b/src/locales/en/messages.json
@@ -1237,6 +1237,18 @@
"yourVaultIsLockedPinCode": {
"message": "Your vault is locked. Verify your PIN code to continue."
},
+ "unlockWithWindowsHello": {
+ "message": "Unlock with Windows Hello"
+ },
+ "windowsHelloConsentMessage": {
+ "message": "Verify for Bitwarden."
+ },
+ "unlockWithTouchId": {
+ "message": "Unlock with Touch ID"
+ },
+ "touchIdConsentMessage": {
+ "message": "Verify for Bitwarden."
+ },
"lockWithMasterPassOnRestart": {
"message": "Lock with master password on restart"
},
diff --git a/src/main.ts b/src/main.ts
index ee5968bd72..88f3c09aa8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -9,6 +9,7 @@ import { PowerMonitorMain } from './main/powerMonitor.main';
import { ConstantsService } from 'jslib/services/constants.service';
+import { BiometricMain } from 'jslib/abstractions/biometric.main';
import { ElectronConstants } from 'jslib/electron/electronConstants';
import { KeytarStorageListener } from 'jslib/electron/keytarStorageListener';
import { ElectronLogService } from 'jslib/electron/services/electronLog.service';
@@ -31,6 +32,7 @@ export class Main {
menuMain: MenuMain;
powerMonitorMain: PowerMonitorMain;
trayMain: TrayMain;
+ biometricMain: BiometricMain;
constructor() {
// Set paths for portable builds
@@ -105,6 +107,14 @@ export class Main {
});
this.keytarStorageListener = new KeytarStorageListener('Bitwarden');
+
+ if (process.platform === 'win32') {
+ const BiometricWindowsMain = require('jslib/electron/biometric.windows.main').default;
+ this.biometricMain = new BiometricWindowsMain(this.storageService, this.i18nService);
+ } else if (process.platform === 'darwin') {
+ const BiometricDarwinMain = require('jslib/electron/biometric.darwin.main').default;
+ this.biometricMain = new BiometricDarwinMain(this.storageService, this.i18nService);
+ }
}
bootstrap() {
@@ -125,6 +135,9 @@ export class Main {
}
this.powerMonitorMain.init();
await this.updaterMain.init();
+ if (this.biometricMain != null) {
+ await this.biometricMain.init();
+ }
}, (e: any) => {
// tslint:disable-next-line
console.error(e);
diff --git a/src/package.json b/src/package.json
index 337daec17c..2eb5721434 100644
--- a/src/package.json
+++ b/src/package.json
@@ -12,6 +12,7 @@
"url": "https://github.com/bitwarden/desktop"
},
"dependencies": {
+ "@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
"desktop-idle": "1.1.2",
"electron-log": "2.2.17",
"electron-store": "1.3.0",