workos 0.11.2 → 0.12.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +163 -6
  2. package/dist/bin.js +20 -1
  3. package/dist/bin.js.map +1 -1
  4. package/dist/check-coverage.ts +237 -0
  5. package/dist/commands/dev.d.ts +23 -0
  6. package/dist/commands/dev.js +139 -0
  7. package/dist/commands/dev.js.map +1 -0
  8. package/dist/commands/emulate.d.ts +6 -0
  9. package/dist/commands/emulate.js +64 -0
  10. package/dist/commands/emulate.js.map +1 -0
  11. package/dist/emulate/core/id.d.ts +33 -0
  12. package/dist/emulate/core/id.js +58 -0
  13. package/dist/emulate/core/id.js.map +1 -0
  14. package/dist/emulate/core/index.d.ts +8 -0
  15. package/dist/emulate/core/index.js +8 -0
  16. package/dist/emulate/core/index.js.map +1 -0
  17. package/dist/emulate/core/jwt.d.ts +28 -0
  18. package/dist/emulate/core/jwt.js +78 -0
  19. package/dist/emulate/core/jwt.js.map +1 -0
  20. package/dist/emulate/core/middleware/auth.d.ts +18 -0
  21. package/dist/emulate/core/middleware/auth.js +28 -0
  22. package/dist/emulate/core/middleware/auth.js.map +1 -0
  23. package/dist/emulate/core/middleware/error-handler.d.ts +22 -0
  24. package/dist/emulate/core/middleware/error-handler.js +72 -0
  25. package/dist/emulate/core/middleware/error-handler.js.map +1 -0
  26. package/dist/emulate/core/pagination.d.ts +21 -0
  27. package/dist/emulate/core/pagination.js +35 -0
  28. package/dist/emulate/core/pagination.js.map +1 -0
  29. package/dist/emulate/core/plugin.d.ts +15 -0
  30. package/dist/emulate/core/plugin.js +2 -0
  31. package/dist/emulate/core/plugin.js.map +1 -0
  32. package/dist/emulate/core/server.d.ts +17 -0
  33. package/dist/emulate/core/server.js +116 -0
  34. package/dist/emulate/core/server.js.map +1 -0
  35. package/dist/emulate/core/store.d.ts +42 -0
  36. package/dist/emulate/core/store.js +148 -0
  37. package/dist/emulate/core/store.js.map +1 -0
  38. package/dist/emulate/index.d.ts +25 -0
  39. package/dist/emulate/index.js +47 -0
  40. package/dist/emulate/index.js.map +1 -0
  41. package/dist/emulate/workos/entities.d.ts +360 -0
  42. package/dist/emulate/workos/entities.js +2 -0
  43. package/dist/emulate/workos/entities.js.map +1 -0
  44. package/dist/emulate/workos/event-bus.d.ts +12 -0
  45. package/dist/emulate/workos/event-bus.js +45 -0
  46. package/dist/emulate/workos/event-bus.js.map +1 -0
  47. package/dist/emulate/workos/helpers.d.ts +63 -0
  48. package/dist/emulate/workos/helpers.js +518 -0
  49. package/dist/emulate/workos/helpers.js.map +1 -0
  50. package/dist/emulate/workos/index.d.ts +91 -0
  51. package/dist/emulate/workos/index.js +319 -0
  52. package/dist/emulate/workos/index.js.map +1 -0
  53. package/dist/emulate/workos/routes/api-keys.d.ts +2 -0
  54. package/dist/emulate/workos/routes/api-keys.js +35 -0
  55. package/dist/emulate/workos/routes/api-keys.js.map +1 -0
  56. package/dist/emulate/workos/routes/audit-logs.d.ts +2 -0
  57. package/dist/emulate/workos/routes/audit-logs.js +107 -0
  58. package/dist/emulate/workos/routes/audit-logs.js.map +1 -0
  59. package/dist/emulate/workos/routes/auth-challenges.d.ts +2 -0
  60. package/dist/emulate/workos/routes/auth-challenges.js +51 -0
  61. package/dist/emulate/workos/routes/auth-challenges.js.map +1 -0
  62. package/dist/emulate/workos/routes/auth-factors.d.ts +2 -0
  63. package/dist/emulate/workos/routes/auth-factors.js +51 -0
  64. package/dist/emulate/workos/routes/auth-factors.js.map +1 -0
  65. package/dist/emulate/workos/routes/auth.d.ts +2 -0
  66. package/dist/emulate/workos/routes/auth.js +349 -0
  67. package/dist/emulate/workos/routes/auth.js.map +1 -0
  68. package/dist/emulate/workos/routes/authorization-checks.d.ts +10 -0
  69. package/dist/emulate/workos/routes/authorization-checks.js +135 -0
  70. package/dist/emulate/workos/routes/authorization-checks.js.map +1 -0
  71. package/dist/emulate/workos/routes/authorization-org-roles.d.ts +2 -0
  72. package/dist/emulate/workos/routes/authorization-org-roles.js +206 -0
  73. package/dist/emulate/workos/routes/authorization-org-roles.js.map +1 -0
  74. package/dist/emulate/workos/routes/authorization-permissions.d.ts +2 -0
  75. package/dist/emulate/workos/routes/authorization-permissions.js +78 -0
  76. package/dist/emulate/workos/routes/authorization-permissions.js.map +1 -0
  77. package/dist/emulate/workos/routes/authorization-resources.d.ts +2 -0
  78. package/dist/emulate/workos/routes/authorization-resources.js +128 -0
  79. package/dist/emulate/workos/routes/authorization-resources.js.map +1 -0
  80. package/dist/emulate/workos/routes/authorization-roles.d.ts +2 -0
  81. package/dist/emulate/workos/routes/authorization-roles.js +136 -0
  82. package/dist/emulate/workos/routes/authorization-roles.js.map +1 -0
  83. package/dist/emulate/workos/routes/config.d.ts +2 -0
  84. package/dist/emulate/workos/routes/config.js +56 -0
  85. package/dist/emulate/workos/routes/config.js.map +1 -0
  86. package/dist/emulate/workos/routes/connect.d.ts +2 -0
  87. package/dist/emulate/workos/routes/connect.js +69 -0
  88. package/dist/emulate/workos/routes/connect.js.map +1 -0
  89. package/dist/emulate/workos/routes/connections.d.ts +2 -0
  90. package/dist/emulate/workos/routes/connections.js +77 -0
  91. package/dist/emulate/workos/routes/connections.js.map +1 -0
  92. package/dist/emulate/workos/routes/data-integrations.d.ts +2 -0
  93. package/dist/emulate/workos/routes/data-integrations.js +55 -0
  94. package/dist/emulate/workos/routes/data-integrations.js.map +1 -0
  95. package/dist/emulate/workos/routes/directories.d.ts +2 -0
  96. package/dist/emulate/workos/routes/directories.js +106 -0
  97. package/dist/emulate/workos/routes/directories.js.map +1 -0
  98. package/dist/emulate/workos/routes/email-verification.d.ts +2 -0
  99. package/dist/emulate/workos/routes/email-verification.js +49 -0
  100. package/dist/emulate/workos/routes/email-verification.js.map +1 -0
  101. package/dist/emulate/workos/routes/events.d.ts +2 -0
  102. package/dist/emulate/workos/routes/events.js +21 -0
  103. package/dist/emulate/workos/routes/events.js.map +1 -0
  104. package/dist/emulate/workos/routes/feature-flags.d.ts +2 -0
  105. package/dist/emulate/workos/routes/feature-flags.js +131 -0
  106. package/dist/emulate/workos/routes/feature-flags.js.map +1 -0
  107. package/dist/emulate/workos/routes/invitations.d.ts +2 -0
  108. package/dist/emulate/workos/routes/invitations.js +125 -0
  109. package/dist/emulate/workos/routes/invitations.js.map +1 -0
  110. package/dist/emulate/workos/routes/legacy-mfa.d.ts +2 -0
  111. package/dist/emulate/workos/routes/legacy-mfa.js +75 -0
  112. package/dist/emulate/workos/routes/legacy-mfa.js.map +1 -0
  113. package/dist/emulate/workos/routes/magic-auth.d.ts +2 -0
  114. package/dist/emulate/workos/routes/magic-auth.js +32 -0
  115. package/dist/emulate/workos/routes/magic-auth.js.map +1 -0
  116. package/dist/emulate/workos/routes/memberships.d.ts +2 -0
  117. package/dist/emulate/workos/routes/memberships.js +118 -0
  118. package/dist/emulate/workos/routes/memberships.js.map +1 -0
  119. package/dist/emulate/workos/routes/organization-domains.d.ts +2 -0
  120. package/dist/emulate/workos/routes/organization-domains.js +58 -0
  121. package/dist/emulate/workos/routes/organization-domains.js.map +1 -0
  122. package/dist/emulate/workos/routes/organizations.d.ts +2 -0
  123. package/dist/emulate/workos/routes/organizations.js +133 -0
  124. package/dist/emulate/workos/routes/organizations.js.map +1 -0
  125. package/dist/emulate/workos/routes/password-reset.d.ts +2 -0
  126. package/dist/emulate/workos/routes/password-reset.js +61 -0
  127. package/dist/emulate/workos/routes/password-reset.js.map +1 -0
  128. package/dist/emulate/workos/routes/pipes.d.ts +2 -0
  129. package/dist/emulate/workos/routes/pipes.js +86 -0
  130. package/dist/emulate/workos/routes/pipes.js.map +1 -0
  131. package/dist/emulate/workos/routes/portal.d.ts +2 -0
  132. package/dist/emulate/workos/routes/portal.js +18 -0
  133. package/dist/emulate/workos/routes/portal.js.map +1 -0
  134. package/dist/emulate/workos/routes/radar.d.ts +2 -0
  135. package/dist/emulate/workos/routes/radar.js +45 -0
  136. package/dist/emulate/workos/routes/radar.js.map +1 -0
  137. package/dist/emulate/workos/routes/sessions.d.ts +2 -0
  138. package/dist/emulate/workos/routes/sessions.js +51 -0
  139. package/dist/emulate/workos/routes/sessions.js.map +1 -0
  140. package/dist/emulate/workos/routes/sso.d.ts +2 -0
  141. package/dist/emulate/workos/routes/sso.js +160 -0
  142. package/dist/emulate/workos/routes/sso.js.map +1 -0
  143. package/dist/emulate/workos/routes/user-features.d.ts +2 -0
  144. package/dist/emulate/workos/routes/user-features.js +50 -0
  145. package/dist/emulate/workos/routes/user-features.js.map +1 -0
  146. package/dist/emulate/workos/routes/users.d.ts +2 -0
  147. package/dist/emulate/workos/routes/users.js +133 -0
  148. package/dist/emulate/workos/routes/users.js.map +1 -0
  149. package/dist/emulate/workos/routes/webhook-endpoints.d.ts +2 -0
  150. package/dist/emulate/workos/routes/webhook-endpoints.js +70 -0
  151. package/dist/emulate/workos/routes/webhook-endpoints.js.map +1 -0
  152. package/dist/emulate/workos/routes/widgets.d.ts +2 -0
  153. package/dist/emulate/workos/routes/widgets.js +27 -0
  154. package/dist/emulate/workos/routes/widgets.js.map +1 -0
  155. package/dist/emulate/workos/store.d.ts +48 -0
  156. package/dist/emulate/workos/store.js +93 -0
  157. package/dist/emulate/workos/store.js.map +1 -0
  158. package/dist/emulate/workos/webhook-signer.d.ts +1 -0
  159. package/dist/emulate/workos/webhook-signer.js +8 -0
  160. package/dist/emulate/workos/webhook-signer.js.map +1 -0
  161. package/dist/gen-routes-lib.spec.ts +659 -0
  162. package/dist/gen-routes-lib.ts +647 -0
  163. package/dist/gen-routes.ts +96 -0
  164. package/dist/lib/dev-command.d.ts +26 -0
  165. package/dist/lib/dev-command.js +122 -0
  166. package/dist/lib/dev-command.js.map +1 -0
  167. package/dist/utils/help-json.js +23 -0
  168. package/dist/utils/help-json.js.map +1 -1
  169. package/package.json +20 -7
