Commit 55371a9
authored
feat: full WorkOS API emulator (emulate, dev, RBAC, webhooks, events, 84% API coverage) (#100)
* feat: add `workos emulate` command and programmatic emulator API
Port the WorkOS emulator from vercel-labs/emulate into the CLI as a
`workos emulate` command with a `workos/emulate` programmatic API.
Core infrastructure (store, ID generation, cursor pagination, JWT,
Hono server factory, auth/error middleware) lives in src/emulate/core/.
WorkOS-specific plugin (12 route files covering organizations, users,
memberships, connections, SSO, auth flows) lives in src/emulate/workos/.
The emulator supports:
- CLI: `workos emulate --port 4100 --seed config.yaml --json`
- Programmatic: `createEmulator({ port: 0, seed: { users: [...] } })`
- Health check at /health
- Seed config via YAML/JSON (auto-detected or explicit path)
- Store reset for test isolation
- Random port assignment (port: 0) for parallel test suites
New dependencies: hono, @hono/node-server, jose
* feat: add workos dev command for local development with emulator
* feat: add pipes emulation routes for user/provider connections
* feat: add gen:routes codegen script for emulator route generation
Developer tool that reads a WorkOS OpenAPI spec (YAML/JSON) and generates
TypeScript entity types, store registrations, formatter helpers, and route
stubs matching the hand-written emulate patterns. Includes 46 unit tests
covering parsing, code generation, and idempotency.
* chore: formatting:
* docs: add local development section to README
Document workos emulate, workos dev, seed configuration, programmatic
API, and emulated endpoint coverage.
* fix: set decomposed SDK env vars in workos dev
authkit-nextjs (and the @workos-inc/node SDK) read WORKOS_API_HOSTNAME,
WORKOS_API_PORT, and WORKOS_API_HTTPS — not WORKOS_API_BASE_URL.
buildDevEnv now sets both styles so the emulator works with authkit SDKs
and direct consumers.
* feat: seed default test user in workos dev
workos dev now seeds a default user (test@example.com / password) and
organization so the AuthKit login flow works out of the box. The banner
prints the credentials for easy copy-paste.
Skipped when the user provides --seed or a workos-emulate.config.* file
is auto-detected in the project directory.
* chore: formatting:
* fix: address emulator review findings (API key, JWT issuer, cleanup, redirects)
- Expose apiKey on Emulator interface; use it in dev/emulate CLI output
and buildDevEnv() so custom seed apiKeys propagate correctly
- Update JWT issuer to actual bound URL after port resolution, fixing
iss mismatch when using port: 0
- Clean up password resets, email verifications, and magic auths on
user deletion; harden password_reset/confirm to return 404 if user
was deleted
- Restrict redirect_uri in authorize endpoints to localhost origins,
preventing open-redirect abuse
- Remove unused jose dependency
* feat: emulator auth completeness + shared entity infrastructure
Add token refresh with rotation, logout redirects, multi-user authorize
(login_hint), impersonation, sealed sessions (AES-256-GCM), MFA
challenge/verify, device authorization, and AuthKit OAuth complete flow.
New grant types: refresh_token, mfa-totp, organization-selection, device_code.
Includes shared entity/store/helper infrastructure for all 4 API parity phases.
* feat: emulator user management — invitations, config, widgets
Add invitations CRUD (create/accept/revoke/resend/by-token), config
endpoints (redirect URIs, CORS origins, JWT template), user features
(authorized apps, connected accounts, data providers), and widget
token generation.
* feat: emulator authorization — RBAC roles, permissions, FGA resources
Add environment roles, org roles with priority ordering, permissions
CRUD, role-permission management, authorization resources, permission
checks, and role assignments. JWT tokens now include role and
permissions claims for org-scoped sessions.
* feat: emulator CRUD domains — directory sync, audit logs, feature flags, and more
Add 9 CRUD domains: Directory Sync, Audit Logs, Feature Flags, Connect,
Data Integrations, Radar, API Keys, Portal, and Legacy MFA. Each domain
follows the entity/store/helper/route pattern with full test coverage.
* feat: emulator events & webhooks — event bus, collection hooks, webhook delivery
Add cross-cutting event system with webhook delivery. Collection-level
hooks (Option A) auto-emit events on insert/update/delete for all major
entities. Hybrid route-level emission for action-specific events
(invitation accept/revoke, authentication succeeded).
Webhook endpoints CRUD with HMAC-SHA256 signed delivery (fire-and-forget,
5s timeout). Events API with paginated listing and type filtering.
Review: 2 cycles (6 findings in cycle 1, all resolved in cycle 2).
* chore: formatting:
* fix: resolve lint warnings in emulator spec files
Remove unused variables and imports flagged by oxlint:
- sessions.spec.ts: remove unused `req` helper and `headers` constant
- auth.spec.ts: remove unused `user` variable in impersonator test
- data-integrations.spec.ts: remove unused `getWorkOSStore` import and `store` variable
- event-bus.spec.ts: use non-null assertion instead of optional chaining
* feat: add emulator API coverage checker script
Compares the WorkOS OpenAPI spec against emulator route registrations
to report missing endpoints, extra endpoints, and coverage percentage.
Usage: pnpm check:coverage ~/Developer/workos/packages/api/open-api-spec.yaml
* fix: allow organization_id in refresh_token grant for switchToOrganization
The refresh token handler now reads organization_id from the request
body, falling back to the stored token's org. This enables the SDK's
switchToOrganization flow during token refresh.
* fix: alias /x/authkit/users/authenticate for SDK refresh token flow
The AuthKit SDK sends refresh_token grants to /x/authkit/users/authenticate
rather than /user_management/authenticate. Register the same handler on
both paths so the SDK's refreshTokens() and switchToOrganization() work
against the emulator.
* chore: update README
* docs: update README emulated endpoints table for phases 1-5
Add all new endpoint groups from auth completeness, user management,
authorization, CRUD domains, and events/webhooks phases. Add seed
config examples for roles, permissions, and webhook endpoints.1 parent 1a05561 commit 55371a9
File tree
101 files changed
+13431
-9
lines changed- scripts
- src
- commands
- emulate
- core
- middleware
- workos
- routes
- lib
- utils
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
101 files changed
+13431
-9
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
80 | 84 | | |
81 | 85 | | |
82 | 86 | | |
| |||
192 | 196 | | |
193 | 197 | | |
194 | 198 | | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
195 | 351 | | |
196 | 352 | | |
197 | 353 | | |
| |||
466 | 622 | | |
467 | 623 | | |
468 | 624 | | |
469 | | - | |
470 | | - | |
471 | | - | |
472 | | - | |
473 | | - | |
474 | | - | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
475 | 632 | | |
476 | 633 | | |
477 | 634 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
37 | 47 | | |
38 | 48 | | |
39 | 49 | | |
| |||
51 | 61 | | |
52 | 62 | | |
53 | 63 | | |
| 64 | + | |
| 65 | + | |
54 | 66 | | |
55 | 67 | | |
56 | 68 | | |
| |||
96 | 108 | | |
97 | 109 | | |
98 | 110 | | |
99 | | - | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
100 | 114 | | |
101 | 115 | | |
102 | 116 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments