WorkOSSeedConfig has eleven keys (organizations, users, connections, pipeConnections, invitations, roles, permissions, webhookEndpoints, connectApplications, apiKeys, jwtTemplate) and none of them covers authentication factors. There are no hits for totp anywhere in the seed path, so the only way to get a factor is POST /user_management/users/{id}/auth_factors or the legacy /auth/factors/enroll after the emulator is up.
That makes anything touching MFA administration a two-step setup: seed the users, then walk them over HTTP enrolling factors, because ListAuthFactors returning an empty list is indistinguishable from "this user has no MFA". With state in memory, the enrolment also does not survive a restart while the seeded users do, so the environment comes back subtly different from how it was set up.
A seeded user can already be given every other authentication method, which is what makes the omission look accidental rather than deliberate:
password writes a password_hash, so the password grant works
oauth_provider calls linkOAuthIdentity, so GET /user_management/users/{id}/identities reports it
A per-user flag would sit alongside those:
users:
- email: alice@example.com
password: test123
totp: true # or mfa: [totp]
inserting into the same ws.authFactors collection the two existing routes write to, with the shape routes/auth-factors.ts already builds (object: 'authentication_factor', type: 'totp', totp: { issuer, user, uri }), defaulting issuer the way that route does.
One optional addition while the area is open: the TOTP secret is randomBytes(20) per enrolment and surfaces only inside the returned uri, so nothing can compute a valid code for a seeded factor deterministically. /auth/factors/:id/challenge and /auth/challenges/:id/verify are both modelled, so accepting a pinned totp_secret in the seed would make the whole challenge-and-verify path testable rather than just the existence of a factor. Reasonable to leave out if that is out of scope.
Is there a reason factors are deliberately API-only, or would a seed key be accepted?
WorkOSSeedConfighas eleven keys (organizations,users,connections,pipeConnections,invitations,roles,permissions,webhookEndpoints,connectApplications,apiKeys,jwtTemplate) and none of them covers authentication factors. There are no hits fortotpanywhere in the seed path, so the only way to get a factor isPOST /user_management/users/{id}/auth_factorsor the legacy/auth/factors/enrollafter the emulator is up.That makes anything touching MFA administration a two-step setup: seed the users, then walk them over HTTP enrolling factors, because
ListAuthFactorsreturning an empty list is indistinguishable from "this user has no MFA". With state in memory, the enrolment also does not survive a restart while the seeded users do, so the environment comes back subtly different from how it was set up.A seeded user can already be given every other authentication method, which is what makes the omission look accidental rather than deliberate:
passwordwrites apassword_hash, so the password grant worksoauth_providercallslinkOAuthIdentity, soGET /user_management/users/{id}/identitiesreports itA per-user flag would sit alongside those:
inserting into the same
ws.authFactorscollection the two existing routes write to, with the shaperoutes/auth-factors.tsalready builds (object: 'authentication_factor',type: 'totp',totp: { issuer, user, uri }), defaultingissuerthe way that route does.One optional addition while the area is open: the TOTP secret is
randomBytes(20)per enrolment and surfaces only inside the returneduri, so nothing can compute a valid code for a seeded factor deterministically./auth/factors/:id/challengeand/auth/challenges/:id/verifyare both modelled, so accepting a pinnedtotp_secretin the seed would make the whole challenge-and-verify path testable rather than just the existence of a factor. Reasonable to leave out if that is out of scope.Is there a reason factors are deliberately API-only, or would a seed key be accepted?