@@ -0,0 +1,107 @@
1
+ import { notFound, parseJsonBody, validationError } from '../../core/index.js';
2
+ import { getWorkOSStore } from '../store.js';
3
+ import { formatAuditLogAction, formatAuditLogEvent, formatAuditLogExport, parseListParams } from '../helpers.js';
4
+ export function auditLogRoutes(ctx) {
5
+ const { app, store } = ctx;
6
+ const ws = getWorkOSStore(store);
7
+ // List actions
8
+ app.get('/audit_logs/actions', (c) => {
9
+ const url = new URL(c.req.url);
10
+ const params = parseListParams(url);
11
+ const result = ws.auditLogActions.list({ ...params });
12
+ return c.json({
13
+ object: 'list',
14
+ data: result.data.map(formatAuditLogAction),
15
+ list_metadata: result.list_metadata,
16
+ });
17
+ });
18
+ // Create/update action schema
19
+ app.post('/audit_logs/actions/:actionName/schemas', async (c) => {
20
+ const actionName = c.req.param('actionName');
21
+ const body = await parseJsonBody(c);
22
+ // Upsert: find existing action or create new one
23
+ let action = ws.auditLogActions.findOneBy('name', actionName);
24
+ if (action) {
25
+ // Store schema in store data keyed by action name
26
+ store.setData(`audit_schema_${actionName}`, body);
27
+ return c.json(formatAuditLogAction(action));
28
+ }
29
+ action = ws.auditLogActions.insert({
30
+ object: 'audit_log_action',
31
+ name: actionName,
32
+ description: null,
33
+ condition: null,
34
+ });
35
+ store.setData(`audit_schema_${actionName}`, body);
36
+ return c.json(formatAuditLogAction(action), 201);
37
+ });
38
+ // Create audit log event
39
+ app.post('/audit_logs/events', async (c) => {
40
+ const body = await parseJsonBody(c);
41
+ const organizationId = body.organization_id;
42
+ if (!organizationId) {
43
+ throw validationError('organization_id is required', [{ field: 'organization_id', code: 'required' }]);
44
+ }
45
+ const actionBody = body.action;
46
+ if (!actionBody?.name) {
47
+ throw validationError('action.name is required', [{ field: 'action.name', code: 'required' }]);
48
+ }
49
+ const event = ws.auditLogEvents.insert({
50
+ object: 'audit_log_event',
51
+ organization_id: organizationId,
52
+ action: {
53
+ name: actionBody.name,
54
+ type: actionBody.type ?? 'C',
55
+ id: actionBody.id ?? actionBody.name,
56
+ },
57
+ actor: body.actor ?? {},
58
+ targets: body.targets ?? [],
59
+ metadata: body.metadata ?? null,
60
+ occurred_at: body.occurred_at ?? new Date().toISOString(),
61
+ });
62
+ return c.json(formatAuditLogEvent(event), 201);
63
+ });
64
+ // Create export (auto-transition to ready)
65
+ app.post('/audit_logs/exports', async (c) => {
66
+ const body = await parseJsonBody(c);
67
+ const organizationId = body.organization_id;
68
+ if (!organizationId) {
69
+ throw validationError('organization_id is required', [{ field: 'organization_id', code: 'required' }]);
70
+ }
71
+ const exp = ws.auditLogExports.insert({
72
+ object: 'audit_log_export',
73
+ organization_id: organizationId,
74
+ state: 'ready',
75
+ url: `https://emulator.workos.test/exports/audit_log_export_mock.csv`,
76
+ filters: body.filters ?? {},
77
+ });
78
+ return c.json(formatAuditLogExport(exp), 201);
79
+ });
80
+ // Get export
81
+ app.get('/audit_logs/exports/:id', (c) => {
82
+ const exp = ws.auditLogExports.get(c.req.param('id'));
83
+ if (!exp)
84
+ throw notFound('AuditLogExport');
85
+ return c.json(formatAuditLogExport(exp));
86
+ });
87
+ // Get org audit log configuration
88
+ app.get('/organizations/:id/audit_log_configuration', (c) => {
89
+ const orgId = c.req.param('id');
90
+ return c.json({
91
+ object: 'audit_log_configuration',
92
+ organization_id: orgId,
93
+ enabled: true,
94
+ retention_days: 365,
95
+ });
96
+ });
97
+ // Get org audit logs retention
98
+ app.get('/organizations/:id/audit_logs_retention', (c) => {
99
+ const orgId = c.req.param('id');
100
+ return c.json({
101
+ object: 'audit_logs_retention',
102
+ organization_id: orgId,
103
+ retention_days: 365,
104
+ });
105
+ });
106
+ }
107
+ //# sourceMappingURL=audit-logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-logs.js","sourceRoot":"","sources":["../../../../src/emulate/workos/routes/audit-logs.ts"],"names":[],"mappings":"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","sourcesContent":["import { type RouteContext, notFound, parseJsonBody, validationError } from '../../core/index.js';\nimport { getWorkOSStore } from '../store.js';\nimport { formatAuditLogAction, formatAuditLogEvent, formatAuditLogExport, parseListParams } from '../helpers.js';\n\nexport function auditLogRoutes(ctx: RouteContext): void {\n const { app, store } = ctx;\n const ws = getWorkOSStore(store);\n\n // List actions\n app.get('/audit_logs/actions', (c) => {\n const url = new URL(c.req.url);\n const params = parseListParams(url);\n const result = ws.auditLogActions.list({ ...params });\n return c.json({\n object: 'list',\n data: result.data.map(formatAuditLogAction),\n list_metadata: result.list_metadata,\n });\n });\n\n // Create/update action schema\n app.post('/audit_logs/actions/:actionName/schemas', async (c) => {\n const actionName = c.req.param('actionName');\n const body = await parseJsonBody(c);\n\n // Upsert: find existing action or create new one\n let action = ws.auditLogActions.findOneBy('name', actionName);\n if (action) {\n // Store schema in store data keyed by action name\n store.setData(`audit_schema_${actionName}`, body);\n return c.json(formatAuditLogAction(action));\n }\n\n action = ws.auditLogActions.insert({\n object: 'audit_log_action',\n name: actionName,\n description: null,\n condition: null,\n });\n store.setData(`audit_schema_${actionName}`, body);\n return c.json(formatAuditLogAction(action), 201);\n });\n\n // Create audit log event\n app.post('/audit_logs/events', async (c) => {\n const body = await parseJsonBody(c);\n const organizationId = body.organization_id as string | undefined;\n if (!organizationId) {\n throw validationError('organization_id is required', [{ field: 'organization_id', code: 'required' }]);\n }\n\n const actionBody = body.action as Record<string, string> | undefined;\n if (!actionBody?.name) {\n throw validationError('action.name is required', [{ field: 'action.name', code: 'required' }]);\n }\n\n const event = ws.auditLogEvents.insert({\n object: 'audit_log_event',\n organization_id: organizationId,\n action: {\n name: actionBody.name,\n type: actionBody.type ?? 'C',\n id: actionBody.id ?? actionBody.name,\n },\n actor: (body.actor as Record<string, unknown>) ?? {},\n targets: (body.targets as Array<Record<string, unknown>>) ?? [],\n metadata: (body.metadata as Record<string, unknown>) ?? null,\n occurred_at: (body.occurred_at as string) ?? new Date().toISOString(),\n });\n\n return c.json(formatAuditLogEvent(event), 201);\n });\n\n // Create export (auto-transition to ready)\n app.post('/audit_logs/exports', async (c) => {\n const body = await parseJsonBody(c);\n const organizationId = body.organization_id as string | undefined;\n if (!organizationId) {\n throw validationError('organization_id is required', [{ field: 'organization_id', code: 'required' }]);\n }\n\n const exp = ws.auditLogExports.insert({\n object: 'audit_log_export',\n organization_id: organizationId,\n state: 'ready',\n url: `https://emulator.workos.test/exports/audit_log_export_mock.csv`,\n filters: (body.filters as Record<string, unknown>) ?? {},\n });\n\n return c.json(formatAuditLogExport(exp), 201);\n });\n\n // Get export\n app.get('/audit_logs/exports/:id', (c) => {\n const exp = ws.auditLogExports.get(c.req.param('id'));\n if (!exp) throw notFound('AuditLogExport');\n return c.json(formatAuditLogExport(exp));\n });\n\n // Get org audit log configuration\n app.get('/organizations/:id/audit_log_configuration', (c) => {\n const orgId = c.req.param('id');\n return c.json({\n object: 'audit_log_configuration',\n organization_id: orgId,\n enabled: true,\n retention_days: 365,\n });\n });\n\n // Get org audit logs retention\n app.get('/organizations/:id/audit_logs_retention', (c) => {\n const orgId = c.req.param('id');\n return c.json({\n object: 'audit_logs_retention',\n organization_id: orgId,\n retention_days: 365,\n });\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { type RouteContext } from '../../core/index.js';
2
+ export declare function authChallengeRoutes(ctx: RouteContext): void;
@@ -0,0 +1,51 @@
1
+ import { notFound, parseJsonBody, WorkOSApiError } from '../../core/index.js';
2
+ import { getWorkOSStore } from '../store.js';
3
+ import { formatAuthChallenge, expiresIn, isExpired, generateCode } from '../helpers.js';
4
+ export function authChallengeRoutes(ctx) {
5
+ const { app, store } = ctx;
6
+ const ws = getWorkOSStore(store);
7
+ app.post('/user_management/auth_factors/:id/challenges', async (c) => {
8
+ const factorId = c.req.param('id');
9
+ const factor = ws.authFactors.get(factorId);
10
+ if (!factor)
11
+ throw notFound('AuthenticationFactor');
12
+ const user = ws.users.get(factor.user_id);
13
+ if (!user)
14
+ throw notFound('User');
15
+ // Emulator generates a code and stores it for verification
16
+ const code = generateCode();
17
+ const challenge = ws.authChallenges.insert({
18
+ object: 'authentication_challenge',
19
+ user_id: user.id,
20
+ factor_id: factor.id,
21
+ expires_at: expiresIn(10),
22
+ code,
23
+ });
24
+ return c.json(formatAuthChallenge(challenge), 201);
25
+ });
26
+ app.post('/user_management/auth_challenges/:id/verify', async (c) => {
27
+ const challengeId = c.req.param('id');
28
+ const challenge = ws.authChallenges.get(challengeId);
29
+ if (!challenge)
30
+ throw notFound('AuthenticationChallenge');
31
+ if (isExpired(challenge.expires_at)) {
32
+ ws.authChallenges.delete(challenge.id);
33
+ throw new WorkOSApiError(400, 'Challenge has expired', 'expired_challenge');
34
+ }
35
+ const body = await parseJsonBody(c);
36
+ const code = body.code;
37
+ if (!code) {
38
+ throw new WorkOSApiError(400, 'code is required', 'invalid_request');
39
+ }
40
+ // In the emulator, accept the stored code or any 6-digit code for convenience
41
+ if (challenge.code && code !== challenge.code) {
42
+ throw new WorkOSApiError(400, 'Invalid one-time code', 'invalid_one_time_code');
43
+ }
44
+ ws.authChallenges.delete(challenge.id);
45
+ return c.json({
46
+ challenge: formatAuthChallenge(challenge),
47
+ valid: true,
48
+ });
49
+ });
50
+ }
51
+ //# sourceMappingURL=auth-challenges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-challenges.js","sourceRoot":"","sources":["../../../../src/emulate/workos/routes/auth-challenges.ts"],"names":[],"mappings":"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","sourcesContent":["import { type RouteContext, notFound, parseJsonBody, WorkOSApiError } from '../../core/index.js';\nimport { getWorkOSStore } from '../store.js';\nimport { formatAuthChallenge, expiresIn, isExpired, generateCode } from '../helpers.js';\n\nexport function authChallengeRoutes(ctx: RouteContext): void {\n const { app, store } = ctx;\n const ws = getWorkOSStore(store);\n\n app.post('/user_management/auth_factors/:id/challenges', async (c) => {\n const factorId = c.req.param('id');\n const factor = ws.authFactors.get(factorId);\n if (!factor) throw notFound('AuthenticationFactor');\n\n const user = ws.users.get(factor.user_id);\n if (!user) throw notFound('User');\n\n // Emulator generates a code and stores it for verification\n const code = generateCode();\n\n const challenge = ws.authChallenges.insert({\n object: 'authentication_challenge',\n user_id: user.id,\n factor_id: factor.id,\n expires_at: expiresIn(10),\n code,\n });\n\n return c.json(formatAuthChallenge(challenge), 201);\n });\n\n app.post('/user_management/auth_challenges/:id/verify', async (c) => {\n const challengeId = c.req.param('id');\n const challenge = ws.authChallenges.get(challengeId);\n if (!challenge) throw notFound('AuthenticationChallenge');\n\n if (isExpired(challenge.expires_at)) {\n ws.authChallenges.delete(challenge.id);\n throw new WorkOSApiError(400, 'Challenge has expired', 'expired_challenge');\n }\n\n const body = await parseJsonBody(c);\n const code = body.code as string;\n if (!code) {\n throw new WorkOSApiError(400, 'code is required', 'invalid_request');\n }\n\n // In the emulator, accept the stored code or any 6-digit code for convenience\n if (challenge.code && code !== challenge.code) {\n throw new WorkOSApiError(400, 'Invalid one-time code', 'invalid_one_time_code');\n }\n\n ws.authChallenges.delete(challenge.id);\n\n return c.json({\n challenge: formatAuthChallenge(challenge),\n valid: true,\n });\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { type RouteContext } from '../../core/index.js';
2
+ export declare function authFactorRoutes(ctx: RouteContext): void;
@@ -0,0 +1,51 @@
1
+ import { notFound, parseJsonBody } from '../../core/index.js';
2
+ import { getWorkOSStore } from '../store.js';
3
+ import { formatAuthFactor } from '../helpers.js';
4
+ import { randomBytes } from 'node:crypto';
5
+ export function authFactorRoutes(ctx) {
6
+ const { app, store } = ctx;
7
+ const ws = getWorkOSStore(store);
8
+ app.post('/user_management/users/:userlandUserId/auth_factors', async (c) => {
9
+ const userId = c.req.param('userlandUserId');
10
+ const user = ws.users.get(userId);
11
+ if (!user)
12
+ throw notFound('User');
13
+ const body = await parseJsonBody(c);
14
+ const type = body.type ?? 'totp';
15
+ const issuer = body.totp_issuer ?? 'WorkOS Emulator';
16
+ const secret = randomBytes(20).toString('hex').slice(0, 32).toUpperCase();
17
+ const uri = `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(user.email)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}`;
18
+ const factor = ws.authFactors.insert({
19
+ object: 'authentication_factor',
20
+ user_id: user.id,
21
+ type: type,
22
+ totp: {
23
+ issuer,
24
+ user: user.email,
25
+ uri,
26
+ },
27
+ });
28
+ return c.json(formatAuthFactor(factor), 201);
29
+ });
30
+ app.get('/user_management/users/:userlandUserId/auth_factors', (c) => {
31
+ const userId = c.req.param('userlandUserId');
32
+ const user = ws.users.get(userId);
33
+ if (!user)
34
+ throw notFound('User');
35
+ const factors = ws.authFactors.findBy('user_id', user.id);
36
+ return c.json({
37
+ object: 'list',
38
+ data: factors.map(formatAuthFactor),
39
+ list_metadata: { before: null, after: null },
40
+ });
41
+ });
42
+ app.delete('/user_management/auth_factors/:id', (c) => {
43
+ const factorId = c.req.param('id');
44
+ const factor = ws.authFactors.get(factorId);
45
+ if (!factor)
46
+ throw notFound('AuthenticationFactor');
47
+ ws.authFactors.delete(factor.id);
48
+ return c.body(null, 204);
49
+ });
50
+ }
51
+ //# sourceMappingURL=auth-factors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-factors.js","sourceRoot":"","sources":["../../../../src/emulate/workos/routes/auth-factors.ts"],"names":[],"mappings":"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","sourcesContent":["import { type RouteContext, notFound, parseJsonBody } from '../../core/index.js';\nimport { getWorkOSStore } from '../store.js';\nimport { formatAuthFactor } from '../helpers.js';\nimport { randomBytes } from 'node:crypto';\n\nexport function authFactorRoutes(ctx: RouteContext): void {\n const { app, store } = ctx;\n const ws = getWorkOSStore(store);\n\n app.post('/user_management/users/:userlandUserId/auth_factors', async (c) => {\n const userId = c.req.param('userlandUserId');\n const user = ws.users.get(userId);\n if (!user) throw notFound('User');\n\n const body = await parseJsonBody(c);\n const type = (body.type as string) ?? 'totp';\n const issuer = (body.totp_issuer as string) ?? 'WorkOS Emulator';\n const secret = randomBytes(20).toString('hex').slice(0, 32).toUpperCase();\n const uri = `otpauth://totp/${encodeURIComponent(issuer)}:${encodeURIComponent(user.email)}?secret=${secret}&issuer=${encodeURIComponent(issuer)}`;\n\n const factor = ws.authFactors.insert({\n object: 'authentication_factor',\n user_id: user.id,\n type: type as 'totp',\n totp: {\n issuer,\n user: user.email,\n uri,\n },\n });\n\n return c.json(formatAuthFactor(factor), 201);\n });\n\n app.get('/user_management/users/:userlandUserId/auth_factors', (c) => {\n const userId = c.req.param('userlandUserId');\n const user = ws.users.get(userId);\n if (!user) throw notFound('User');\n\n const factors = ws.authFactors.findBy('user_id', user.id);\n return c.json({\n object: 'list',\n data: factors.map(formatAuthFactor),\n list_metadata: { before: null, after: null },\n });\n });\n\n app.delete('/user_management/auth_factors/:id', (c) => {\n const factorId = c.req.param('id');\n const factor = ws.authFactors.get(factorId);\n if (!factor) throw notFound('AuthenticationFactor');\n\n ws.authFactors.delete(factor.id);\n return c.body(null, 204);\n });\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { type RouteContext } from '../../core/index.js';
2
+ export declare function authRoutes(ctx: RouteContext): void;
@@ -0,0 +1,349 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { notFound, parseJsonBody, WorkOSApiError, generateId } from '../../core/index.js';
3
+ import { getWorkOSStore } from '../store.js';
4
+ import { formatUser, formatDeviceAuthorization, verifyPassword, isExpired, expiresIn, assertLocalRedirectUri, sealSession, } from '../helpers.js';
5
+ export function authRoutes(ctx) {
6
+ const { app, store, jwt } = ctx;
7
+ const ws = getWorkOSStore(store);
8
+ app.get('/user_management/authorize', (c) => {
9
+ const url = new URL(c.req.url);
10
+ const redirectUri = url.searchParams.get('redirect_uri');
11
+ const state = url.searchParams.get('state');
12
+ const codeChallenge = url.searchParams.get('code_challenge');
13
+ const codeChallengeMethod = url.searchParams.get('code_challenge_method');
14
+ const loginHint = url.searchParams.get('login_hint');
15
+ if (!redirectUri) {
16
+ throw new WorkOSApiError(400, 'redirect_uri is required', 'invalid_request');
17
+ }
18
+ assertLocalRedirectUri(redirectUri);
19
+ let user;
20
+ if (loginHint) {
21
+ user = ws.users.findOneBy('email', loginHint);
22
+ if (!user) {
23
+ const redirect = new URL(redirectUri);
24
+ redirect.searchParams.set('error', 'user_not_found');
25
+ if (state)
26
+ redirect.searchParams.set('state', state);
27
+ return c.redirect(redirect.toString());
28
+ }
29
+ }
30
+ else {
31
+ const users = ws.users.all();
32
+ user = users[0];
33
+ }
34
+ if (!user) {
35
+ const redirect = new URL(redirectUri);
36
+ redirect.searchParams.set('error', 'no_users');
37
+ if (state)
38
+ redirect.searchParams.set('state', state);
39
+ return c.redirect(redirect.toString());
40
+ }
41
+ const authCode = ws.authCodes.insert({
42
+ user_id: user.id,
43
+ organization_id: null,
44
+ code: generateId('auth_code'),
45
+ redirect_uri: redirectUri,
46
+ expires_at: expiresIn(10),
47
+ code_challenge: codeChallenge ?? null,
48
+ code_challenge_method: codeChallengeMethod ?? null,
49
+ });
50
+ const redirect = new URL(redirectUri);
51
+ redirect.searchParams.set('code', authCode.code);
52
+ if (state)
53
+ redirect.searchParams.set('state', state);
54
+ return c.redirect(redirect.toString());
55
+ });
56
+ // Device authorization endpoint
57
+ app.post('/user_management/authorize/device', async (c) => {
58
+ const body = await parseJsonBody(c);
59
+ const clientId = body.client_id;
60
+ if (!clientId) {
61
+ throw new WorkOSApiError(400, 'client_id is required', 'invalid_request');
62
+ }
63
+ // Auto-approve with first user for emulator convenience
64
+ const users = ws.users.all();
65
+ const user = users[0] ?? null;
66
+ const deviceAuth = ws.deviceAuthorizations.insert({
67
+ device_code: generateId('dev_code'),
68
+ user_code: Math.random().toString(36).slice(2, 10).toUpperCase(),
69
+ user_id: user?.id ?? null,
70
+ client_id: clientId,
71
+ expires_at: expiresIn(15),
72
+ interval: 5,
73
+ });
74
+ return c.json(formatDeviceAuthorization(deviceAuth));
75
+ });
76
+ // AuthKit SDK uses /x/authkit/users/authenticate for the same flow
77
+ const authenticateHandler = async (c) => {
78
+ const body = await parseJsonBody(c);
79
+ const grantType = body.grant_type;
80
+ const clientId = body.client_id;
81
+ const clientSecret = body.client_secret;
82
+ if (!grantType) {
83
+ throw new WorkOSApiError(400, 'grant_type is required', 'invalid_request');
84
+ }
85
+ let user;
86
+ let organizationId = null;
87
+ let authMethod;
88
+ switch (grantType) {
89
+ case 'authorization_code': {
90
+ const code = body.code;
91
+ if (!code)
92
+ throw new WorkOSApiError(400, 'code is required', 'invalid_request');
93
+ const authCode = ws.authCodes.all().find((ac) => ac.code === code);
94
+ if (!authCode)
95
+ throw new WorkOSApiError(400, 'Invalid code', 'invalid_code');
96
+ if (isExpired(authCode.expires_at)) {
97
+ throw new WorkOSApiError(400, 'Code has expired', 'expired_code');
98
+ }
99
+ if (authCode.code_challenge) {
100
+ const codeVerifier = body.code_verifier;
101
+ if (!codeVerifier) {
102
+ throw new WorkOSApiError(400, 'code_verifier is required', 'invalid_request');
103
+ }
104
+ const method = authCode.code_challenge_method ?? 'S256';
105
+ let challenge;
106
+ if (method === 'S256') {
107
+ challenge = createHash('sha256').update(codeVerifier).digest('base64url');
108
+ }
109
+ else {
110
+ challenge = codeVerifier;
111
+ }
112
+ if (challenge !== authCode.code_challenge) {
113
+ throw new WorkOSApiError(400, 'Invalid code_verifier', 'invalid_code_verifier');
114
+ }
115
+ }
116
+ user = ws.users.get(authCode.user_id);
117
+ organizationId = authCode.organization_id;
118
+ ws.authCodes.delete(authCode.id);
119
+ authMethod = 'OAuth';
120
+ break;
121
+ }
122
+ case 'password': {
123
+ const email = body.email;
124
+ const password = body.password;
125
+ if (!email || !password) {
126
+ throw new WorkOSApiError(400, 'email and password are required', 'invalid_request');
127
+ }
128
+ user = ws.users.findOneBy('email', email);
129
+ if (!user || !user.password_hash || !verifyPassword(password, user.password_hash)) {
130
+ throw new WorkOSApiError(401, 'Invalid credentials', 'invalid_credentials');
131
+ }
132
+ authMethod = 'Password';
133
+ break;
134
+ }
135
+ // Accept both old and new grant type names for magic-auth
136
+ case 'urn:workos:oauth:grant-type:magic-auth':
137
+ case 'urn:workos:oauth:grant-type:magic-auth:code': {
138
+ const code = body.code;
139
+ const email = body.email;
140
+ if (!code || !email) {
141
+ throw new WorkOSApiError(400, 'code and email are required', 'invalid_request');
142
+ }
143
+ const magicAuth = ws.magicAuths.all().find((ma) => ma.code === code && ma.email === email);
144
+ if (!magicAuth) {
145
+ throw new WorkOSApiError(400, 'Invalid code', 'invalid_code');
146
+ }
147
+ if (isExpired(magicAuth.expires_at)) {
148
+ throw new WorkOSApiError(400, 'Code has expired', 'expired_code');
149
+ }
150
+ user = ws.users.get(magicAuth.user_id);
151
+ ws.magicAuths.delete(magicAuth.id);
152
+ authMethod = 'MagicAuth';
153
+ break;
154
+ }
155
+ // Accept both old and new grant type names for email-verification
156
+ case 'urn:workos:oauth:grant-type:email-verification':
157
+ case 'urn:workos:oauth:grant-type:email-verification:code': {
158
+ const code = body.code;
159
+ const userId = body.user_id;
160
+ if (!code || !userId) {
161
+ throw new WorkOSApiError(400, 'code and user_id are required', 'invalid_request');
162
+ }
163
+ const ev = ws.emailVerifications.findBy('user_id', userId).find((v) => v.code === code);
164
+ if (!ev) {
165
+ throw new WorkOSApiError(400, 'Invalid code', 'invalid_code');
166
+ }
167
+ if (isExpired(ev.expires_at)) {
168
+ throw new WorkOSApiError(400, 'Code has expired', 'expired_code');
169
+ }
170
+ ws.users.update(userId, { email_verified: true });
171
+ ws.emailVerifications.delete(ev.id);
172
+ user = ws.users.get(userId);
173
+ authMethod = 'EmailVerification';
174
+ break;
175
+ }
176
+ case 'refresh_token': {
177
+ const token = body.refresh_token;
178
+ if (!token) {
179
+ throw new WorkOSApiError(400, 'refresh_token is required', 'invalid_request');
180
+ }
181
+ const refreshToken = ws.refreshTokens.findOneBy('token', token);
182
+ if (!refreshToken) {
183
+ throw new WorkOSApiError(400, 'Invalid refresh token', 'invalid_grant');
184
+ }
185
+ if (isExpired(refreshToken.expires_at)) {
186
+ ws.refreshTokens.delete(refreshToken.id);
187
+ throw new WorkOSApiError(400, 'Refresh token has expired', 'invalid_grant');
188
+ }
189
+ user = ws.users.get(refreshToken.user_id);
190
+ // Allow body.organization_id to switch org context (switchToOrganization)
191
+ organizationId = body.organization_id ?? refreshToken.organization_id;
192
+ // Rotate: delete old, issue new below
193
+ ws.refreshTokens.delete(refreshToken.id);
194
+ authMethod = 'OAuth';
195
+ break;
196
+ }
197
+ case 'urn:workos:oauth:grant-type:mfa-totp': {
198
+ const code = body.code;
199
+ const pendingToken = body.pending_authentication_token;
200
+ const challengeId = body.authentication_challenge_id;
201
+ if (!code || !pendingToken || !challengeId) {
202
+ throw new WorkOSApiError(400, 'code, pending_authentication_token, and authentication_challenge_id are required', 'invalid_request');
203
+ }
204
+ const pending = store.getData(`pending_auth:${pendingToken}`);
205
+ if (!pending) {
206
+ throw new WorkOSApiError(400, 'Invalid pending authentication token', 'invalid_pending_authentication_token');
207
+ }
208
+ const challenge = ws.authChallenges.get(challengeId);
209
+ if (!challenge) {
210
+ throw new WorkOSApiError(400, 'Invalid authentication challenge', 'invalid_request');
211
+ }
212
+ if (isExpired(challenge.expires_at)) {
213
+ ws.authChallenges.delete(challenge.id);
214
+ throw new WorkOSApiError(400, 'Challenge has expired', 'expired_challenge');
215
+ }
216
+ // Verify code against the challenge's stored code
217
+ if (challenge.code && code !== challenge.code) {
218
+ throw new WorkOSApiError(400, 'Invalid one-time code', 'invalid_one_time_code');
219
+ }
220
+ ws.authChallenges.delete(challenge.id);
221
+ store.setData(`pending_auth:${pendingToken}`, undefined);
222
+ user = ws.users.get(pending.user_id);
223
+ organizationId = pending.organization_id;
224
+ authMethod = 'MFA';
225
+ break;
226
+ }
227
+ case 'urn:workos:oauth:grant-type:organization-selection': {
228
+ const pendingToken = body.pending_authentication_token;
229
+ const orgId = body.organization_id;
230
+ if (!pendingToken || !orgId) {
231
+ throw new WorkOSApiError(400, 'pending_authentication_token and organization_id are required', 'invalid_request');
232
+ }
233
+ const pending = store.getData(`pending_auth:${pendingToken}`);
234
+ if (!pending) {
235
+ throw new WorkOSApiError(400, 'Invalid pending authentication token', 'invalid_pending_authentication_token');
236
+ }
237
+ const org = ws.organizations.get(orgId);
238
+ if (!org)
239
+ throw notFound('Organization');
240
+ store.setData(`pending_auth:${pendingToken}`, undefined);
241
+ user = ws.users.get(pending.user_id);
242
+ organizationId = orgId;
243
+ authMethod = pending.auth_method;
244
+ break;
245
+ }
246
+ case 'urn:ietf:params:oauth:grant-type:device_code': {
247
+ const deviceCode = body.device_code;
248
+ if (!deviceCode) {
249
+ throw new WorkOSApiError(400, 'device_code is required', 'invalid_request');
250
+ }
251
+ const deviceAuth = ws.deviceAuthorizations.findOneBy('device_code', deviceCode);
252
+ if (!deviceAuth) {
253
+ throw new WorkOSApiError(400, 'Invalid device code', 'invalid_grant');
254
+ }
255
+ if (isExpired(deviceAuth.expires_at)) {
256
+ ws.deviceAuthorizations.delete(deviceAuth.id);
257
+ throw new WorkOSApiError(400, 'Device code has expired', 'expired_token');
258
+ }
259
+ if (!deviceAuth.user_id) {
260
+ throw new WorkOSApiError(400, 'Authorization pending', 'authorization_pending');
261
+ }
262
+ user = ws.users.get(deviceAuth.user_id);
263
+ ws.deviceAuthorizations.delete(deviceAuth.id);
264
+ authMethod = 'OAuth';
265
+ break;
266
+ }
267
+ default:
268
+ throw new WorkOSApiError(400, `Unsupported grant_type: ${grantType}`, 'invalid_request');
269
+ }
270
+ if (!user)
271
+ throw notFound('User');
272
+ ws.users.update(user.id, { last_sign_in_at: new Date().toISOString() });
273
+ const updatedUser = ws.users.get(user.id);
274
+ const session = ws.sessions.insert({
275
+ object: 'session',
276
+ user_id: user.id,
277
+ organization_id: organizationId,
278
+ ip_address: c.req.header('x-forwarded-for') ?? null,
279
+ user_agent: c.req.header('user-agent') ?? null,
280
+ });
281
+ // Resolve role + permissions for org-scoped sessions
282
+ let roleSlug;
283
+ let permissionSlugs;
284
+ if (organizationId) {
285
+ const membership = ws.organizationMemberships
286
+ .findBy('organization_id', organizationId)
287
+ .find((m) => m.user_id === user.id);
288
+ if (membership) {
289
+ roleSlug = membership.role.slug;
290
+ const role = ws.roles
291
+ .findBy('slug', membership.role.slug)
292
+ .find((r) => r.organization_id === organizationId || r.type === 'EnvironmentRole');
293
+ if (role) {
294
+ const rps = ws.rolePermissions.findBy('role_id', role.id);
295
+ permissionSlugs = rps
296
+ .map((rp) => ws.permissions.get(rp.permission_id))
297
+ .filter(Boolean)
298
+ .map((p) => p.slug);
299
+ }
300
+ }
301
+ }
302
+ const accessToken = jwt.sign({
303
+ sub: user.id,
304
+ sid: session.id,
305
+ org_id: organizationId ?? undefined,
306
+ role: roleSlug,
307
+ permissions: permissionSlugs,
308
+ aud: clientId ?? 'workos-emulate',
309
+ });
310
+ // Store a real refresh token
311
+ const newRefreshToken = ws.refreshTokens.insert({
312
+ token: generateId('ref'),
313
+ user_id: user.id,
314
+ organization_id: organizationId,
315
+ session_id: session.id,
316
+ expires_at: expiresIn(30 * 24 * 60), // 30 days
317
+ });
318
+ // Compute sealed session when client_secret is provided
319
+ const apiKey = c.req
320
+ .header('Authorization')
321
+ ?.replace(/^Bearer\s+/i, '')
322
+ .trim();
323
+ const sealKey = clientSecret ?? apiKey;
324
+ const sealedSession = sealKey
325
+ ? sealSession({ access_token: accessToken, refresh_token: newRefreshToken.token, session_id: session.id }, sealKey)
326
+ : null;
327
+ // Emit authentication event (hybrid Option B for action-specific events)
328
+ const eventBus = store.getData('eventBus');
329
+ if (eventBus) {
330
+ const authEventType = `authentication.${authMethod.toLowerCase()}_succeeded`;
331
+ eventBus.emit({
332
+ event: authEventType,
333
+ data: { user_id: user.id, email: updatedUser.email, method: authMethod, ip_address: session.ip_address },
334
+ });
335
+ }
336
+ return c.json({
337
+ user: formatUser(updatedUser),
338
+ organization_id: organizationId,
339
+ access_token: accessToken,
340
+ refresh_token: newRefreshToken.token,
341
+ authentication_method: authMethod,
342
+ sealed_session: sealedSession,
343
+ impersonator: updatedUser.impersonator ?? undefined,
344
+ });
345
+ };
346
+ app.post('/user_management/authenticate', authenticateHandler);
347
+ app.post('/x/authkit/users/authenticate', authenticateHandler);
348
+ }
349
+ //# sourceMappingURL=auth.js.map