Initial work of biometric unlock for browser
This commit is contained in:
parent
296ccb6829
commit
f311101ed9
|
@ -777,6 +777,12 @@
|
||||||
"integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=",
|
"integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/firefox-webext-browser": {
|
||||||
|
"version": "78.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/firefox-webext-browser/-/firefox-webext-browser-78.0.1.tgz",
|
||||||
|
"integrity": "sha512-0d7oiI9K6Y4efP4Crl3JB88zYl7vaRdLtumqz8v6axMF8RCnK0NaGUjL4DnyQ7GLPo98b+s0BSRalaxAXgvPAQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/jasmine": {
|
"@types/jasmine": {
|
||||||
"version": "3.3.12",
|
"version": "3.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.12.tgz",
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
"@angular/compiler-cli": "^9.1.12",
|
"@angular/compiler-cli": "^9.1.12",
|
||||||
"@ngtools/webpack": "^9.1.12",
|
"@ngtools/webpack": "^9.1.12",
|
||||||
"@types/chrome": "^0.0.73",
|
"@types/chrome": "^0.0.73",
|
||||||
|
"@types/firefox-webext-browser": "^78.0.1",
|
||||||
"@types/jasmine": "^3.3.12",
|
"@types/jasmine": "^3.3.12",
|
||||||
"@types/lunr": "^2.3.3",
|
"@types/lunr": "^2.3.3",
|
||||||
"@types/mousetrap": "^1.6.0",
|
"@types/mousetrap": "^1.6.0",
|
||||||
|
@ -48,7 +49,6 @@
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^1.0.0",
|
"css-loader": "^1.0.0",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
"mini-css-extract-plugin": "^0.9.0",
|
|
||||||
"file-loader": "^2.0.0",
|
"file-loader": "^2.0.0",
|
||||||
"gulp": "^4.0.0",
|
"gulp": "^4.0.0",
|
||||||
"gulp-filter": "^5.1.0",
|
"gulp-filter": "^5.1.0",
|
||||||
|
@ -68,6 +68,7 @@
|
||||||
"karma-jasmine": "^2.0.1",
|
"karma-jasmine": "^2.0.1",
|
||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.4.0",
|
||||||
"karma-typescript": "^4.0.0",
|
"karma-typescript": "^4.0.0",
|
||||||
|
"mini-css-extract-plugin": "^0.9.0",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^4.13.1",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"style-loader": "^0.23.0",
|
"style-loader": "^0.23.0",
|
||||||
|
|
|
@ -1247,6 +1247,12 @@
|
||||||
"yourVaultIsLockedPinCode": {
|
"yourVaultIsLockedPinCode": {
|
||||||
"message": "Your vault is locked. Verify your PIN code to continue."
|
"message": "Your vault is locked. Verify your PIN code to continue."
|
||||||
},
|
},
|
||||||
|
"unlockWithBiometric": {
|
||||||
|
"message": "Unlock with biometric"
|
||||||
|
},
|
||||||
|
"awaitDesktop": {
|
||||||
|
"message": "Awaiting biometric confirmation from desktop application."
|
||||||
|
},
|
||||||
"lockWithMasterPassOnRestart": {
|
"lockWithMasterPassOnRestart": {
|
||||||
"message": "Lock with master password on browser restart"
|
"message": "Lock with master password on browser restart"
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,13 +4,3 @@ const bitwardenMain = (window as any).bitwardenMain = new MainBackground();
|
||||||
bitwardenMain.bootstrap().then(() => {
|
bitwardenMain.bootstrap().then(() => {
|
||||||
// Finished bootstrapping
|
// Finished bootstrapping
|
||||||
});
|
});
|
||||||
|
|
||||||
const port = chrome.runtime.connectNative('com.8bit.bitwarden');
|
|
||||||
|
|
||||||
port.onMessage.addListener((msg: any) => {
|
|
||||||
console.log('Received' + msg);
|
|
||||||
});
|
|
||||||
port.onDisconnect.addListener(() => {
|
|
||||||
console.log('Disconnected');
|
|
||||||
});
|
|
||||||
port.postMessage({ text: 'Hello, my_application' });
|
|
||||||
|
|
|
@ -55,8 +55,8 @@ export default class ContextMenusBackground {
|
||||||
private async cipherAction(info: any) {
|
private async cipherAction(info: any) {
|
||||||
const id = info.menuItemId.split('_')[1];
|
const id = info.menuItemId.split('_')[1];
|
||||||
if (id === 'noop') {
|
if (id === 'noop') {
|
||||||
if (chrome.browserAction && chrome.browserAction.openPopup) {
|
if (chrome.browserAction && (chrome.browserAction as any).openPopup) {
|
||||||
chrome.browserAction.openPopup();
|
(chrome.browserAction as any).openPopup();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ import I18nService from '../services/i18n.service';
|
||||||
import { PopupUtilsService } from '../popup/services/popup-utils.service';
|
import { PopupUtilsService } from '../popup/services/popup-utils.service';
|
||||||
|
|
||||||
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service';
|
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service';
|
||||||
|
import { NativeMessagingBackground } from './nativeMessaging.background';
|
||||||
|
|
||||||
export default class MainBackground {
|
export default class MainBackground {
|
||||||
messagingService: MessagingServiceAbstraction;
|
messagingService: MessagingServiceAbstraction;
|
||||||
|
@ -137,8 +138,11 @@ export default class MainBackground {
|
||||||
private menuOptionsLoaded: any[] = [];
|
private menuOptionsLoaded: any[] = [];
|
||||||
private syncTimeout: any;
|
private syncTimeout: any;
|
||||||
private isSafari: boolean;
|
private isSafari: boolean;
|
||||||
|
nativeMessagingBackground: NativeMessagingBackground;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.nativeMessagingBackground = new NativeMessagingBackground();
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
this.messagingService = new BrowserMessagingService();
|
this.messagingService = new BrowserMessagingService();
|
||||||
this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService,
|
this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService,
|
||||||
|
@ -146,7 +150,7 @@ export default class MainBackground {
|
||||||
if (this.systemService != null) {
|
if (this.systemService != null) {
|
||||||
this.systemService.clearClipboard(clipboardValue, clearMs);
|
this.systemService.clearClipboard(clipboardValue, clearMs);
|
||||||
}
|
}
|
||||||
});
|
}, this.nativeMessagingBackground);
|
||||||
this.storageService = new BrowserStorageService(this.platformUtilsService);
|
this.storageService = new BrowserStorageService(this.platformUtilsService);
|
||||||
this.secureStorageService = new BrowserStorageService(this.platformUtilsService);
|
this.secureStorageService = new BrowserStorageService(this.platformUtilsService);
|
||||||
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
|
this.i18nService = new I18nService(BrowserApi.getUILanguage(window));
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { BrowserApi } from "../browser/browserApi";
|
||||||
|
|
||||||
|
export class NativeMessagingBackground {
|
||||||
|
private connected = false;
|
||||||
|
private port: browser.runtime.Port | chrome.runtime.Port;
|
||||||
|
|
||||||
|
private resolver: any = null;
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
this.port = BrowserApi.connectNative("com.8bit.bitwarden");
|
||||||
|
|
||||||
|
this.connected = true;
|
||||||
|
this.port.onMessage.addListener((msg: any) => {
|
||||||
|
if (this.resolver) {
|
||||||
|
this.resolver(msg);
|
||||||
|
} else {
|
||||||
|
console.error('NO RESOLVER');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.port.onDisconnect.addListener(() => {
|
||||||
|
this.connected = false;
|
||||||
|
console.log('Disconnected');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(message: object) {
|
||||||
|
// If not connected, try to connect
|
||||||
|
if (!this.connected) {
|
||||||
|
this.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await(): Promise<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.resolver = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -221,4 +221,12 @@ export class BrowserApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static connectNative(application: string): browser.runtime.Port | chrome.runtime.Port {
|
||||||
|
if (BrowserApi.isWebExtensionsApi) {
|
||||||
|
return browser.runtime.connectNative(application);
|
||||||
|
} else if (BrowserApi.isChromeApi) {
|
||||||
|
return chrome.runtime.connectNative(application);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
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 var opr: any;
|
declare var opr: any;
|
||||||
declare var chrome: any;
|
|
||||||
declare var browser: any;
|
|
||||||
declare var safari: any;
|
declare var safari: any;
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
<label for="pin">{{'unlockWithPin' | i18n}}</label>
|
<label for="pin">{{'unlockWithPin' | i18n}}</label>
|
||||||
<input id="pin" type="checkbox" (change)="updatePin()" [(ngModel)]="pin">
|
<input id="pin" type="checkbox" (change)="updatePin()" [(ngModel)]="pin">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box-content-row box-content-row-checkbox" appBoxRow>
|
||||||
|
<label for="biometric">{{'unlockWithBiometric' | i18n}}</label>
|
||||||
|
<input id="biometric" type="checkbox" (change)="updateBiometric()" [ngModel]="biometric">
|
||||||
|
</div>
|
||||||
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
|
<a class="box-content-row box-content-row-flex text-default" href="#" appStopClick appBlurClick
|
||||||
(click)="lock()">
|
(click)="lock()">
|
||||||
<div class="row-main">{{'lockNow' | i18n}}</div>
|
<div class="row-main">{{'lockNow' | i18n}}</div>
|
||||||
|
|
|
@ -51,6 +51,7 @@ export class SettingsComponent implements OnInit {
|
||||||
vaultTimeoutActions: any[];
|
vaultTimeoutActions: any[];
|
||||||
vaultTimeoutAction: string;
|
vaultTimeoutAction: string;
|
||||||
pin: boolean = null;
|
pin: boolean = null;
|
||||||
|
biometric: boolean = null;
|
||||||
previousVaultTimeout: number = null;
|
previousVaultTimeout: number = null;
|
||||||
|
|
||||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
||||||
|
@ -100,6 +101,7 @@ export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
||||||
this.pin = pinSet[0] || pinSet[1];
|
this.pin = pinSet[0] || pinSet[1];
|
||||||
|
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveVaultTimeout(newValue: number) {
|
async saveVaultTimeout(newValue: number) {
|
||||||
|
@ -204,6 +206,43 @@ export class SettingsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateBiometric() {
|
||||||
|
const current = this.biometric;
|
||||||
|
if (this.biometric) {
|
||||||
|
this.biometric = false;
|
||||||
|
} else {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = `<div class="swal2-text">${this.i18nService.t('awaitDesktop')}</div>`;
|
||||||
|
|
||||||
|
const submitted = Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
buttonsStyling: false,
|
||||||
|
html: div,
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t('cancel'),
|
||||||
|
showConfirmButton: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Show waiting message
|
||||||
|
this.biometric = await this.platformUtilsService.authenticateBiometric();
|
||||||
|
Swal.close();
|
||||||
|
|
||||||
|
if (this.biometric == false) {
|
||||||
|
this.platformUtilsService.showToast("error", "Unable to enable biometrics", "Ensure the desktop application is running, and browser integration is enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 lock() {
|
async lock() {
|
||||||
this.analytics.eventTrack.next({ action: 'Lock Now' });
|
this.analytics.eventTrack.next({ action: 'Lock Now' });
|
||||||
await this.vaultTimeoutService.lock(true);
|
await this.vaultTimeoutService.lock(true);
|
||||||
|
|
|
@ -27,7 +27,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
|
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36',
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.ChromeExtension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
|
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: {},
|
value: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.OperaExtension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43',
|
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.74 Safari/537.36 Edg/79.0.309.43',
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: true,
|
value: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension);
|
||||||
|
|
||||||
Object.defineProperty(window, 'safariAppExtension', {
|
Object.defineProperty(window, 'safariAppExtension', {
|
||||||
|
@ -92,7 +92,7 @@ describe('Browser Utils Service', () => {
|
||||||
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40',
|
value: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.97 Safari/537.36 Vivaldi/1.94.1008.40',
|
||||||
});
|
});
|
||||||
|
|
||||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
|
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { BrowserApi } from '../browser/browserApi';
|
import { BrowserApi } from '../browser/browserApi';
|
||||||
import { SafariApp } from '../browser/safariApp';
|
import { SafariApp } from '../browser/safariApp';
|
||||||
|
import { NativeMessagingBackground } from '../background/nativeMessaging.background';
|
||||||
|
|
||||||
import { DeviceType } from 'jslib/enums/deviceType';
|
import { DeviceType } from 'jslib/enums/deviceType';
|
||||||
|
|
||||||
|
@ -18,7 +19,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
||||||
private analyticsIdCache: string = null;
|
private analyticsIdCache: string = null;
|
||||||
|
|
||||||
constructor(private messagingService: MessagingService,
|
constructor(private messagingService: MessagingService,
|
||||||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void) { }
|
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||||
|
private nativeMessagingBackground: NativeMessagingBackground) { }
|
||||||
|
|
||||||
getDevice(): DeviceType {
|
getDevice(): DeviceType {
|
||||||
if (this.deviceCache) {
|
if (this.deviceCache) {
|
||||||
|
@ -288,11 +290,16 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
||||||
}
|
}
|
||||||
|
|
||||||
supportsBiometric() {
|
supportsBiometric() {
|
||||||
return Promise.resolve(false);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateBiometric() {
|
async authenticateBiometric() {
|
||||||
return Promise.resolve(false);
|
const responsePromise = this.nativeMessagingBackground.await();
|
||||||
|
this.nativeMessagingBackground.send({'command': 'biometricUnlock'});
|
||||||
|
|
||||||
|
const response = await responsePromise;
|
||||||
|
|
||||||
|
return response.response == 'unlocked';
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebarViewName(): string {
|
sidebarViewName(): string {
|
||||||
|
|
|
@ -10,7 +10,9 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"types": [
|
"types": [
|
||||||
"jasmine",
|
"jasmine",
|
||||||
"sweetalert2"
|
"sweetalert2",
|
||||||
|
"@types/chrome",
|
||||||
|
"@types/firefox-webext-browser"
|
||||||
],
|
],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
Loading…
Reference in New Issue