Skip to content

Commit f4897d8

Browse files
committed
feat(passkeys): configure passkey feature flags
1 parent 2dcae73 commit f4897d8

8 files changed

Lines changed: 163 additions & 0 deletions

File tree

packages/fxa-auth-server/config/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,6 +2493,14 @@ const convictConf = convict({
24932493
},
24942494
},
24952495
},
2496+
passkeys: {
2497+
enabled: {
2498+
default: false,
2499+
doc: 'Enable passkeys authentication feature',
2500+
env: 'PASSKEYS__ENABLED',
2501+
format: Boolean,
2502+
},
2503+
},
24962504
twilio: {
24972505
credentialMode: {
24982506
default: '',
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
import { ConfigType } from '../config';
6+
import { AppError } from '@fxa/accounts/errors';
7+
8+
/**
9+
* Checks if the passkey feature is enabled in the configuration
10+
* @param config - The application configuration object
11+
* @returns true if the passkey feature is enabled
12+
* @throws AppError.featureNotEnabled if the feature is disabled
13+
*/
14+
export function isPasskeyFeatureEnabled(config: ConfigType): boolean {
15+
if (!config.passkeys.enabled) {
16+
throw AppError.featureNotEnabled();
17+
}
18+
return true;
19+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
'use strict';
6+
7+
const { assert } = require('chai');
8+
const { isPasskeyFeatureEnabled } = require('../../lib/passkey-utils');
9+
const { AppError } = require('@fxa/accounts/errors');
10+
11+
describe('passkey-utils', () => {
12+
describe('isPasskeyFeatureEnabled', () => {
13+
it('should return true when passkeys are enabled', () => {
14+
const config = {
15+
passkeys: {
16+
enabled: true,
17+
},
18+
};
19+
20+
const result = isPasskeyFeatureEnabled(config);
21+
assert.equal(result, true, 'should return true when enabled');
22+
});
23+
24+
it('should throw featureNotEnabled error when passkeys are disabled', () => {
25+
const config = {
26+
passkeys: {
27+
enabled: false,
28+
},
29+
};
30+
31+
try {
32+
isPasskeyFeatureEnabled(config);
33+
assert.fail('should have thrown an error');
34+
} catch (error) {
35+
assert.equal(
36+
error.errno,
37+
AppError.featureNotEnabled().errno,
38+
'should throw featureNotEnabled error'
39+
);
40+
assert.equal(
41+
error.message,
42+
'Feature not enabled',
43+
'should have correct error message'
44+
);
45+
}
46+
});
47+
48+
it('should throw featureNotEnabled error when config.passkeys.enabled is undefined', () => {
49+
const config = {
50+
passkeys: {},
51+
};
52+
53+
try {
54+
isPasskeyFeatureEnabled(config);
55+
assert.fail('should have thrown an error');
56+
} catch (error) {
57+
assert.equal(
58+
error.errno,
59+
AppError.featureNotEnabled().errno,
60+
'should throw featureNotEnabled error'
61+
);
62+
}
63+
});
64+
});
65+
});

packages/fxa-content-server/server/lib/beta-settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ const settingsConfig = {
121121
paymentsNextSubscriptionManagement: config.get(
122122
'featureFlags.paymentsNextSubscriptionManagement'
123123
),
124+
passkeysEnabled: config.get('featureFlags.passkeysEnabled'),
124125
},
125126
nimbus: {
126127
enabled: config.get('nimbus.enabled'),

packages/fxa-content-server/server/lib/configuration.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,12 @@ const conf = (module.exports = convict({
247247
format: Boolean,
248248
env: 'FEATURE_FLAGS_PAYMENTS_NEXT_SUBSCRIPTION_MANAGEMENT',
249249
},
250+
passkeysEnabled: {
251+
default: false,
252+
doc: 'Enables passkeys authentication',
253+
format: Boolean,
254+
env: 'FEATURE_FLAGS_PASSKEYS_ENABLED',
255+
},
250256
},
251257
cms: {
252258
enabled: {

packages/fxa-content-server/server/lib/routes/react-app/route-definition-index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ function getIndexRouteDefinition(config) {
5555
const FEATURE_FLAGS_SHOW_LOCALE_TOGGLE = config.get(
5656
'featureFlags.showLocaleToggle'
5757
);
58+
const FEATURE_FLAGS_PASSKEYS_ENABLED = config.get(
59+
'featureFlags.passkeysEnabled'
60+
);
5861
const GLEAN_ENABLED = config.get('glean.enabled');
5962
const GLEAN_APPLICATION_ID = config.get('glean.applicationId');
6063
const GLEAN_UPLOAD_ENABLED = config.get('glean.uploadEnabled');
@@ -121,6 +124,7 @@ function getIndexRouteDefinition(config) {
121124
recoveryCodeSetupOnSyncSignIn:
122125
FEATURE_FLAGS_RECOVERY_CODE_SETUP_ON_SYNC_SIGN_IN,
123126
showLocaleToggle: FEATURE_FLAGS_SHOW_LOCALE_TOGGLE,
127+
passkeysEnabled: FEATURE_FLAGS_PASSKEYS_ENABLED,
124128
},
125129
cms: {
126130
enabled: CMS_ENABLED,

packages/fxa-settings/src/lib/config.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,62 @@ describe('reset', () => {
158158
expect(config.version).toBeUndefined();
159159
});
160160
});
161+
162+
describe('featureFlags', () => {
163+
it('can parse passkeysEnabled feature flag', () => {
164+
const data = {
165+
featureFlags: {
166+
passkeysEnabled: true,
167+
},
168+
};
169+
170+
readConfigMeta(() => {
171+
return {
172+
getAttribute() {
173+
return encodeURIComponent(JSON.stringify(data));
174+
},
175+
};
176+
});
177+
178+
expect(config.featureFlags).toBeDefined();
179+
expect(config.featureFlags?.passkeysEnabled).toBe(true);
180+
});
181+
182+
it('handles passkeysEnabled as false', () => {
183+
const data = {
184+
featureFlags: {
185+
passkeysEnabled: false,
186+
},
187+
};
188+
189+
readConfigMeta(() => {
190+
return {
191+
getAttribute() {
192+
return encodeURIComponent(JSON.stringify(data));
193+
},
194+
};
195+
});
196+
197+
expect(config.featureFlags).toBeDefined();
198+
expect(config.featureFlags?.passkeysEnabled).toBe(false);
199+
});
200+
201+
it('handles undefined passkeysEnabled flag', () => {
202+
const data = {
203+
featureFlags: {
204+
keyStretchV2: true,
205+
},
206+
};
207+
208+
readConfigMeta(() => {
209+
return {
210+
getAttribute() {
211+
return encodeURIComponent(JSON.stringify(data));
212+
},
213+
};
214+
});
215+
216+
expect(config.featureFlags).toBeDefined();
217+
expect(config.featureFlags?.passkeysEnabled).toBeUndefined();
218+
});
219+
});

packages/fxa-settings/src/lib/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export interface Config {
101101
recoveryCodeSetupOnSyncSignIn?: boolean;
102102
showLocaleToggle?: boolean;
103103
paymentsNextSubscriptionManagement?: boolean;
104+
passkeysEnabled?: boolean;
104105
};
105106
nimbus: {
106107
enabled: boolean;

0 commit comments

Comments
 (0)