Initial work of biometric unlock for browser
This commit is contained in:
parent
296ccb6829
commit
f311101ed9
|
@ -777,6 +777,12 @@
|
|||
"integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=",
|
||||
"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": {
|
||||
"version": "3.3.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.12.tgz",
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
"@angular/compiler-cli": "^9.1.12",
|
||||
"@ngtools/webpack": "^9.1.12",
|
||||
"@types/chrome": "^0.0.73",
|
||||
"@types/firefox-webext-browser": "^78.0.1",
|
||||
"@types/jasmine": "^3.3.12",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/mousetrap": "^1.6.0",
|
||||
|
@ -48,7 +49,6 @@
|
|||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"del": "^3.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-filter": "^5.1.0",
|
||||
|
@ -68,6 +68,7 @@
|
|||
"karma-jasmine": "^2.0.1",
|
||||
"karma-jasmine-html-reporter": "^1.4.0",
|
||||
"karma-typescript": "^4.0.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.0",
|
||||
|
|
|
@ -1247,6 +1247,12 @@
|
|||
"yourVaultIsLockedPinCode": {
|
||||
"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": {
|
||||
"message": "Lock with master password on browser restart"
|
||||
},
|
||||
|
|
|
@ -4,13 +4,3 @@ const bitwardenMain = (window as any).bitwardenMain = new MainBackground();
|
|||
bitwardenMain.bootstrap().then(() => {
|
||||
// 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) {
|
||||
const id = info.menuItemId.split('_')[1];
|
||||
if (id === 'noop') {
|
||||
if (chrome.browserAction && chrome.browserAction.openPopup) {
|
||||
chrome.browserAction.openPopup();
|
||||
if (chrome.browserAction && (chrome.browserAction as any).openPopup) {
|
||||
(chrome.browserAction as any).openPopup();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ import I18nService from '../services/i18n.service';
|
|||
import { PopupUtilsService } from '../popup/services/popup-utils.service';
|
||||
|
||||
import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service';
|
||||
import { NativeMessagingBackground } from './nativeMessaging.background';
|
||||
|
||||
export default class MainBackground {
|
||||
messagingService: MessagingServiceAbstraction;
|
||||
|
@ -137,8 +138,11 @@ export default class MainBackground {
|
|||
private menuOptionsLoaded: any[] = [];
|
||||
private syncTimeout: any;
|
||||
private isSafari: boolean;
|
||||
nativeMessagingBackground: NativeMessagingBackground;
|
||||
|
||||
constructor() {
|
||||
this.nativeMessagingBackground = new NativeMessagingBackground();
|
||||
|
||||
// Services
|
||||
this.messagingService = new BrowserMessagingService();
|
||||
this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService,
|
||||
|
@ -146,7 +150,7 @@ export default class MainBackground {
|
|||
if (this.systemService != null) {
|
||||
this.systemService.clearClipboard(clipboardValue, clearMs);
|
||||
}
|
||||
});
|
||||
}, this.nativeMessagingBackground);
|
||||
this.storageService = new BrowserStorageService(this.platformUtilsService);
|
||||
this.secureStorageService = new BrowserStorageService(this.platformUtilsService);
|
||||
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 unescape(s: string): string;
|
||||
declare var opr: any;
|
||||
declare var chrome: any;
|
||||
declare var browser: any;
|
||||
declare var safari: any;
|
||||
|
|
|
@ -42,6 +42,10 @@
|
|||
<label for="pin">{{'unlockWithPin' | i18n}}</label>
|
||||
<input id="pin" type="checkbox" (change)="updatePin()" [(ngModel)]="pin">
|
||||
</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
|
||||
(click)="lock()">
|
||||
<div class="row-main">{{'lockNow' | i18n}}</div>
|
||||
|
|
|
@ -51,6 +51,7 @@ export class SettingsComponent implements OnInit {
|
|||
vaultTimeoutActions: any[];
|
||||
vaultTimeoutAction: string;
|
||||
pin: boolean = null;
|
||||
biometric: boolean = null;
|
||||
previousVaultTimeout: number = null;
|
||||
|
||||
constructor(private platformUtilsService: PlatformUtilsService, private i18nService: I18nService,
|
||||
|
@ -100,6 +101,7 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
const pinSet = await this.vaultTimeoutService.isPinLockSet();
|
||||
this.pin = pinSet[0] || pinSet[1];
|
||||
this.biometric = await this.vaultTimeoutService.isBiometricLockSet();
|
||||
}
|
||||
|
||||
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() {
|
||||
this.analytics.eventTrack.next({ action: 'Lock Now' });
|
||||
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',
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
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',
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.FirefoxExtension);
|
||||
});
|
||||
|
||||
|
@ -52,7 +52,7 @@ describe('Browser Utils Service', () => {
|
|||
value: {},
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
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',
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.EdgeExtension);
|
||||
});
|
||||
|
||||
|
@ -77,7 +77,7 @@ describe('Browser Utils Service', () => {
|
|||
value: true,
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.SafariExtension);
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null);
|
||||
const browserPlatformUtilsService = new BrowserPlatformUtilsService(null, null, null);
|
||||
expect(browserPlatformUtilsService.getDevice()).toBe(DeviceType.VivaldiExtension);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { BrowserApi } from '../browser/browserApi';
|
||||
import { SafariApp } from '../browser/safariApp';
|
||||
import { NativeMessagingBackground } from '../background/nativeMessaging.background';
|
||||
|
||||
import { DeviceType } from 'jslib/enums/deviceType';
|
||||
|
||||
|
@ -18,7 +19,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||
private analyticsIdCache: string = null;
|
||||
|
||||
constructor(private messagingService: MessagingService,
|
||||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void) { }
|
||||
private clipboardWriteCallback: (clipboardValue: string, clearMs: number) => void,
|
||||
private nativeMessagingBackground: NativeMessagingBackground) { }
|
||||
|
||||
getDevice(): DeviceType {
|
||||
if (this.deviceCache) {
|
||||
|
@ -288,13 +290,18 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||
}
|
||||
|
||||
supportsBiometric() {
|
||||
return Promise.resolve(false);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
authenticateBiometric() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
async authenticateBiometric() {
|
||||
const responsePromise = this.nativeMessagingBackground.await();
|
||||
this.nativeMessagingBackground.send({'command': 'biometricUnlock'});
|
||||
|
||||
const response = await responsePromise;
|
||||
|
||||
return response.response == 'unlocked';
|
||||
}
|
||||
|
||||
sidebarViewName(): string {
|
||||
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
|
||||
return 'sidebar';
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
"sourceMap": true,
|
||||
"types": [
|
||||
"jasmine",
|
||||
"sweetalert2"
|
||||
"sweetalert2",
|
||||
"@types/chrome",
|
||||
"@types/firefox-webext-browser"
|
||||
],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
|
Loading…
Reference in New Issue