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}} +
+ + {{biometricText | 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",