Conversation
Because: * Passwordless sync sign-in needs a flag on both sides before the routes and UI paths that use it can be gated. This commit: * Adds `passkeys.passwordlessSyncEnabled` to auth-server config with an `isPasskeyPasswordlessSyncEnabled` guard that throws when disabled. * Adds `passkeyPasswordlessSyncEnabled` to the content-server feature flags and the settings `featureFlags` interface. * Enables both in local config. Defaults stay false.
Because: * "Master switch" is terminology we're moving away from. This commit: * Renames it in the passkeys convict docs on both servers and in the passkey-utils test names. Comments and test names only.
There was a problem hiding this comment.
Pull request overview
Adds a new “passwordless sync via passkeys” feature flag across the auth-server and content-server config surfaces, plus a backend guard and unit tests, so future routes/UI can be safely gated behind a dark-launched flag (default off).
Changes:
- Auth-server: add
passkeys.passwordlessSyncEnabledconvict config +isPasskeyPasswordlessSyncEnabledguard (throwsAppError.featureNotEnabled()unless both flags are enabled). - Content-server: add
featureFlags.passkeyPasswordlessSyncEnabledconvict flag and plumb it through to frontend config/meta. - Frontend typing/local dev: extend
fxa-settingsConfig.featureFlagsinterface and enable the flags in local dev config templates.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/fxa-auth-server/config/index.ts | Adds passkeys.passwordlessSyncEnabled convict config (default false, env var wiring) and updates wording “Master” → “Primary”. |
| packages/fxa-auth-server/lib/passkey-utils.ts | Adds isPasskeyPasswordlessSyncEnabled guard consistent with existing passkey flag guards. |
| packages/fxa-auth-server/lib/passkey-utils.spec.ts | Adds unit coverage for the new guard, including “undefined flag” behavior. |
| packages/fxa-auth-server/config/dev.json | Enables the new backend flag in local dev config. |
| packages/fxa-content-server/server/lib/configuration.js | Adds featureFlags.passkeyPasswordlessSyncEnabled convict entry and updates “Master” → “Primary” wording. |
| packages/fxa-content-server/server/lib/beta-settings.js | Plumbs the new content-server feature flag into injected settings config. |
| packages/fxa-content-server/server/lib/routes/react-app/route-definition-index.js | Passes through passkeyPasswordlessSyncEnabled into the React app config payload. |
| packages/fxa-content-server/server/config/local.json-dist | Enables the new content-server flag in the local config template. |
| packages/fxa-settings/src/lib/config.ts | Extends Config.featureFlags typing to include passkeyPasswordlessSyncEnabled (and minor formatting fixes). |
| * Requires both the primary `passkeys.enabled` flag and `passkeys.passwordlessSyncEnabled`. | ||
| * @throws AppError.featureNotEnabled if either flag is disabled | ||
| */ | ||
| export function isPasskeyPasswordlessSyncEnabled( |
There was a problem hiding this comment.
I've probably commented on this before, but I don't love the pattern that's been established here. It's pretty unconventional to have an 'is' function throw instead of just return false. I feel like semantically being clear that an error is being raised would be better. e.g. requirePasskeyPasswordlessSyncEnabled(...) : void 0.
I don't feel like this should be addressed in this PR, cause it'd make thing incosistent, but might be a nice polish PR / follow up.
Because
frontend before the routes and UI paths that use it can be gated in later
tickets in this epic.
of the behaviour it will eventually gate.
This pull request
passkeys.passwordlessSyncEnabledto the auth-server convict block inpackages/fxa-auth-server/config/index.ts(defaultfalse, envPASSKEYS__PASSWORDLESS_SYNC_ENABLED).isPasskeyPasswordlessSyncEnabledtopackages/fxa-auth-server/lib/passkey-utils.ts, which throwsAppError.featureNotEnabled()unless both the primarypasskeys.enabledflagand the new flag are on — the same shape as the existing
isPasskeyRegistrationEnabledandisPasskeyAuthenticationEnabledguards.passkeyPasswordlessSyncEnabledto the content-server feature flags inconfiguration.js, and passes it through in bothbeta-settings.jsandroutes/react-app/route-definition-index.js, matching howpasskeysEnabledis wired.
featureFlagsinterface inpackages/fxa-settings/src/lib/config.ts.(
fxa-auth-server/config/dev.jsonand the content-serverlocal.json-disttemplate).Issue that this pull request solves
Closes: FXA-13141
Checklist
Put an
xin the boxes that applyHow to review (Optional)
lib/passkey-utils.tsfor the guard, and thethree content-server files for flag plumbing.
pass-throughs, then the local config enablement.
The thing worth checking is that no plumbing site was missed, since a missed
one fails silently rather than loudly (see below).
Screenshots (Optional)
Please attach the screenshots of the changes made in case of change in user interface.
Other information (Optional)
Verified on the running local services, not just statically:
passkeys.passwordlessSyncEnabled: truein its resolvedconfig after restart, with
/__heartbeat__returning 200.passkeyPasswordlessSyncEnabled: truein themeta[name="fxa-config"]tag onlocalhost:3030— which is the exact sourcethe functional tests read via
ConfigPage.getConfig().packages/fxa-auth-server/lib/passkey-utils.spec.tspasses 13/13, andfxa-settingstypechecks with 0 errors.Heads-up for anyone running this locally: the content-server
server/config/local.jsonis gitignored, so only thelocal.json-disttemplatechange is in this diff.
local.jsonoverlays last, so until you add"passkeyPasswordlessSyncEnabled": trueto your own copy, the flag readsfalselocally even though-disthas it on. Functional tests in this epictest.skipon missing flags, so that shows up as a green run rather than afailure. I hit this during verification.
Ticket correction: FXA-13141 says "note there is no
isPasskeyFeatureEnabled".It does exist, at
packages/fxa-auth-server/lib/passkey-utils.ts:23, and threepasskey management routes use it. I followed the code rather than the ticket.