From e56e1a5c7f435f3d2fc3151c1c78c1bb429fb34f Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Wed, 10 Mar 2021 21:27:05 +0100 Subject: [PATCH] Add support for browser biometrics on Firefox >= 87 (#1668) --- gulpfile.js | 1 - src/_locales/en/messages.json | 12 +++--- src/browser/browserApi.ts | 9 +++++ src/popup/settings/settings.component.ts | 41 ++++++++++---------- src/services/browserPlatformUtils.service.ts | 5 ++- 5 files changed, 39 insertions(+), 29 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 81f158f3d9..409ad5687d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -58,7 +58,6 @@ function dist(browserName, manifest) { function distFirefox() { return dist('firefox', (manifest) => { delete manifest.content_security_policy; - delete manifest.optional_permissions; removeShortcuts(manifest); return manifest; }); diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 29b0d73fcd..8665d2c325 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -1441,18 +1441,18 @@ "biometricsNotSupportedDesc": { "message": "Browser biometrics is not supported on this device." }, - "nativeMessagingPermissionPromptTitle": { - "message": "Additional Permission required" - }, - "nativeMessagingPermissionPromptDesc": { - "message": "To enable browser biometrics we need to request an additional permission. Once allowed, the browser extension will reload and you may need to unlock your vault again." - }, "nativeMessaginPermissionErrorTitle": { "message": "Permission not provided" }, "nativeMessaginPermissionErrorDesc": { "message": "Without permission to communicate with the Bitwarden Desktop Application we cannot provide biometrics in the browser extension. Please try again." }, + "nativeMessaginPermissionSidebarTitle": { + "message": "Permission request error" + }, + "nativeMessaginPermissionSidebarDesc": { + "message": "This action cannot be done in the sidebar, please retry the action in the popup or popout." + }, "personalOwnershipSubmitError": { "message": "Due to an Enterprise Policy, you are restricted from saving items to your personal vault. Change the Ownership option to an organization and choose from available Collections." }, diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 521e947c7e..0993d79799 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -167,4 +167,13 @@ export class BrowserApi { return chrome.runtime.connectNative(application); } } + + static requestPermission(permission: any) { + if (BrowserApi.isWebExtensionsApi) { + return browser.permissions.request(permission); + } + return new Promise((resolve, reject) => { + chrome.permissions.request(permission, resolve); + }); + } } diff --git a/src/popup/settings/settings.component.ts b/src/popup/settings/settings.component.ts index be4e6f1923..b8923b0ce5 100644 --- a/src/popup/settings/settings.component.ts +++ b/src/popup/settings/settings.component.ts @@ -23,6 +23,7 @@ import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { UserService } from 'jslib/abstractions/user.service'; import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service'; +import { PopupUtilsService } from '../services/popup-utils.service'; const RateUrls = { [DeviceType.ChromeExtension]: @@ -59,7 +60,8 @@ export class SettingsComponent implements OnInit { private analytics: Angulartics2, private vaultTimeoutService: VaultTimeoutService, private storageService: StorageService, public messagingService: MessagingService, private router: Router, private environmentService: EnvironmentService, - private cryptoService: CryptoService, private userService: UserService) { + private cryptoService: CryptoService, private userService: UserService, + private popupUtilsService: PopupUtilsService) { } async ngOnInit() { @@ -212,31 +214,30 @@ export class SettingsComponent implements OnInit { async updateBiometric() { if (this.biometric && this.supportsBiometric) { - // Request permission to use the optional permission for nativeMessaging - if (!this.platformUtilsService.isFirefox()) { - const hasPermission = await new Promise(resolve => { - chrome.permissions.contains({ permissions: ['nativeMessaging'] }, resolve); - }); + let granted; + try { + granted = await BrowserApi.requestPermission({ permissions: ['nativeMessaging'] }); + } catch (e) { + // tslint:disable-next-line + console.error(e); - if (!hasPermission) { + if (this.platformUtilsService.isFirefox() && this.popupUtilsService.inSidebar(window)) { await this.platformUtilsService.showDialog( - this.i18nService.t('nativeMessagingPermissionPromptDesc'), this.i18nService.t('nativeMessagingPermissionPromptTitle'), + this.i18nService.t('nativeMessaginPermissionSidebarDesc'), this.i18nService.t('nativeMessaginPermissionSidebarTitle'), this.i18nService.t('ok'), null); - - const granted = await new Promise((resolve, reject) => { - chrome.permissions.request({ permissions: ['nativeMessaging'] }, resolve); - }); - - if (!granted) { - await this.platformUtilsService.showDialog( - this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'), - this.i18nService.t('ok'), null); - this.biometric = false; - return; - } + this.biometric = false; + return; } } + if (!granted) { + await this.platformUtilsService.showDialog( + this.i18nService.t('nativeMessaginPermissionErrorDesc'), this.i18nService.t('nativeMessaginPermissionErrorTitle'), + this.i18nService.t('ok'), null); + this.biometric = false; + return; + } + const submitted = Swal.fire({ heightAuto: false, buttonsStyling: false, diff --git a/src/services/browserPlatformUtils.service.ts b/src/services/browserPlatformUtils.service.ts index 5041069599..6cfc322c46 100644 --- a/src/services/browserPlatformUtils.service.ts +++ b/src/services/browserPlatformUtils.service.ts @@ -292,8 +292,9 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService }); } - supportsBiometric() { - return Promise.resolve(!this.isFirefox() && !this.isSafari()); + async supportsBiometric() { + const isUnsuportedFirefox = this.isFirefox() && parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) < 87; + return !isUnsuportedFirefox && !this.isSafari(); } authenticateBiometric() {