mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-12 10:14:10 +01:00
Desktop biometrics support (#119)
* Initial work on windows hello support * Switch to use windows.security.credentials.ui UserConsentVerifier * Fix linting warnings * Remove unessesary supportsBiometric from lock screen * Rename biometric.main to windows.biometric.main. Add abstraction for biometric. * Add support for dynamic biometric text. * Add untested darwin implementation * Rename fingerprintUnlock to biometric * Add new functions to cliPlatformUtils.service.ts. * Hide login if biometric is not supported * Export default for biometric.*.main.ts * Remove @nodert-win10-rs4/windows.security.credentials * Add build requirements to readme * Auto prompt biometric when starting the application. * Ensure we support biometric before trying to auto prompt. * Fix review comments and linting errors
This commit is contained in:
parent
94d363bfca
commit
c62f5287cd
11
README.md
11
README.md
@ -3,3 +3,14 @@
|
|||||||
# Bitwarden JavaScript Library
|
# Bitwarden JavaScript Library
|
||||||
|
|
||||||
Common code referenced across Bitwarden JavaScript projects.
|
Common code referenced across Bitwarden JavaScript projects.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Git
|
||||||
|
* node-gyp
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
* *Microsoft Build Tools 2015* in Visual Studio Installer
|
||||||
|
* [Windows 10 SDK 17134](https://developer.microsoft.com/en-us/windows/downloads/sdk-archive/)
|
||||||
|
either by downloading it seperately or through the Visual Studio Installer.
|
||||||
|
15
package-lock.json
generated
15
package-lock.json
generated
@ -254,6 +254,21 @@
|
|||||||
"msgpack5": "^4.0.2"
|
"msgpack5": "^4.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@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=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/commander": {
|
"@types/commander": {
|
||||||
"version": "2.12.2",
|
"version": "2.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"@angular/upgrade": "7.2.1",
|
"@angular/upgrade": "7.2.1",
|
||||||
"@microsoft/signalr": "3.1.0",
|
"@microsoft/signalr": "3.1.0",
|
||||||
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
"@microsoft/signalr-protocol-msgpack": "3.1.0",
|
||||||
|
"@nodert-win10-rs4/windows.security.credentials.ui": "^0.4.4",
|
||||||
"big-integer": "1.6.36",
|
"big-integer": "1.6.36",
|
||||||
"chalk": "2.4.1",
|
"chalk": "2.4.1",
|
||||||
"commander": "2.18.0",
|
"commander": "2.18.0",
|
||||||
|
5
src/abstractions/biometric.main.ts
Normal file
5
src/abstractions/biometric.main.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export abstract class BiometricMain {
|
||||||
|
init: () => Promise<void>;
|
||||||
|
supportsBiometric: () => Promise<boolean>;
|
||||||
|
requestCreate: () => Promise<boolean>;
|
||||||
|
}
|
@ -32,4 +32,6 @@ export abstract class PlatformUtilsService {
|
|||||||
isSelfHost: () => boolean;
|
isSelfHost: () => boolean;
|
||||||
copyToClipboard: (text: string, options?: any) => void;
|
copyToClipboard: (text: string, options?: any) => void;
|
||||||
readFromClipboard: (options?: any) => Promise<string>;
|
readFromClipboard: (options?: any) => Promise<string>;
|
||||||
|
supportsBiometric: () => Promise<boolean>;
|
||||||
|
authenticateBiometric: () => Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { CipherString } from '../models/domain/cipherString';
|
import { CipherString } from '../models/domain/cipherString';
|
||||||
|
|
||||||
export abstract class VaultTimeoutService {
|
export abstract class VaultTimeoutService {
|
||||||
|
biometricLocked: boolean;
|
||||||
pinProtectedKey: CipherString;
|
pinProtectedKey: CipherString;
|
||||||
isLocked: () => Promise<boolean>;
|
isLocked: () => Promise<boolean>;
|
||||||
checkVaultTimeout: () => Promise<void>;
|
checkVaultTimeout: () => Promise<void>;
|
||||||
@ -8,5 +9,6 @@ export abstract class VaultTimeoutService {
|
|||||||
logOut: () => Promise<void>;
|
logOut: () => Promise<void>;
|
||||||
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
|
setVaultTimeoutOptions: (vaultTimeout: number, vaultTimeoutAction: string) => Promise<void>;
|
||||||
isPinLockSet: () => Promise<[boolean, boolean]>;
|
isPinLockSet: () => Promise<[boolean, boolean]>;
|
||||||
|
isBiometricLockSet: () => Promise<boolean>;
|
||||||
clear: () => Promise<any>;
|
clear: () => Promise<any>;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { OnInit } from '@angular/core';
|
import { OnInit } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
import { ApiService } from '../../abstractions/api.service';
|
import { ApiService } from '../../abstractions/api.service';
|
||||||
import { CryptoService } from '../../abstractions/crypto.service';
|
import { CryptoService } from '../../abstractions/crypto.service';
|
||||||
@ -29,6 +30,9 @@ export class LockComponent implements OnInit {
|
|||||||
pinLock: boolean = false;
|
pinLock: boolean = false;
|
||||||
webVaultHostname: string = '';
|
webVaultHostname: string = '';
|
||||||
formPromise: Promise<any>;
|
formPromise: Promise<any>;
|
||||||
|
supportsBiometric: boolean;
|
||||||
|
biometricLock: boolean;
|
||||||
|
biometricText: string;
|
||||||
|
|
||||||
protected successRoute: string = 'vault';
|
protected successRoute: string = 'vault';
|
||||||
protected onSuccessfulSubmit: () => void;
|
protected onSuccessfulSubmit: () => void;
|
||||||
@ -46,12 +50,20 @@ export class LockComponent implements OnInit {
|
|||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
|
this.pinSet = await this.vaultTimeoutService.isPinLockSet();
|
||||||
this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1];
|
this.pinLock = (this.pinSet[0] && this.vaultTimeoutService.pinProtectedKey != null) || this.pinSet[1];
|
||||||
|
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||||
|
this.biometricLock = await this.vaultTimeoutService.isBiometricLockSet();
|
||||||
|
this.biometricText = await this.storageService.get(ConstantsService.biometricText);
|
||||||
this.email = await this.userService.getEmail();
|
this.email = await this.userService.getEmail();
|
||||||
let vaultUrl = this.environmentService.getWebVaultUrl();
|
let vaultUrl = this.environmentService.getWebVaultUrl();
|
||||||
if (vaultUrl == null) {
|
if (vaultUrl == null) {
|
||||||
vaultUrl = 'https://bitwarden.com';
|
vaultUrl = 'https://bitwarden.com';
|
||||||
}
|
}
|
||||||
this.webVaultHostname = Utils.getHostname(vaultUrl);
|
this.webVaultHostname = Utils.getHostname(vaultUrl);
|
||||||
|
this.router.routerState.root.queryParams.pipe(first()).subscribe((params) => {
|
||||||
|
if (this.supportsBiometric && params.promptBiometric) {
|
||||||
|
this.unlockBiometric();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
@ -146,6 +158,18 @@ export class LockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async unlockBiometric() {
|
||||||
|
if (!this.biometricLock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const success = await this.platformUtilsService.authenticateBiometric();
|
||||||
|
|
||||||
|
this.vaultTimeoutService.biometricLocked = !success;
|
||||||
|
if (success) {
|
||||||
|
await this.doContinue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
togglePassword() {
|
togglePassword() {
|
||||||
this.platformUtilsService.eventTrack('Toggled Master Password on Unlock');
|
this.platformUtilsService.eventTrack('Toggled Master Password on Unlock');
|
||||||
this.showPassword = !this.showPassword;
|
this.showPassword = !this.showPassword;
|
||||||
@ -158,6 +182,7 @@ export class LockComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async doContinue() {
|
private async doContinue() {
|
||||||
|
this.vaultTimeoutService.biometricLocked = false;
|
||||||
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey);
|
||||||
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon);
|
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon);
|
||||||
this.messagingService.send('unlocked');
|
this.messagingService.send('unlocked');
|
||||||
|
@ -27,7 +27,7 @@ export class AuthGuardService implements CanActivate {
|
|||||||
if (routerState != null) {
|
if (routerState != null) {
|
||||||
this.messagingService.send('lockedUrl', { url: routerState.url });
|
this.messagingService.send('lockedUrl', { url: routerState.url });
|
||||||
}
|
}
|
||||||
this.router.navigate(['lock']);
|
this.router.navigate(['lock'], { queryParams: { promptBiometric: true }});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,4 +129,12 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
|
|||||||
readFromClipboard(options?: any): Promise<string> {
|
readFromClipboard(options?: any): Promise<string> {
|
||||||
throw new Error('Not implemented.');
|
throw new Error('Not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportsBiometric(): Promise<boolean> {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticateBiometric(): Promise<boolean> {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
32
src/electron/biometric.darwin.main.ts
Normal file
32
src/electron/biometric.darwin.main.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { I18nService, StorageService } from '../abstractions';
|
||||||
|
|
||||||
|
import { ipcMain, systemPreferences } from 'electron';
|
||||||
|
import { BiometricMain } from '../abstractions/biometric.main';
|
||||||
|
import { ConstantsService } from '../services';
|
||||||
|
import { ElectronConstants } from './electronConstants';
|
||||||
|
|
||||||
|
export default class BiometricDarwinMain implements BiometricMain {
|
||||||
|
constructor(private storageService: StorageService, private i18nservice: I18nService) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric());
|
||||||
|
this.storageService.save(ConstantsService.biometricText, 'unlockWithTouchId');
|
||||||
|
|
||||||
|
ipcMain.on('biometric', async (event: any, message: any) => {
|
||||||
|
event.returnValue = await this.requestCreate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
supportsBiometric(): Promise<boolean> {
|
||||||
|
return Promise.resolve(systemPreferences.canPromptTouchID());
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestCreate(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await systemPreferences.promptTouchID(this.i18nservice.t('touchIdConsentMessage'));
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/electron/biometric.windows.main.ts
Normal file
46
src/electron/biometric.windows.main.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import * as util from 'util';
|
||||||
|
|
||||||
|
import {
|
||||||
|
UserConsentVerificationResult,
|
||||||
|
UserConsentVerifier,
|
||||||
|
UserConsentVerifierAvailability,
|
||||||
|
} from '@nodert-win10-rs4/windows.security.credentials.ui';
|
||||||
|
import { I18nService, StorageService } from '../abstractions';
|
||||||
|
|
||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { BiometricMain } from '../abstractions/biometric.main';
|
||||||
|
import { ConstantsService } from '../services';
|
||||||
|
import { ElectronConstants } from './electronConstants';
|
||||||
|
|
||||||
|
const requestVerification: any = util.promisify(UserConsentVerifier.requestVerificationAsync);
|
||||||
|
const checkAvailability: any = util.promisify(UserConsentVerifier.checkAvailabilityAsync);
|
||||||
|
|
||||||
|
const AllowedAvailabilities = [
|
||||||
|
UserConsentVerifierAvailability.available,
|
||||||
|
UserConsentVerifierAvailability.deviceBusy,
|
||||||
|
];
|
||||||
|
|
||||||
|
export default class BiometricWindowsMain implements BiometricMain {
|
||||||
|
constructor(private storageService: StorageService, private i18nservice: I18nService) {}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.storageService.save(ElectronConstants.enableBiometric, await this.supportsBiometric());
|
||||||
|
this.storageService.save(ConstantsService.biometricText, 'unlockWithWindowsHello');
|
||||||
|
|
||||||
|
ipcMain.on('biometric', async (event: any, message: any) => {
|
||||||
|
event.returnValue = await this.requestCreate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async supportsBiometric(): Promise<boolean> {
|
||||||
|
const availability = await checkAvailability();
|
||||||
|
|
||||||
|
return AllowedAvailabilities.includes(availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestCreate(): Promise<boolean> {
|
||||||
|
const verification = await requestVerification(this.i18nservice.t('windowsHelloConsentMessage'));
|
||||||
|
|
||||||
|
return verification === UserConsentVerificationResult.verified;
|
||||||
|
}
|
||||||
|
}
|
@ -5,4 +5,5 @@ export class ElectronConstants {
|
|||||||
static readonly enableStartToTrayKey: string = 'enableStartToTrayKey';
|
static readonly enableStartToTrayKey: string = 'enableStartToTrayKey';
|
||||||
static readonly enableAlwaysOnTopKey: string = 'enableAlwaysOnTopKey';
|
static readonly enableAlwaysOnTopKey: string = 'enableAlwaysOnTopKey';
|
||||||
static readonly minimizeOnCopyToClipboardKey: string = 'minimizeOnCopyToClipboardKey';
|
static readonly minimizeOnCopyToClipboardKey: string = 'minimizeOnCopyToClipboardKey';
|
||||||
|
static readonly enableBiometric: string = 'enabledBiometric';
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
clipboard,
|
clipboard,
|
||||||
|
ipcRenderer,
|
||||||
remote,
|
remote,
|
||||||
shell,
|
shell,
|
||||||
} from 'electron';
|
} from 'electron';
|
||||||
@ -15,8 +16,10 @@ import { DeviceType } from '../../enums/deviceType';
|
|||||||
import { I18nService } from '../../abstractions/i18n.service';
|
import { I18nService } from '../../abstractions/i18n.service';
|
||||||
import { MessagingService } from '../../abstractions/messaging.service';
|
import { MessagingService } from '../../abstractions/messaging.service';
|
||||||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
import { PlatformUtilsService } from '../../abstractions/platformUtils.service';
|
||||||
|
import { StorageService } from '../../abstractions/storage.service';
|
||||||
|
|
||||||
import { AnalyticsIds } from '../../misc/analytics';
|
import { AnalyticsIds } from '../../misc/analytics';
|
||||||
|
import { ElectronConstants } from '../electronConstants';
|
||||||
|
|
||||||
export class ElectronPlatformUtilsService implements PlatformUtilsService {
|
export class ElectronPlatformUtilsService implements PlatformUtilsService {
|
||||||
identityClientId: string;
|
identityClientId: string;
|
||||||
@ -25,7 +28,7 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
|
|||||||
private analyticsIdCache: string = null;
|
private analyticsIdCache: string = null;
|
||||||
|
|
||||||
constructor(private i18nService: I18nService, private messagingService: MessagingService,
|
constructor(private i18nService: I18nService, private messagingService: MessagingService,
|
||||||
private isDesktopApp: boolean) {
|
private isDesktopApp: boolean, private storageService: StorageService) {
|
||||||
this.identityClientId = isDesktopApp ? 'desktop' : 'connector';
|
this.identityClientId = isDesktopApp ? 'desktop' : 'connector';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,4 +206,17 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
|
|||||||
const type = options ? options.type : null;
|
const type = options ? options.type : null;
|
||||||
return Promise.resolve(clipboard.readText(type));
|
return Promise.resolve(clipboard.readText(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportsBiometric(): Promise<boolean> {
|
||||||
|
return this.storageService.get(ElectronConstants.enableBiometric);
|
||||||
|
}
|
||||||
|
|
||||||
|
authenticateBiometric(): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const val = ipcRenderer.sendSync('biometric', {
|
||||||
|
action: 'authenticate',
|
||||||
|
});
|
||||||
|
resolve(val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
101
src/globals.d.ts
vendored
101
src/globals.d.ts
vendored
@ -1,3 +1,104 @@
|
|||||||
declare function escape(s: string): string;
|
declare function escape(s: string): string;
|
||||||
declare function unescape(s: string): string;
|
declare function unescape(s: string): string;
|
||||||
declare module 'duo_web_sdk';
|
declare module 'duo_web_sdk';
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
declare module '@nodert-win10-rs4/windows.security.credentials.ui' {
|
||||||
|
export enum AuthenticationProtocol {
|
||||||
|
basic,
|
||||||
|
digest,
|
||||||
|
ntlm,
|
||||||
|
kerberos,
|
||||||
|
negotiate,
|
||||||
|
credSsp,
|
||||||
|
custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CredentialSaveOption {
|
||||||
|
unselected,
|
||||||
|
selected,
|
||||||
|
hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserConsentVerifierAvailability {
|
||||||
|
available,
|
||||||
|
deviceNotPresent,
|
||||||
|
notConfiguredForUser,
|
||||||
|
disabledByPolicy,
|
||||||
|
deviceBusy,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserConsentVerificationResult {
|
||||||
|
verified,
|
||||||
|
deviceNotPresent,
|
||||||
|
notConfiguredForUser,
|
||||||
|
disabledByPolicy,
|
||||||
|
deviceBusy,
|
||||||
|
retriesExhausted,
|
||||||
|
canceled,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CredentialPickerOptions {
|
||||||
|
targetName: String;
|
||||||
|
previousCredential: Object;
|
||||||
|
message: String;
|
||||||
|
errorCode: Number;
|
||||||
|
customAuthenticationProtocol: String;
|
||||||
|
credentialSaveOption: CredentialSaveOption;
|
||||||
|
caption: String;
|
||||||
|
callerSavesCredential: Boolean;
|
||||||
|
authenticationProtocol: AuthenticationProtocol;
|
||||||
|
alwaysDisplayDialog: Boolean;
|
||||||
|
constructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CredentialPickerResults {
|
||||||
|
credential: Object;
|
||||||
|
credentialDomainName: String;
|
||||||
|
credentialPassword: String;
|
||||||
|
credentialSaveOption: CredentialSaveOption;
|
||||||
|
credentialSaved: Boolean;
|
||||||
|
credentialUserName: String;
|
||||||
|
errorCode: Number;
|
||||||
|
constructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CredentialPicker {
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
static pickAsync(
|
||||||
|
options: CredentialPickerOptions,
|
||||||
|
callback: (error: Error, result: CredentialPickerResults) => void
|
||||||
|
): void;
|
||||||
|
static pickAsync(
|
||||||
|
targetName: String,
|
||||||
|
message: String,
|
||||||
|
callback: (error: Error, result: CredentialPickerResults) => void
|
||||||
|
): void;
|
||||||
|
static pickAsync(
|
||||||
|
targetName: String,
|
||||||
|
message: String,
|
||||||
|
caption: String,
|
||||||
|
callback: (error: Error, result: CredentialPickerResults) => void
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserConsentVerifier {
|
||||||
|
constructor();
|
||||||
|
|
||||||
|
static checkAvailabilityAsync(
|
||||||
|
callback: (
|
||||||
|
error: Error,
|
||||||
|
result: UserConsentVerifierAvailability
|
||||||
|
) => void
|
||||||
|
): void;
|
||||||
|
|
||||||
|
static requestVerificationAsync(
|
||||||
|
message: String,
|
||||||
|
callback: (
|
||||||
|
error: Error,
|
||||||
|
result: UserConsentVerificationResult
|
||||||
|
) => void
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { MessagingService } from '../abstractions/messaging.service';
|
|||||||
import { PlatformUtilsService } from '../abstractions/platformUtils.service';
|
import { PlatformUtilsService } from '../abstractions/platformUtils.service';
|
||||||
import { TokenService } from '../abstractions/token.service';
|
import { TokenService } from '../abstractions/token.service';
|
||||||
import { UserService } from '../abstractions/user.service';
|
import { UserService } from '../abstractions/user.service';
|
||||||
|
import { VaultTimeoutService } from '../abstractions/vaultTimeout.service';
|
||||||
|
|
||||||
export const TwoFactorProviders = {
|
export const TwoFactorProviders = {
|
||||||
[TwoFactorProviderType.Authenticator]: {
|
[TwoFactorProviderType.Authenticator]: {
|
||||||
@ -89,7 +90,7 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
private userService: UserService, private tokenService: TokenService,
|
private userService: UserService, private tokenService: TokenService,
|
||||||
private appIdService: AppIdService, private i18nService: I18nService,
|
private appIdService: AppIdService, private i18nService: I18nService,
|
||||||
private platformUtilsService: PlatformUtilsService, private messagingService: MessagingService,
|
private platformUtilsService: PlatformUtilsService, private messagingService: MessagingService,
|
||||||
private setCryptoKeys = true) { }
|
private vaultTimeoutService: VaultTimeoutService, private setCryptoKeys = true) { }
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t('emailTitle');
|
TwoFactorProviders[TwoFactorProviderType.Email].name = this.i18nService.t('emailTitle');
|
||||||
@ -317,6 +318,7 @@ export class AuthService implements AuthServiceAbstraction {
|
|||||||
await this.cryptoService.setEncPrivateKey(tokenResponse.privateKey);
|
await this.cryptoService.setEncPrivateKey(tokenResponse.privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.vaultTimeoutService.biometricLocked = false;
|
||||||
this.messagingService.send('loggedIn');
|
this.messagingService.send('loggedIn');
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ export class ConstantsService {
|
|||||||
static readonly eventCollectionKey: string = 'eventCollection';
|
static readonly eventCollectionKey: string = 'eventCollection';
|
||||||
static readonly ssoCodeVerifierKey: string = 'ssoCodeVerifier';
|
static readonly ssoCodeVerifierKey: string = 'ssoCodeVerifier';
|
||||||
static readonly ssoStateKey: string = 'ssoState';
|
static readonly ssoStateKey: string = 'ssoState';
|
||||||
|
static readonly biometricUnlockKey: string = 'biometric';
|
||||||
|
static readonly biometricText: string = 'biometricText';
|
||||||
|
|
||||||
readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey;
|
readonly environmentUrlsKey: string = ConstantsService.environmentUrlsKey;
|
||||||
readonly disableGaKey: string = ConstantsService.disableGaKey;
|
readonly disableGaKey: string = ConstantsService.disableGaKey;
|
||||||
@ -51,4 +53,6 @@ export class ConstantsService {
|
|||||||
readonly eventCollectionKey: string = ConstantsService.eventCollectionKey;
|
readonly eventCollectionKey: string = ConstantsService.eventCollectionKey;
|
||||||
readonly ssoCodeVerifierKey: string = ConstantsService.ssoCodeVerifierKey;
|
readonly ssoCodeVerifierKey: string = ConstantsService.ssoCodeVerifierKey;
|
||||||
readonly ssoStateKey: string = ConstantsService.ssoStateKey;
|
readonly ssoStateKey: string = ConstantsService.ssoStateKey;
|
||||||
|
readonly biometricUnlockKey: string = ConstantsService.biometricUnlockKey;
|
||||||
|
readonly biometricText: string = ConstantsService.biometricText;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
this.key = key;
|
this.key = key;
|
||||||
|
|
||||||
const option = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
const option = await this.storageService.get<number>(ConstantsService.vaultTimeoutKey);
|
||||||
if (option != null) {
|
const biometric = await this.storageService.get<boolean>(ConstantsService.biometricUnlockKey);
|
||||||
|
if (option != null && !biometric) {
|
||||||
// if we have a lock option set, we do not store the key
|
// if we have a lock option set, we do not store the key
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -291,7 +292,8 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
async toggleKey(): Promise<any> {
|
async toggleKey(): Promise<any> {
|
||||||
const key = await this.getKey();
|
const key = await this.getKey();
|
||||||
const option = await this.storageService.get(ConstantsService.vaultTimeoutKey);
|
const option = await this.storageService.get(ConstantsService.vaultTimeoutKey);
|
||||||
if (option != null || option === 0) {
|
const biometric = await this.storageService.get(ConstantsService.biometricUnlockKey);
|
||||||
|
if (!biometric && (option != null || option === 0)) {
|
||||||
// if we have a lock option set, clear the key
|
// if we have a lock option set, clear the key
|
||||||
await this.clearKey();
|
await this.clearKey();
|
||||||
this.key = key;
|
this.key = key;
|
||||||
|
@ -16,6 +16,7 @@ import { CipherString } from '../models/domain/cipherString';
|
|||||||
|
|
||||||
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
||||||
pinProtectedKey: CipherString = null;
|
pinProtectedKey: CipherString = null;
|
||||||
|
biometricLocked: boolean = true;
|
||||||
|
|
||||||
private inited = false;
|
private inited = false;
|
||||||
|
|
||||||
@ -42,6 +43,11 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
// Keys aren't stored for a device that is locked or logged out.
|
// Keys aren't stored for a device that is locked or logged out.
|
||||||
async isLocked(): Promise<boolean> {
|
async isLocked(): Promise<boolean> {
|
||||||
const hasKey = await this.cryptoService.hasKey();
|
const hasKey = await this.cryptoService.hasKey();
|
||||||
|
if (hasKey) {
|
||||||
|
if (await this.isBiometricLockSet() && this.biometricLocked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
return !hasKey;
|
return !hasKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +97,17 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (allowSoftLock) {
|
||||||
|
const biometricLocked = await this.isBiometricLockSet();
|
||||||
|
if (biometricLocked) {
|
||||||
|
this.messagingService.send('locked');
|
||||||
|
if (this.lockedCallback != null) {
|
||||||
|
await this.lockedCallback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.cryptoService.clearKey(),
|
this.cryptoService.clearKey(),
|
||||||
this.cryptoService.clearOrgKeys(true),
|
this.cryptoService.clearOrgKeys(true),
|
||||||
@ -127,6 +144,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
|
|||||||
return [protectedPin != null, pinProtectedKey != null];
|
return [protectedPin != null, pinProtectedKey != null];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isBiometricLockSet(): Promise<boolean> {
|
||||||
|
return await this.storageService.get<boolean>(ConstantsService.biometricUnlockKey);
|
||||||
|
}
|
||||||
|
|
||||||
clear(): Promise<any> {
|
clear(): Promise<any> {
|
||||||
this.pinProtectedKey = null;
|
this.pinProtectedKey = null;
|
||||||
return this.storageService.remove(ConstantsService.protectedPin);
|
return this.storageService.remove(ConstantsService.protectedPin);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"lib": ["es5", "es6", "dom"],
|
"lib": ["es5", "es6", "es7", "dom"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
Loading…
Reference in New Issue
Block a user