mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 23:33:31 +00:00
[PM-1500] Add feature flag to enable passkeys (#5406)
* Added launch darkly feature flag to passkeys implementation * fixed linter * Updated fido2 client service test to accomodate feature flag * Updated fido2client service to include unit test for feature flag * Renamed enable pass keys to fido2 vault credentials, added unit test when feature flag is not enabled * fixed failing Login domain test case
This commit is contained in:
@@ -56,6 +56,7 @@ import { AvatarUpdateService } from "@bitwarden/common/services/account/avatar-u
|
|||||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||||
|
import { ConfigApiService } from "@bitwarden/common/services/config/config-api.service";
|
||||||
import { ConfigService } from "@bitwarden/common/services/config/config.service";
|
import { ConfigService } from "@bitwarden/common/services/config/config.service";
|
||||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||||
import { ContainerService } from "@bitwarden/common/services/container.service";
|
import { ContainerService } from "@bitwarden/common/services/container.service";
|
||||||
@@ -509,6 +510,8 @@ export default class MainBackground {
|
|||||||
this.userVerificationApiService
|
this.userVerificationApiService
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.configApiService = new ConfigApiService(this.apiService);
|
||||||
|
|
||||||
this.configService = new ConfigService(
|
this.configService = new ConfigService(
|
||||||
this.stateService,
|
this.stateService,
|
||||||
this.configApiService,
|
this.configApiService,
|
||||||
@@ -524,6 +527,7 @@ export default class MainBackground {
|
|||||||
);
|
);
|
||||||
this.fido2ClientService = new Fido2ClientService(
|
this.fido2ClientService = new Fido2ClientService(
|
||||||
this.fido2AuthenticatorService,
|
this.fido2AuthenticatorService,
|
||||||
|
this.configService,
|
||||||
this.logService
|
this.logService
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Observable } from "rxjs";
|
|||||||
|
|
||||||
import { DeviceType } from "@bitwarden/common/enums/device-type.enum";
|
import { DeviceType } from "@bitwarden/common/enums/device-type.enum";
|
||||||
|
|
||||||
|
|
||||||
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
import BrowserPlatformUtilsService from "../services/browserPlatformUtils.service";
|
||||||
import { TabMessage } from "../types/tab-messages";
|
import { TabMessage } from "../types/tab-messages";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum FeatureFlag {
|
export enum FeatureFlag {
|
||||||
DisplayEuEnvironmentFlag = "display-eu-environment",
|
DisplayEuEnvironmentFlag = "display-eu-environment",
|
||||||
DisplayLowKdfIterationWarningFlag = "display-kdf-iteration-warning",
|
DisplayLowKdfIterationWarningFlag = "display-kdf-iteration-warning",
|
||||||
|
Fido2VaultCredentials = "fido2-vault-credentials",
|
||||||
TrustedDeviceEncryption = "trusted-device-encryption",
|
TrustedDeviceEncryption = "trusted-device-encryption",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { mock, MockProxy } from "jest-mock-extended";
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
|
||||||
|
import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import {
|
import {
|
||||||
Fido2AutenticatorError,
|
Fido2AutenticatorError,
|
||||||
@@ -10,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AssertCredentialParams,
|
AssertCredentialParams,
|
||||||
CreateCredentialParams,
|
CreateCredentialParams,
|
||||||
|
FallbackRequestedError,
|
||||||
} from "../abstractions/fido2-client.service.abstraction";
|
} from "../abstractions/fido2-client.service.abstraction";
|
||||||
import { Fido2Utils } from "../abstractions/fido2-utils";
|
import { Fido2Utils } from "../abstractions/fido2-utils";
|
||||||
|
|
||||||
@@ -20,11 +22,14 @@ const RpId = "bitwarden.com";
|
|||||||
|
|
||||||
describe("FidoAuthenticatorService", () => {
|
describe("FidoAuthenticatorService", () => {
|
||||||
let authenticator!: MockProxy<Fido2AuthenticatorService>;
|
let authenticator!: MockProxy<Fido2AuthenticatorService>;
|
||||||
|
let configService!: MockProxy<ConfigServiceAbstraction>;
|
||||||
let client!: Fido2ClientService;
|
let client!: Fido2ClientService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
authenticator = mock<Fido2AuthenticatorService>();
|
authenticator = mock<Fido2AuthenticatorService>();
|
||||||
client = new Fido2ClientService(authenticator);
|
configService = mock<ConfigServiceAbstraction>();
|
||||||
|
client = new Fido2ClientService(authenticator, configService);
|
||||||
|
configService.getFeatureFlagBool.mockResolvedValue(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("createCredential", () => {
|
describe("createCredential", () => {
|
||||||
@@ -188,6 +193,16 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
await rejects.toMatchObject({ name: "NotAllowedError" });
|
await rejects.toMatchObject({ name: "NotAllowedError" });
|
||||||
await rejects.toBeInstanceOf(DOMException);
|
await rejects.toBeInstanceOf(DOMException);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw FallbackRequestedError if feature flag is not enabled", async () => {
|
||||||
|
const params = createParams();
|
||||||
|
configService.getFeatureFlagBool.mockResolvedValue(false);
|
||||||
|
|
||||||
|
const result = async () => await client.createCredential(params);
|
||||||
|
|
||||||
|
const rejects = expect(result).rejects;
|
||||||
|
await rejects.toThrow(FallbackRequestedError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createParams(params: Partial<CreateCredentialParams> = {}): CreateCredentialParams {
|
function createParams(params: Partial<CreateCredentialParams> = {}): CreateCredentialParams {
|
||||||
@@ -315,6 +330,16 @@ describe("FidoAuthenticatorService", () => {
|
|||||||
await rejects.toMatchObject({ name: "NotAllowedError" });
|
await rejects.toMatchObject({ name: "NotAllowedError" });
|
||||||
await rejects.toBeInstanceOf(DOMException);
|
await rejects.toBeInstanceOf(DOMException);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw FallbackRequestedError if feature flag is not enabled", async () => {
|
||||||
|
const params = createParams();
|
||||||
|
configService.getFeatureFlagBool.mockResolvedValue(false);
|
||||||
|
|
||||||
|
const result = async () => await client.assertCredential(params);
|
||||||
|
|
||||||
|
const rejects = expect(result).rejects;
|
||||||
|
await rejects.toThrow(FallbackRequestedError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("assert non-discoverable credential", () => {
|
describe("assert non-discoverable credential", () => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { parse } from "tldts";
|
import { parse } from "tldts";
|
||||||
|
|
||||||
|
import { ConfigServiceAbstraction } from "../../abstractions/config/config.service.abstraction";
|
||||||
import { LogService } from "../../abstractions/log.service";
|
import { LogService } from "../../abstractions/log.service";
|
||||||
|
import { FeatureFlag } from "../../enums/feature-flag.enum";
|
||||||
import { Utils } from "../../misc/utils";
|
import { Utils } from "../../misc/utils";
|
||||||
import {
|
import {
|
||||||
Fido2AutenticatorError,
|
Fido2AutenticatorError,
|
||||||
@@ -26,12 +28,25 @@ import { Fido2Utils } from "../abstractions/fido2-utils";
|
|||||||
import { isValidRpId } from "./domain-utils";
|
import { isValidRpId } from "./domain-utils";
|
||||||
|
|
||||||
export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
||||||
constructor(private authenticator: Fido2AuthenticatorService, private logService?: LogService) {}
|
constructor(
|
||||||
|
private authenticator: Fido2AuthenticatorService,
|
||||||
|
private configService: ConfigServiceAbstraction,
|
||||||
|
private logService?: LogService
|
||||||
|
) {}
|
||||||
|
|
||||||
async createCredential(
|
async createCredential(
|
||||||
params: CreateCredentialParams,
|
params: CreateCredentialParams,
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
): Promise<CreateCredentialResult> {
|
): Promise<CreateCredentialResult> {
|
||||||
|
const enableFido2VaultCredentials = await this.configService.getFeatureFlagBool(
|
||||||
|
FeatureFlag.Fido2VaultCredentials
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!enableFido2VaultCredentials) {
|
||||||
|
this.logService?.warning(`[Fido2Client] Fido2VaultCredential is not enabled`);
|
||||||
|
throw new FallbackRequestedError();
|
||||||
|
}
|
||||||
|
|
||||||
if (!params.sameOriginWithAncestors) {
|
if (!params.sameOriginWithAncestors) {
|
||||||
this.logService?.warning(
|
this.logService?.warning(
|
||||||
`[Fido2Client] Invalid 'sameOriginWithAncestors' value: ${params.sameOriginWithAncestors}`
|
`[Fido2Client] Invalid 'sameOriginWithAncestors' value: ${params.sameOriginWithAncestors}`
|
||||||
@@ -176,6 +191,15 @@ export class Fido2ClientService implements Fido2ClientServiceAbstraction {
|
|||||||
params: AssertCredentialParams,
|
params: AssertCredentialParams,
|
||||||
abortController = new AbortController()
|
abortController = new AbortController()
|
||||||
): Promise<AssertCredentialResult> {
|
): Promise<AssertCredentialResult> {
|
||||||
|
const enableFido2VaultCredentials = await this.configService.getFeatureFlagBool(
|
||||||
|
FeatureFlag.Fido2VaultCredentials
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!enableFido2VaultCredentials) {
|
||||||
|
this.logService?.warning(`[Fido2Client] Fido2VaultCredential is not enabled`);
|
||||||
|
throw new FallbackRequestedError();
|
||||||
|
}
|
||||||
|
|
||||||
const { domain: effectiveDomain } = parse(params.origin, { allowPrivateDomains: true });
|
const { domain: effectiveDomain } = parse(params.origin, { allowPrivateDomains: true });
|
||||||
if (effectiveDomain == undefined) {
|
if (effectiveDomain == undefined) {
|
||||||
this.logService?.warning(`[Fido2Client] Invalid origin: ${params.origin}`);
|
this.logService?.warning(`[Fido2Client] Invalid origin: ${params.origin}`);
|
||||||
|
|||||||
@@ -112,6 +112,19 @@ describe("Login DTO", () => {
|
|||||||
password: "myPassword",
|
password: "myPassword",
|
||||||
passwordRevisionDate: passwordRevisionDate.toISOString(),
|
passwordRevisionDate: passwordRevisionDate.toISOString(),
|
||||||
totp: "myTotp",
|
totp: "myTotp",
|
||||||
|
fido2Key: {
|
||||||
|
nonDiscoverableId: "keyId",
|
||||||
|
keyType: "keyType",
|
||||||
|
keyAlgorithm: "keyAlgorithm",
|
||||||
|
keyCurve: "keyCurve",
|
||||||
|
keyValue: "keyValue",
|
||||||
|
rpId: "rpId",
|
||||||
|
userHandle: "userHandle",
|
||||||
|
counter: "counter",
|
||||||
|
rpName: "rpName",
|
||||||
|
userName: "userName",
|
||||||
|
origin: "origin",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
@@ -120,6 +133,19 @@ describe("Login DTO", () => {
|
|||||||
password: "myPassword_fromJSON",
|
password: "myPassword_fromJSON",
|
||||||
passwordRevisionDate: passwordRevisionDate,
|
passwordRevisionDate: passwordRevisionDate,
|
||||||
totp: "myTotp_fromJSON",
|
totp: "myTotp_fromJSON",
|
||||||
|
fido2Key: {
|
||||||
|
nonDiscoverableId: "keyId_fromJSON",
|
||||||
|
keyType: "keyType_fromJSON",
|
||||||
|
keyAlgorithm: "keyAlgorithm_fromJSON",
|
||||||
|
keyCurve: "keyCurve_fromJSON",
|
||||||
|
keyValue: "keyValue_fromJSON",
|
||||||
|
rpId: "rpId_fromJSON",
|
||||||
|
userHandle: "userHandle_fromJSON",
|
||||||
|
counter: "counter_fromJSON",
|
||||||
|
rpName: "rpName_fromJSON",
|
||||||
|
userName: "userName_fromJSON",
|
||||||
|
origin: "origin_fromJSON",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(actual).toBeInstanceOf(Login);
|
expect(actual).toBeInstanceOf(Login);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user