From 74208d568e9f20b729db539fe24855d501fb54d5 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 23 Nov 2023 11:09:27 -0500 Subject: [PATCH] [PM-4690] Setting in the browser extension that turns off passkeys (#6929) * use passkeys setting * check state service on isFido2FeatureEnabled * fix broken json * update description text * make setting global * invert logic to positive state * fix and add to fido2 client service tests --- apps/browser/src/_locales/en/messages.json | 6 ++++++ .../src/popup/settings/options.component.html | 17 +++++++++++++++ .../src/popup/settings/options.component.ts | 7 +++++++ .../platform/abstractions/state.service.ts | 2 ++ .../platform/models/domain/global-state.ts | 1 + .../src/platform/services/state.service.ts | 18 ++++++++++++++++ .../fido2/fido2-client.service.spec.ts | 21 +++++++++++++++++++ .../services/fido2/fido2-client.service.ts | 6 +++++- 8 files changed, 77 insertions(+), 1 deletion(-) diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index af2de0643ed..0109c7036f6 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -657,6 +657,12 @@ "changedPasswordNotificationDescAlt": { "message": "Ask to update a login's password when a change is detected on a website. Applies to all logged in accounts." }, + "enableUsePasskeys": { + "message": "Ask to save and use passkeys" + }, + "usePasskeysDesc": { + "message": "Ask to save new passkeys or log in with passkeys stored in your vault. Applies to all logged in accounts." + }, "notificationChangeDesc": { "message": "Do you want to update this password in Bitwarden?" }, diff --git a/apps/browser/src/popup/settings/options.component.html b/apps/browser/src/popup/settings/options.component.html index db9a29fa119..fb8906463be 100644 --- a/apps/browser/src/popup/settings/options.component.html +++ b/apps/browser/src/popup/settings/options.component.html @@ -104,6 +104,23 @@ }} +
+
+
+ + +
+
+ +
diff --git a/apps/browser/src/popup/settings/options.component.ts b/apps/browser/src/popup/settings/options.component.ts index b23fefbf646..159bd100e81 100644 --- a/apps/browser/src/popup/settings/options.component.ts +++ b/apps/browser/src/popup/settings/options.component.ts @@ -24,6 +24,7 @@ export class OptionsComponent implements OnInit { enableContextMenuItem = false; enableAddLoginNotification = false; enableChangedPasswordNotification = false; + enablePasskeys = true; showCardsCurrentTab = false; showIdentitiesCurrentTab = false; showClearClipboard = true; @@ -100,6 +101,8 @@ export class OptionsComponent implements OnInit { this.enableBadgeCounter = !(await this.stateService.getDisableBadgeCounter()); + this.enablePasskeys = await this.stateService.getEnablePasskeys(); + this.theme = await this.stateService.getTheme(); const defaultUriMatch = await this.stateService.getDefaultUriMatch(); @@ -118,6 +121,10 @@ export class OptionsComponent implements OnInit { ); } + async updateEnablePasskeys() { + await this.stateService.setEnablePasskeys(this.enablePasskeys); + } + async updateContextMenuItem() { await this.stateService.setDisableContextMenuItem(!this.enableContextMenuItem); this.messagingService.send("bgUpdateContextMenu"); diff --git a/libs/common/src/platform/abstractions/state.service.ts b/libs/common/src/platform/abstractions/state.service.ts index 374f5f7171e..f4f86cb8595 100644 --- a/libs/common/src/platform/abstractions/state.service.ts +++ b/libs/common/src/platform/abstractions/state.service.ts @@ -243,6 +243,8 @@ export abstract class StateService { value: boolean, options?: StorageOptions ) => Promise; + getEnablePasskeys: (options?: StorageOptions) => Promise; + setEnablePasskeys: (value: boolean, options?: StorageOptions) => Promise; getDisableContextMenuItem: (options?: StorageOptions) => Promise; setDisableContextMenuItem: (value: boolean, options?: StorageOptions) => Promise; /** diff --git a/libs/common/src/platform/models/domain/global-state.ts b/libs/common/src/platform/models/domain/global-state.ts index 61fc5ffa34f..ab52115a7ed 100644 --- a/libs/common/src/platform/models/domain/global-state.ts +++ b/libs/common/src/platform/models/domain/global-state.ts @@ -36,6 +36,7 @@ export class GlobalState { enableDuckDuckGoBrowserIntegration?: boolean; region?: string; neverDomains?: { [id: string]: unknown }; + enablePasskeys?: boolean; disableAddLoginNotification?: boolean; disableChangedPasswordNotification?: boolean; disableContextMenuItem?: boolean; diff --git a/libs/common/src/platform/services/state.service.ts b/libs/common/src/platform/services/state.service.ts index 2ddba9b2ffe..fda79c5524c 100644 --- a/libs/common/src/platform/services/state.service.ts +++ b/libs/common/src/platform/services/state.service.ts @@ -1213,6 +1213,24 @@ export class StateService< ); } + async getEnablePasskeys(options?: StorageOptions): Promise { + return ( + (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) + ?.enablePasskeys ?? true + ); + } + + async setEnablePasskeys(value: boolean, options?: StorageOptions): Promise { + const globals = await this.getGlobals( + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + globals.enablePasskeys = value; + await this.saveGlobals( + globals, + this.reconcileOptions(options, await this.defaultOnDiskOptions()) + ); + } + async getDisableContextMenuItem(options?: StorageOptions): Promise { return ( (await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions()))) diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts index af845ac0938..753c5800f7c 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.spec.ts @@ -40,6 +40,7 @@ describe("FidoAuthenticatorService", () => { client = new Fido2ClientService(authenticator, configService, authService, stateService); configService.getFeatureFlag.mockResolvedValue(true); + stateService.getEnablePasskeys.mockResolvedValue(true); tab = { id: 123, windowId: 456 } as chrome.tabs.Tab; }); @@ -229,6 +230,16 @@ describe("FidoAuthenticatorService", () => { await rejects.toThrow(FallbackRequestedError); }); + it("should throw FallbackRequestedError if passkeys state is not enabled", async () => { + const params = createParams(); + stateService.getEnablePasskeys.mockResolvedValue(false); + + const result = async () => await client.createCredential(params, tab); + + const rejects = expect(result).rejects; + await rejects.toThrow(FallbackRequestedError); + }); + it("should throw FallbackRequestedError if user is logged out", async () => { const params = createParams(); authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); @@ -389,6 +400,16 @@ describe("FidoAuthenticatorService", () => { await rejects.toThrow(FallbackRequestedError); }); + it("should throw FallbackRequestedError if passkeys state is not enabled", async () => { + const params = createParams(); + stateService.getEnablePasskeys.mockResolvedValue(false); + + const result = async () => await client.assertCredential(params, tab); + + const rejects = expect(result).rejects; + await rejects.toThrow(FallbackRequestedError); + }); + it("should throw FallbackRequestedError if user is logged out", async () => { const params = createParams(); authService.getAuthStatus.mockResolvedValue(AuthenticationStatus.LoggedOut); diff --git a/libs/common/src/vault/services/fido2/fido2-client.service.ts b/libs/common/src/vault/services/fido2/fido2-client.service.ts index e9eeef19801..e377b47c548 100644 --- a/libs/common/src/vault/services/fido2/fido2-client.service.ts +++ b/libs/common/src/vault/services/fido2/fido2-client.service.ts @@ -46,7 +46,11 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction { ) {} async isFido2FeatureEnabled(): Promise { - return await this.configService.getFeatureFlag(FeatureFlag.Fido2VaultCredentials); + const featureFlagEnabled = await this.configService.getFeatureFlag( + FeatureFlag.Fido2VaultCredentials + ); + const userEnabledPasskeys = await this.stateService.getEnablePasskeys(); + return featureFlagEnabled && userEnabledPasskeys; } async createCredential(