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.
- package/README.md +163 -6
- package/dist/bin.js +20 -1
- package/dist/bin.js.map +1 -1
- package/dist/check-coverage.ts +237 -0
- package/dist/commands/dev.d.ts +23 -0
- package/dist/commands/dev.js +139 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/emulate.d.ts +6 -0
- package/dist/commands/emulate.js +64 -0
- package/dist/commands/emulate.js.map +1 -0
- package/dist/emulate/core/id.d.ts +33 -0
- package/dist/emulate/core/id.js +58 -0
- package/dist/emulate/core/id.js.map +1 -0
- package/dist/emulate/core/index.d.ts +8 -0
- package/dist/emulate/core/index.js +8 -0
- package/dist/emulate/core/index.js.map +1 -0
- package/dist/emulate/core/jwt.d.ts +28 -0
- package/dist/emulate/core/jwt.js +78 -0
- package/dist/emulate/core/jwt.js.map +1 -0
- package/dist/emulate/core/middleware/auth.d.ts +18 -0
- package/dist/emulate/core/middleware/auth.js +28 -0
- package/dist/emulate/core/middleware/auth.js.map +1 -0
- package/dist/emulate/core/middleware/error-handler.d.ts +22 -0
- package/dist/emulate/core/middleware/error-handler.js +72 -0
- package/dist/emulate/core/middleware/error-handler.js.map +1 -0
- package/dist/emulate/core/pagination.d.ts +21 -0
- package/dist/emulate/core/pagination.js +35 -0
- package/dist/emulate/core/pagination.js.map +1 -0
- package/dist/emulate/core/plugin.d.ts +15 -0
- package/dist/emulate/core/plugin.js +2 -0
- package/dist/emulate/core/plugin.js.map +1 -0
- package/dist/emulate/core/server.d.ts +17 -0
- package/dist/emulate/core/server.js +116 -0
- package/dist/emulate/core/server.js.map +1 -0
- package/dist/emulate/core/store.d.ts +42 -0
- package/dist/emulate/core/store.js +148 -0
- package/dist/emulate/core/store.js.map +1 -0
- package/dist/emulate/index.d.ts +25 -0
- package/dist/emulate/index.js +47 -0
- package/dist/emulate/index.js.map +1 -0
- package/dist/emulate/workos/entities.d.ts +360 -0
- package/dist/emulate/workos/entities.js +2 -0
- package/dist/emulate/workos/entities.js.map +1 -0
- package/dist/emulate/workos/event-bus.d.ts +12 -0
- package/dist/emulate/workos/event-bus.js +45 -0
- package/dist/emulate/workos/event-bus.js.map +1 -0
- package/dist/emulate/workos/helpers.d.ts +63 -0
- package/dist/emulate/workos/helpers.js +518 -0
- package/dist/emulate/workos/helpers.js.map +1 -0
- package/dist/emulate/workos/index.d.ts +91 -0
- package/dist/emulate/workos/index.js +319 -0
- package/dist/emulate/workos/index.js.map +1 -0
- package/dist/emulate/workos/routes/api-keys.d.ts +2 -0
- package/dist/emulate/workos/routes/api-keys.js +35 -0
- package/dist/emulate/workos/routes/api-keys.js.map +1 -0
- package/dist/emulate/workos/routes/audit-logs.d.ts +2 -0
- package/dist/emulate/workos/routes/audit-logs.js +107 -0
- package/dist/emulate/workos/routes/audit-logs.js.map +1 -0
- package/dist/emulate/workos/routes/auth-challenges.d.ts +2 -0
- package/dist/emulate/workos/routes/auth-challenges.js +51 -0
- package/dist/emulate/workos/routes/auth-challenges.js.map +1 -0
- package/dist/emulate/workos/routes/auth-factors.d.ts +2 -0
- package/dist/emulate/workos/routes/auth-factors.js +51 -0
- package/dist/emulate/workos/routes/auth-factors.js.map +1 -0
- package/dist/emulate/workos/routes/auth.d.ts +2 -0
- package/dist/emulate/workos/routes/auth.js +349 -0
- package/dist/emulate/workos/routes/auth.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-checks.d.ts +10 -0
- package/dist/emulate/workos/routes/authorization-checks.js +135 -0
- package/dist/emulate/workos/routes/authorization-checks.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-org-roles.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-org-roles.js +206 -0
- package/dist/emulate/workos/routes/authorization-org-roles.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-permissions.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-permissions.js +78 -0
- package/dist/emulate/workos/routes/authorization-permissions.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-resources.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-resources.js +128 -0
- package/dist/emulate/workos/routes/authorization-resources.js.map +1 -0
- package/dist/emulate/workos/routes/authorization-roles.d.ts +2 -0
- package/dist/emulate/workos/routes/authorization-roles.js +136 -0
- package/dist/emulate/workos/routes/authorization-roles.js.map +1 -0
- package/dist/emulate/workos/routes/config.d.ts +2 -0
- package/dist/emulate/workos/routes/config.js +56 -0
- package/dist/emulate/workos/routes/config.js.map +1 -0
- package/dist/emulate/workos/routes/connect.d.ts +2 -0
- package/dist/emulate/workos/routes/connect.js +69 -0
- package/dist/emulate/workos/routes/connect.js.map +1 -0
- package/dist/emulate/workos/routes/connections.d.ts +2 -0
- package/dist/emulate/workos/routes/connections.js +77 -0
- package/dist/emulate/workos/routes/connections.js.map +1 -0
- package/dist/emulate/workos/routes/data-integrations.d.ts +2 -0
- package/dist/emulate/workos/routes/data-integrations.js +55 -0
- package/dist/emulate/workos/routes/data-integrations.js.map +1 -0
- package/dist/emulate/workos/routes/directories.d.ts +2 -0
- package/dist/emulate/workos/routes/directories.js +106 -0
- package/dist/emulate/workos/routes/directories.js.map +1 -0
- package/dist/emulate/workos/routes/email-verification.d.ts +2 -0
- package/dist/emulate/workos/routes/email-verification.js +49 -0
- package/dist/emulate/workos/routes/email-verification.js.map +1 -0
- package/dist/emulate/workos/routes/events.d.ts +2 -0
- package/dist/emulate/workos/routes/events.js +21 -0
- package/dist/emulate/workos/routes/events.js.map +1 -0
- package/dist/emulate/workos/routes/feature-flags.d.ts +2 -0
- package/dist/emulate/workos/routes/feature-flags.js +131 -0
- package/dist/emulate/workos/routes/feature-flags.js.map +1 -0
- package/dist/emulate/workos/routes/invitations.d.ts +2 -0
- package/dist/emulate/workos/routes/invitations.js +125 -0
- package/dist/emulate/workos/routes/invitations.js.map +1 -0
- package/dist/emulate/workos/routes/legacy-mfa.d.ts +2 -0
- package/dist/emulate/workos/routes/legacy-mfa.js +75 -0
- package/dist/emulate/workos/routes/legacy-mfa.js.map +1 -0
- package/dist/emulate/workos/routes/magic-auth.d.ts +2 -0
- package/dist/emulate/workos/routes/magic-auth.js +32 -0
- package/dist/emulate/workos/routes/magic-auth.js.map +1 -0
- package/dist/emulate/workos/routes/memberships.d.ts +2 -0
- package/dist/emulate/workos/routes/memberships.js +118 -0
- package/dist/emulate/workos/routes/memberships.js.map +1 -0
- package/dist/emulate/workos/routes/organization-domains.d.ts +2 -0
- package/dist/emulate/workos/routes/organization-domains.js +58 -0
- package/dist/emulate/workos/routes/organization-domains.js.map +1 -0
- package/dist/emulate/workos/routes/organizations.d.ts +2 -0
- package/dist/emulate/workos/routes/organizations.js +133 -0
- package/dist/emulate/workos/routes/organizations.js.map +1 -0
- package/dist/emulate/workos/routes/password-reset.d.ts +2 -0
- package/dist/emulate/workos/routes/password-reset.js +61 -0
- package/dist/emulate/workos/routes/password-reset.js.map +1 -0
- package/dist/emulate/workos/routes/pipes.d.ts +2 -0
- package/dist/emulate/workos/routes/pipes.js +86 -0
- package/dist/emulate/workos/routes/pipes.js.map +1 -0
- package/dist/emulate/workos/routes/portal.d.ts +2 -0
- package/dist/emulate/workos/routes/portal.js +18 -0
- package/dist/emulate/workos/routes/portal.js.map +1 -0
- package/dist/emulate/workos/routes/radar.d.ts +2 -0
- package/dist/emulate/workos/routes/radar.js +45 -0
- package/dist/emulate/workos/routes/radar.js.map +1 -0
- package/dist/emulate/workos/routes/sessions.d.ts +2 -0
- package/dist/emulate/workos/routes/sessions.js +51 -0
- package/dist/emulate/workos/routes/sessions.js.map +1 -0
- package/dist/emulate/workos/routes/sso.d.ts +2 -0
- package/dist/emulate/workos/routes/sso.js +160 -0
- package/dist/emulate/workos/routes/sso.js.map +1 -0
- package/dist/emulate/workos/routes/user-features.d.ts +2 -0
- package/dist/emulate/workos/routes/user-features.js +50 -0
- package/dist/emulate/workos/routes/user-features.js.map +1 -0
- package/dist/emulate/workos/routes/users.d.ts +2 -0
- package/dist/emulate/workos/routes/users.js +133 -0
- package/dist/emulate/workos/routes/users.js.map +1 -0
- package/dist/emulate/workos/routes/webhook-endpoints.d.ts +2 -0
- package/dist/emulate/workos/routes/webhook-endpoints.js +70 -0
- package/dist/emulate/workos/routes/webhook-endpoints.js.map +1 -0
- package/dist/emulate/workos/routes/widgets.d.ts +2 -0
- package/dist/emulate/workos/routes/widgets.js +27 -0
- package/dist/emulate/workos/routes/widgets.js.map +1 -0
- package/dist/emulate/workos/store.d.ts +48 -0
- package/dist/emulate/workos/store.js +93 -0
- package/dist/emulate/workos/store.js.map +1 -0
- package/dist/emulate/workos/webhook-signer.d.ts +1 -0
- package/dist/emulate/workos/webhook-signer.js +8 -0
- package/dist/emulate/workos/webhook-signer.js.map +1 -0
- package/dist/gen-routes-lib.spec.ts +659 -0
- package/dist/gen-routes-lib.ts +647 -0
- package/dist/gen-routes.ts +96 -0
- package/dist/lib/dev-command.d.ts +26 -0
- package/dist/lib/dev-command.js +122 -0
- package/dist/lib/dev-command.js.map +1 -0
- package/dist/utils/help-json.js +23 -0
- package/dist/utils/help-json.js.map +1 -1
- 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,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,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,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
|