mirror of
https://github.com/bitwarden/desktop.git
synced 2024-11-30 12:54:31 +01:00
Biometric support (#470)
* Initial work on windows hello support * Hide login button if not enabled * Add windows.security.credentials.ui dependency to desktop as well. * Only enable biometric on windows. * Add support for dynamic biometric text. * Add untested darwin implementation * Ensure we support biometric before showing login with windows hello / touchid. * Ensure compatability with latest jslib * Only require module on use. * Add windows.security.credentials.ui to src/package.json. * Update requirements * Update consent messages for biometrics
This commit is contained in:
parent
560b58f9de
commit
26b023a3cb
@ -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**
|
||||
|
||||
|
15
package-lock.json
generated
15
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -36,5 +36,10 @@
|
||||
{{'logOut' | i18n}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="button" *ngIf="supportsBiometric && biometricLock">
|
||||
<a class="btn primary block" appBlurClick (click)="unlockBiometric()">
|
||||
<i class="fa fa-unlock-alt" aria-hidden="true"></i> <b>{{biometricText | i18n}}</b>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,14 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="supportsBiometric">
|
||||
<div class="checkbox">
|
||||
<label for="biometric">
|
||||
<input id="biometric" type="checkbox" name="biometric" [checked]="biometric" (change)="updateBiometric()">
|
||||
{{biometricText | i18n}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box">
|
||||
|
@ -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<number>(ConstantsService.clearClipboardKey);
|
||||
this.minimizeOnCopyToClipboard = await this.storageService.get<boolean>(
|
||||
ElectronConstants.minimizeOnCopyToClipboardKey);
|
||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
||||
this.biometricText = await this.storageService.get<string>(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);
|
||||
|
@ -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' });
|
||||
|
@ -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,
|
||||
|
@ -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"
|
||||
},
|
||||
|
13
src/main.ts
13
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);
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user