@emulators/okta 0.4.0

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/dist/index.js ADDED
@@ -0,0 +1,1971 @@
1
+ // src/helpers.ts
2
+ import { randomUUID } from "crypto";
3
+ var ORG_AUTH_SERVER_ID = "org";
4
+ var DEFAULT_AUTH_SERVER_ID = "default";
5
+ var DEFAULT_AUDIENCE = "api://default";
6
+ var DEFAULT_EVERYONE_GROUP_NAME = "Everyone";
7
+ var DEFAULT_EVERYONE_GROUP_ID = "00g_everyone";
8
+ function nowIso() {
9
+ return (/* @__PURE__ */ new Date()).toISOString();
10
+ }
11
+ function generateOktaId(prefix) {
12
+ const compact = randomUUID().replace(/-/g, "");
13
+ return `${prefix}${compact.slice(0, 17)}`;
14
+ }
15
+ function normalizeStatus(status, fallback) {
16
+ if (status === "STAGED" || status === "PROVISIONED" || status === "ACTIVE" || status === "SUSPENDED" || status === "DEPROVISIONED") {
17
+ return status;
18
+ }
19
+ return fallback;
20
+ }
21
+ function normalizeAppStatus(status, fallback) {
22
+ if (status === "ACTIVE" || status === "INACTIVE") return status;
23
+ return fallback;
24
+ }
25
+ function normalizeAuthServerStatus(status, fallback) {
26
+ if (status === "ACTIVE" || status === "INACTIVE") return status;
27
+ return fallback;
28
+ }
29
+ function normalizeGroupType(type, fallback) {
30
+ if (type === "OKTA_GROUP" || type === "BUILT_IN") return type;
31
+ return fallback;
32
+ }
33
+ function boolFromQuery(value, fallback) {
34
+ if (value == null) return fallback;
35
+ const lowered = value.toLowerCase();
36
+ if (lowered === "true" || lowered === "1") return true;
37
+ if (lowered === "false" || lowered === "0") return false;
38
+ return fallback;
39
+ }
40
+ function resolveOktaIssuer(baseUrl, authServerId) {
41
+ if (authServerId === ORG_AUTH_SERVER_ID) return baseUrl;
42
+ return `${baseUrl}/oauth2/${authServerId}`;
43
+ }
44
+ function userDisplayName(user) {
45
+ if (user.display_name) return user.display_name;
46
+ const combined = `${user.first_name} ${user.last_name}`.trim();
47
+ return combined || user.login;
48
+ }
49
+ function createDefaultUser() {
50
+ const now = nowIso();
51
+ return {
52
+ okta_id: generateOktaId("00u"),
53
+ status: "ACTIVE",
54
+ activated_at: now,
55
+ status_changed_at: now,
56
+ last_login_at: null,
57
+ password_changed_at: null,
58
+ transitioning_to_status: null,
59
+ login: "testuser@okta.local",
60
+ email: "testuser@okta.local",
61
+ first_name: "Test",
62
+ last_name: "User",
63
+ display_name: "Test User",
64
+ locale: "en-US",
65
+ time_zone: "UTC"
66
+ };
67
+ }
68
+ function createDefaultGroup() {
69
+ return {
70
+ okta_id: DEFAULT_EVERYONE_GROUP_ID,
71
+ type: "BUILT_IN",
72
+ name: DEFAULT_EVERYONE_GROUP_NAME,
73
+ description: "All users in the organization"
74
+ };
75
+ }
76
+ function createDefaultAuthorizationServer() {
77
+ return {
78
+ server_id: DEFAULT_AUTH_SERVER_ID,
79
+ name: "default",
80
+ description: "Default custom authorization server",
81
+ audiences: [DEFAULT_AUDIENCE],
82
+ status: "ACTIVE"
83
+ };
84
+ }
85
+ function createDefaultApp() {
86
+ return {
87
+ okta_id: generateOktaId("0oa"),
88
+ name: "oidc_client",
89
+ label: "Sample OIDC App",
90
+ status: "ACTIVE",
91
+ sign_on_mode: "OPENID_CONNECT",
92
+ settings: {
93
+ oauthClient: {
94
+ redirect_uris: ["http://localhost:3000/callback"]
95
+ }
96
+ },
97
+ credentials: {}
98
+ };
99
+ }
100
+
101
+ // ../core/dist/index.js
102
+ import { Hono } from "hono";
103
+ import { cors } from "hono/cors";
104
+ import { jwtVerify, importPKCS8 } from "jose";
105
+ import { readFileSync } from "fs";
106
+ import { fileURLToPath } from "url";
107
+ import { dirname, join } from "path";
108
+ import { timingSafeEqual } from "crypto";
109
+ function createErrorHandler(documentationUrl) {
110
+ return async (c, next) => {
111
+ if (documentationUrl) {
112
+ c.set("docsUrl", documentationUrl);
113
+ }
114
+ await next();
115
+ };
116
+ }
117
+ var errorHandler = createErrorHandler();
118
+ var isDebug = typeof process !== "undefined" && (process.env.DEBUG === "1" || process.env.DEBUG === "true" || process.env.EMULATE_DEBUG === "1");
119
+ function debug(label, ...args) {
120
+ if (isDebug) {
121
+ console.log(`[${label}]`, ...args);
122
+ }
123
+ }
124
+ var __dirname = dirname(fileURLToPath(import.meta.url));
125
+ var FONTS = {
126
+ "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
127
+ "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
128
+ };
129
+ function parsePagination(c) {
130
+ const page = Math.max(1, parseInt(c.req.query("page") ?? "1", 10) || 1);
131
+ const per_page = Math.min(100, Math.max(1, parseInt(c.req.query("per_page") ?? "30", 10) || 30));
132
+ return { page, per_page };
133
+ }
134
+ function setLinkHeader(c, totalCount, page, perPage) {
135
+ const lastPage = Math.max(1, Math.ceil(totalCount / perPage));
136
+ const baseUrl = new URL(c.req.url);
137
+ const links = [];
138
+ const makeLink = (p, rel) => {
139
+ baseUrl.searchParams.set("page", String(p));
140
+ baseUrl.searchParams.set("per_page", String(perPage));
141
+ return `<${baseUrl.toString()}>; rel="${rel}"`;
142
+ };
143
+ if (page < lastPage) {
144
+ links.push(makeLink(page + 1, "next"));
145
+ links.push(makeLink(lastPage, "last"));
146
+ }
147
+ if (page > 1) {
148
+ links.push(makeLink(1, "first"));
149
+ links.push(makeLink(page - 1, "prev"));
150
+ }
151
+ if (links.length > 0) {
152
+ c.header("Link", links.join(", "));
153
+ }
154
+ }
155
+ function escapeHtml(s) {
156
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
157
+ }
158
+ function escapeAttr(s) {
159
+ return escapeHtml(s).replace(/'/g, "&#39;");
160
+ }
161
+ var CSS = `
162
+ @font-face{
163
+ font-family:'Geist';font-style:normal;font-weight:100 900;font-display:swap;
164
+ src:url('/_emulate/fonts/geist-sans.woff2') format('woff2');
165
+ }
166
+ @font-face{
167
+ font-family:'Geist Pixel';font-style:normal;font-weight:400;font-display:swap;
168
+ src:url('/_emulate/fonts/GeistPixel-Square.woff2') format('woff2');
169
+ }
170
+ *{box-sizing:border-box;margin:0;padding:0}
171
+ body{
172
+ font-family:'Geist',-apple-system,BlinkMacSystemFont,sans-serif;
173
+ background:#000;color:#33ff00;min-height:100vh;
174
+ -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
175
+ }
176
+ .emu-bar{
177
+ border-bottom:1px solid #0a3300;padding:10px 20px;
178
+ display:flex;align-items:center;gap:10px;font-size:.8125rem;color:#1a8c00;
179
+ }
180
+ .emu-bar-title{font-weight:600;color:#33ff00;font-family:'Geist Pixel',monospace;}
181
+ .emu-bar-links{margin-left:auto;display:flex;gap:16px;}
182
+ .emu-bar-links a{
183
+ color:#1a8c00;font-size:.75rem;text-decoration:none;transition:color .15s;
184
+ }
185
+ .emu-bar-links a:hover{color:#33ff00;}
186
+ .emu-bar-links a .full{display:inline;}
187
+ .emu-bar-links a .short{display:none;}
188
+ @media(max-width:600px){
189
+ .emu-bar-links a .full{display:none;}
190
+ .emu-bar-links a .short{display:inline;}
191
+ }
192
+
193
+ .content{
194
+ display:flex;align-items:center;justify-content:center;
195
+ min-height:calc(100vh - 42px);padding:24px 16px;
196
+ }
197
+ .content-inner{width:100%;max-width:420px;}
198
+ .card-title{
199
+ font-family:'Geist Pixel',monospace;
200
+ font-size:1.125rem;font-weight:600;margin-bottom:4px;color:#33ff00;
201
+ }
202
+ .card-subtitle{color:#1a8c00;font-size:.8125rem;margin-bottom:18px;line-height:1.45;}
203
+ .powered-by{
204
+ position:fixed;bottom:0;left:0;right:0;
205
+ text-align:center;padding:12px;font-size:.6875rem;color:#0a3300;
206
+ font-family:'Geist Pixel',monospace;
207
+ }
208
+ .powered-by a{color:#1a8c00;text-decoration:none;transition:color .15s;}
209
+ .powered-by a:hover{color:#33ff00;}
210
+
211
+ .error-title{
212
+ font-family:'Geist Pixel',monospace;
213
+ color:#ff4444;font-size:1.125rem;font-weight:600;margin-bottom:8px;
214
+ }
215
+ .error-msg{color:#1a8c00;font-size:.875rem;line-height:1.5;}
216
+ .error-card{text-align:center;}
217
+
218
+ .user-form{margin-bottom:8px;}
219
+ .user-form:last-of-type{margin-bottom:0;}
220
+ .user-btn{
221
+ width:100%;display:flex;align-items:center;gap:12px;
222
+ padding:10px 12px;border:1px solid #0a3300;border-radius:8px;
223
+ background:#000;color:inherit;cursor:pointer;text-align:left;
224
+ font:inherit;transition:border-color .15s;
225
+ }
226
+ .user-btn:hover{border-color:#33ff00;}
227
+ .avatar{
228
+ width:36px;height:36px;border-radius:50%;
229
+ background:#0a3300;color:#33ff00;font-weight:600;font-size:.875rem;
230
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
231
+ font-family:'Geist Pixel',monospace;
232
+ }
233
+ .user-text{min-width:0;}
234
+ .user-login{font-weight:600;font-size:.875rem;display:block;color:#33ff00;}
235
+ .user-meta{color:#1a8c00;font-size:.75rem;margin-top:1px;}
236
+ .user-email{font-size:.6875rem;color:#116600;word-break:break-all;margin-top:1px;}
237
+
238
+ .settings-layout{
239
+ max-width:920px;margin:0 auto;padding:28px 20px;
240
+ display:flex;gap:28px;
241
+ }
242
+ .settings-sidebar{width:200px;flex-shrink:0;}
243
+ .settings-sidebar a{
244
+ display:block;padding:6px 10px;border-radius:6px;color:#1a8c00;
245
+ text-decoration:none;font-size:.8125rem;transition:color .15s;
246
+ }
247
+ .settings-sidebar a:hover{color:#33ff00;}
248
+ .settings-sidebar a.active{color:#33ff00;font-weight:600;}
249
+ .settings-main{flex:1;min-width:0;}
250
+
251
+ .s-card{
252
+ padding:18px 0;margin-bottom:14px;border-bottom:1px solid #0a3300;
253
+ }
254
+ .s-card:last-child{border-bottom:none;}
255
+ .s-card-header{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
256
+ .s-icon{
257
+ width:42px;height:42px;border-radius:8px;
258
+ background:#0a3300;display:flex;align-items:center;justify-content:center;
259
+ font-size:1.125rem;font-weight:700;color:#116600;flex-shrink:0;
260
+ font-family:'Geist Pixel',monospace;
261
+ }
262
+ .s-title{
263
+ font-family:'Geist Pixel',monospace;
264
+ font-size:1.25rem;font-weight:600;color:#33ff00;
265
+ }
266
+ .s-subtitle{font-size:.75rem;color:#1a8c00;margin-top:2px;}
267
+ .section-heading{
268
+ font-size:.9375rem;font-weight:600;margin-bottom:10px;color:#33ff00;
269
+ display:flex;align-items:center;justify-content:space-between;
270
+ }
271
+ .perm-list{list-style:none;}
272
+ .perm-list li{padding:5px 0;font-size:.8125rem;display:flex;align-items:center;gap:6px;color:#1a8c00;}
273
+ .check{color:#33ff00;}
274
+ .org-row{
275
+ display:flex;align-items:center;gap:8px;padding:7px 0;
276
+ border-bottom:1px solid #0a3300;font-size:.8125rem;
277
+ }
278
+ .org-row:last-child{border-bottom:none;}
279
+ .org-icon{
280
+ width:22px;height:22px;border-radius:4px;background:#0a3300;
281
+ display:flex;align-items:center;justify-content:center;
282
+ font-size:.625rem;font-weight:700;color:#116600;flex-shrink:0;
283
+ font-family:'Geist Pixel',monospace;
284
+ }
285
+ .org-name{font-weight:600;color:#33ff00;}
286
+ .badge{font-size:.6875rem;padding:1px 7px;border-radius:999px;font-weight:500;}
287
+ .badge-granted{background:#0a3300;color:#33ff00;}
288
+ .badge-denied{background:#1a0a0a;color:#ff4444;}
289
+ .badge-requested{background:#0a3300;color:#1a8c00;}
290
+ .btn-revoke{
291
+ display:inline-block;padding:5px 14px;border-radius:6px;
292
+ border:1px solid #0a3300;background:transparent;color:#ff4444;
293
+ font-size:.75rem;font-weight:600;cursor:pointer;transition:border-color .15s;
294
+ }
295
+ .btn-revoke:hover{border-color:#ff4444;}
296
+ .info-text{color:#1a8c00;font-size:.75rem;line-height:1.5;margin-top:10px;}
297
+ .app-link{
298
+ display:flex;align-items:center;gap:12px;padding:12px;
299
+ border:1px solid #0a3300;border-radius:8px;background:#000;
300
+ text-decoration:none;color:inherit;margin-bottom:8px;transition:border-color .15s;
301
+ }
302
+ .app-link:hover{border-color:#33ff00;}
303
+ .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
304
+ .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
305
+ .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
306
+ `;
307
+ var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
308
+ function emuBar(service) {
309
+ const title = service ? `${escapeHtml(service)} Emulator` : "Emulator";
310
+ return `<div class="emu-bar">
311
+ <span class="emu-bar-title">${title}</span>
312
+ <nav class="emu-bar-links">
313
+ <a href="https://github.com/vercel-labs/emulate/issues" target="_blank" rel="noopener"><span class="full">Report Issue</span><span class="short">Report</span></a>
314
+ <a href="https://github.com/vercel-labs/emulate" target="_blank" rel="noopener"><span class="full">Source Code</span><span class="short">Source</span></a>
315
+ <a href="https://emulate.dev" target="_blank" rel="noopener"><span class="full">Learn More</span><span class="short">Learn</span></a>
316
+ </nav>
317
+ </div>`;
318
+ }
319
+ function head(title) {
320
+ return `<!DOCTYPE html>
321
+ <html lang="en">
322
+ <head>
323
+ <meta charset="utf-8"/>
324
+ <meta name="viewport" content="width=device-width,initial-scale=1"/>
325
+ <title>${escapeHtml(title)} | emulate</title>
326
+ <style>${CSS}</style>
327
+ </head>`;
328
+ }
329
+ function renderCardPage(title, subtitle, body, service) {
330
+ return `${head(title)}
331
+ <body>
332
+ ${emuBar(service)}
333
+ <div class="content">
334
+ <div class="content-inner">
335
+ <div class="card-title">${escapeHtml(title)}</div>
336
+ <div class="card-subtitle">${subtitle}</div>
337
+ ${body}
338
+ </div>
339
+ </div>
340
+ ${POWERED_BY}
341
+ </body></html>`;
342
+ }
343
+ function renderErrorPage(title, message, service) {
344
+ return `${head(title)}
345
+ <body>
346
+ ${emuBar(service)}
347
+ <div class="content">
348
+ <div class="content-inner error-card">
349
+ <div class="error-title">${escapeHtml(title)}</div>
350
+ <div class="error-msg">${escapeHtml(message)}</div>
351
+ </div>
352
+ </div>
353
+ ${POWERED_BY}
354
+ </body></html>`;
355
+ }
356
+ function renderUserButton(opts) {
357
+ const hiddens = Object.entries(opts.hiddenFields).map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}"/>`).join("");
358
+ const nameLine = opts.name ? `<div class="user-meta">${escapeHtml(opts.name)}</div>` : "";
359
+ const emailLine = opts.email ? `<div class="user-email">${escapeHtml(opts.email)}</div>` : "";
360
+ return `<form class="user-form" method="post" action="${escapeAttr(opts.formAction)}">
361
+ ${hiddens}
362
+ <button type="submit" class="user-btn">
363
+ <span class="avatar">${escapeHtml(opts.letter)}</span>
364
+ <span class="user-text">
365
+ <span class="user-login">${escapeHtml(opts.login)}</span>
366
+ ${nameLine}${emailLine}
367
+ </span>
368
+ </button>
369
+ </form>`;
370
+ }
371
+ function normalizeUri(uri) {
372
+ try {
373
+ const u = new URL(uri);
374
+ return `${u.origin}${u.pathname.replace(/\/+$/, "")}`;
375
+ } catch {
376
+ return uri.replace(/\/+$/, "").split("?")[0];
377
+ }
378
+ }
379
+ function matchesRedirectUri(incoming, registered) {
380
+ const normalized = normalizeUri(incoming);
381
+ return registered.some((r) => normalizeUri(r) === normalized);
382
+ }
383
+ function constantTimeSecretEqual(a, b) {
384
+ const bufA = Buffer.from(a, "utf-8");
385
+ const bufB = Buffer.from(b, "utf-8");
386
+ if (bufA.length !== bufB.length) return false;
387
+ return timingSafeEqual(bufA, bufB);
388
+ }
389
+ function bodyStr(v) {
390
+ if (typeof v === "string") return v;
391
+ if (Array.isArray(v) && typeof v[0] === "string") return v[0];
392
+ return "";
393
+ }
394
+
395
+ // src/route-helpers.ts
396
+ function createErrorBody(status, errorCode, errorSummary, errorCauses = []) {
397
+ return {
398
+ errorCode,
399
+ errorSummary,
400
+ errorLink: errorCode,
401
+ errorId: `${errorCode}-${Date.now()}`,
402
+ errorCauses,
403
+ status
404
+ };
405
+ }
406
+ function oktaError(c, status, errorCode, errorSummary, errorCauses = []) {
407
+ const body = createErrorBody(status, errorCode, errorSummary, errorCauses);
408
+ return c.json(body, status);
409
+ }
410
+ async function readJsonObject(c) {
411
+ try {
412
+ const body = await c.req.json();
413
+ if (body && typeof body === "object") {
414
+ return body;
415
+ }
416
+ return {};
417
+ } catch {
418
+ return {};
419
+ }
420
+ }
421
+ function requireManagementAuth(c, tokenMap) {
422
+ const existing = c.get("authUser");
423
+ if (existing) return existing;
424
+ const authHeader = c.req.header("Authorization") ?? "";
425
+ if (authHeader.toLowerCase().startsWith("ssws ")) {
426
+ const token = authHeader.slice(5).trim();
427
+ const mapped = tokenMap?.get(token);
428
+ if (mapped) {
429
+ c.set("authUser", mapped);
430
+ c.set("authToken", token);
431
+ c.set("authScopes", mapped.scopes);
432
+ return mapped;
433
+ }
434
+ }
435
+ return oktaError(c, 401, "E0000004", "Authentication failed");
436
+ }
437
+ function findUserByRef(os, userRef) {
438
+ const decoded = decodeURIComponent(userRef);
439
+ return os.users.findOneBy("okta_id", decoded) ?? os.users.findOneBy("login", decoded) ?? os.users.findOneBy("email", decoded);
440
+ }
441
+ function findGroupByRef(os, groupRef) {
442
+ const decoded = decodeURIComponent(groupRef);
443
+ return os.groups.findOneBy("okta_id", decoded);
444
+ }
445
+ function findAppByRef(os, appRef) {
446
+ const decoded = decodeURIComponent(appRef);
447
+ return os.apps.findOneBy("okta_id", decoded);
448
+ }
449
+ function findAuthorizationServerByRef(os, serverRef) {
450
+ const decoded = decodeURIComponent(serverRef);
451
+ return os.authorizationServers.findOneBy("server_id", decoded);
452
+ }
453
+ function userResponse(baseUrl, user) {
454
+ return {
455
+ id: user.okta_id,
456
+ status: user.status,
457
+ created: user.created_at,
458
+ activated: user.activated_at,
459
+ statusChanged: user.status_changed_at,
460
+ lastLogin: user.last_login_at,
461
+ lastUpdated: user.updated_at,
462
+ passwordChanged: user.password_changed_at,
463
+ profile: {
464
+ login: user.login,
465
+ email: user.email,
466
+ firstName: user.first_name,
467
+ lastName: user.last_name,
468
+ displayName: userDisplayName(user),
469
+ locale: user.locale,
470
+ timeZone: user.time_zone
471
+ },
472
+ _links: {
473
+ self: {
474
+ href: `${baseUrl}/api/v1/users/${encodeURIComponent(user.okta_id)}`
475
+ }
476
+ }
477
+ };
478
+ }
479
+ function groupResponse(baseUrl, group) {
480
+ return {
481
+ id: group.okta_id,
482
+ created: group.created_at,
483
+ lastUpdated: group.updated_at,
484
+ lastMembershipUpdated: group.updated_at,
485
+ objectClass: ["okta:user_group"],
486
+ type: group.type,
487
+ profile: {
488
+ name: group.name,
489
+ description: group.description
490
+ },
491
+ _links: {
492
+ self: {
493
+ href: `${baseUrl}/api/v1/groups/${encodeURIComponent(group.okta_id)}`
494
+ }
495
+ }
496
+ };
497
+ }
498
+ function appResponse(baseUrl, app) {
499
+ return {
500
+ id: app.okta_id,
501
+ name: app.name,
502
+ label: app.label,
503
+ status: app.status,
504
+ created: app.created_at,
505
+ lastUpdated: app.updated_at,
506
+ signOnMode: app.sign_on_mode,
507
+ credentials: app.credentials,
508
+ settings: app.settings,
509
+ _links: {
510
+ self: {
511
+ href: `${baseUrl}/api/v1/apps/${encodeURIComponent(app.okta_id)}`
512
+ }
513
+ }
514
+ };
515
+ }
516
+ function authorizationServerResponse(baseUrl, server) {
517
+ return {
518
+ id: server.server_id,
519
+ name: server.name,
520
+ description: server.description,
521
+ audiences: server.audiences,
522
+ issuer: resolveOktaIssuer(baseUrl, server.server_id),
523
+ status: server.status,
524
+ created: server.created_at,
525
+ lastUpdated: server.updated_at,
526
+ _links: {
527
+ self: {
528
+ href: `${baseUrl}/api/v1/authorizationServers/${encodeURIComponent(server.server_id)}`
529
+ }
530
+ }
531
+ };
532
+ }
533
+
534
+ // src/store.ts
535
+ function getOktaStore(store) {
536
+ return {
537
+ users: store.collection("okta.users", ["okta_id", "login", "email"]),
538
+ groups: store.collection("okta.groups", ["okta_id", "name"]),
539
+ apps: store.collection("okta.apps", ["okta_id", "name"]),
540
+ oauthClients: store.collection("okta.oauth_clients", ["client_id", "auth_server_id"]),
541
+ authorizationServers: store.collection("okta.auth_servers", ["server_id"]),
542
+ groupMemberships: store.collection("okta.group_memberships", ["group_okta_id", "user_okta_id"]),
543
+ appAssignments: store.collection("okta.app_assignments", ["app_okta_id", "user_okta_id"])
544
+ };
545
+ }
546
+
547
+ // src/routes/apps.ts
548
+ function appRoutes({ app, store, baseUrl, tokenMap }) {
549
+ const oktaStore = getOktaStore(store);
550
+ app.get("/api/v1/apps", (c) => {
551
+ const auth = requireManagementAuth(c, tokenMap);
552
+ if (auth instanceof Response) return auth;
553
+ const q = (c.req.query("q") ?? "").toLowerCase();
554
+ let apps = oktaStore.apps.all();
555
+ if (q) {
556
+ apps = apps.filter(
557
+ (entry) => `${entry.name} ${entry.label}`.toLowerCase().includes(q)
558
+ );
559
+ }
560
+ const { page, per_page } = parsePagination(c);
561
+ const total = apps.length;
562
+ const start = (page - 1) * per_page;
563
+ const paged = apps.slice(start, start + per_page);
564
+ setLinkHeader(c, total, page, per_page);
565
+ c.header("X-Total-Count", String(total));
566
+ return c.json(paged.map((entry) => appResponse(baseUrl, entry)));
567
+ });
568
+ app.post("/api/v1/apps", async (c) => {
569
+ const auth = requireManagementAuth(c, tokenMap);
570
+ if (auth instanceof Response) return auth;
571
+ const body = await readJsonObject(c);
572
+ const name = typeof body.name === "string" ? body.name : "oidc_client";
573
+ const label = typeof body.label === "string" ? body.label : "Okta App";
574
+ const signOnMode = typeof body.signOnMode === "string" ? body.signOnMode : "OPENID_CONNECT";
575
+ const settings = body.settings && typeof body.settings === "object" ? body.settings : {};
576
+ const credentials = body.credentials && typeof body.credentials === "object" ? body.credentials : {};
577
+ const created = oktaStore.apps.insert({
578
+ okta_id: generateOktaId("0oa"),
579
+ name,
580
+ label,
581
+ status: normalizeAppStatus(typeof body.status === "string" ? body.status : void 0, "ACTIVE"),
582
+ sign_on_mode: signOnMode,
583
+ settings,
584
+ credentials
585
+ });
586
+ return c.json(appResponse(baseUrl, created), 201);
587
+ });
588
+ app.get("/api/v1/apps/:appId/users", (c) => {
589
+ const auth = requireManagementAuth(c, tokenMap);
590
+ if (auth instanceof Response) return auth;
591
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
592
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
593
+ const assignments = oktaStore.appAssignments.findBy("app_okta_id", appEntity.okta_id);
594
+ const users = assignments.map((assignment) => oktaStore.users.findOneBy("okta_id", assignment.user_okta_id)).filter((user) => Boolean(user));
595
+ return c.json(
596
+ users.map((user) => ({
597
+ id: user.okta_id,
598
+ scope: "USER",
599
+ credentials: { userName: user.login },
600
+ profile: userResponse(baseUrl, user).profile
601
+ }))
602
+ );
603
+ });
604
+ app.put("/api/v1/apps/:appId/users/:userId", (c) => {
605
+ const auth = requireManagementAuth(c, tokenMap);
606
+ if (auth instanceof Response) return auth;
607
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
608
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
609
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
610
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
611
+ const existing = oktaStore.appAssignments.findBy("app_okta_id", appEntity.okta_id).find((assignment) => assignment.user_okta_id === user.okta_id);
612
+ if (!existing) {
613
+ oktaStore.appAssignments.insert({
614
+ app_okta_id: appEntity.okta_id,
615
+ user_okta_id: user.okta_id
616
+ });
617
+ }
618
+ return new Response(null, { status: 204 });
619
+ });
620
+ app.delete("/api/v1/apps/:appId/users/:userId", (c) => {
621
+ const auth = requireManagementAuth(c, tokenMap);
622
+ if (auth instanceof Response) return auth;
623
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
624
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
625
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
626
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
627
+ const existing = oktaStore.appAssignments.findBy("app_okta_id", appEntity.okta_id).find((assignment) => assignment.user_okta_id === user.okta_id);
628
+ if (existing) oktaStore.appAssignments.delete(existing.id);
629
+ return new Response(null, { status: 204 });
630
+ });
631
+ app.post("/api/v1/apps/:appId/lifecycle/activate", (c) => {
632
+ const auth = requireManagementAuth(c, tokenMap);
633
+ if (auth instanceof Response) return auth;
634
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
635
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
636
+ const updated = oktaStore.apps.update(appEntity.id, { status: "ACTIVE" });
637
+ return c.json(appResponse(baseUrl, updated ?? appEntity));
638
+ });
639
+ app.post("/api/v1/apps/:appId/lifecycle/deactivate", (c) => {
640
+ const auth = requireManagementAuth(c, tokenMap);
641
+ if (auth instanceof Response) return auth;
642
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
643
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
644
+ const updated = oktaStore.apps.update(appEntity.id, { status: "INACTIVE" });
645
+ return c.json(appResponse(baseUrl, updated ?? appEntity));
646
+ });
647
+ app.get("/api/v1/apps/:appId", (c) => {
648
+ const auth = requireManagementAuth(c, tokenMap);
649
+ if (auth instanceof Response) return auth;
650
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
651
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
652
+ return c.json(appResponse(baseUrl, appEntity));
653
+ });
654
+ app.put("/api/v1/apps/:appId", async (c) => {
655
+ const auth = requireManagementAuth(c, tokenMap);
656
+ if (auth instanceof Response) return auth;
657
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
658
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
659
+ const body = await readJsonObject(c);
660
+ const updated = oktaStore.apps.update(appEntity.id, {
661
+ name: typeof body.name === "string" ? body.name : appEntity.name,
662
+ label: typeof body.label === "string" ? body.label : appEntity.label,
663
+ status: normalizeAppStatus(typeof body.status === "string" ? body.status : void 0, appEntity.status),
664
+ sign_on_mode: typeof body.signOnMode === "string" ? body.signOnMode : appEntity.sign_on_mode,
665
+ settings: body.settings && typeof body.settings === "object" ? body.settings : appEntity.settings,
666
+ credentials: body.credentials && typeof body.credentials === "object" ? body.credentials : appEntity.credentials
667
+ });
668
+ return c.json(appResponse(baseUrl, updated ?? appEntity));
669
+ });
670
+ app.delete("/api/v1/apps/:appId", (c) => {
671
+ const auth = requireManagementAuth(c, tokenMap);
672
+ if (auth instanceof Response) return auth;
673
+ const appEntity = findAppByRef(oktaStore, c.req.param("appId"));
674
+ if (!appEntity) return oktaError(c, 404, "E0000007", "Not found: app");
675
+ if (appEntity.status !== "INACTIVE") {
676
+ return oktaError(c, 400, "E0000001", "App must be INACTIVE before deletion");
677
+ }
678
+ for (const assignment of oktaStore.appAssignments.findBy("app_okta_id", appEntity.okta_id)) {
679
+ oktaStore.appAssignments.delete(assignment.id);
680
+ }
681
+ oktaStore.apps.delete(appEntity.id);
682
+ return new Response(null, { status: 204 });
683
+ });
684
+ }
685
+
686
+ // src/routes/auth-servers.ts
687
+ function normalizeServerId(name) {
688
+ const compact = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
689
+ if (compact.length > 0) return compact;
690
+ return generateOktaId("as");
691
+ }
692
+ function authorizationServerRoutes({ app, store, baseUrl, tokenMap }) {
693
+ const oktaStore = getOktaStore(store);
694
+ app.get("/api/v1/authorizationServers", (c) => {
695
+ const auth = requireManagementAuth(c, tokenMap);
696
+ if (auth instanceof Response) return auth;
697
+ const servers = oktaStore.authorizationServers.all();
698
+ const { page, per_page } = parsePagination(c);
699
+ const total = servers.length;
700
+ const start = (page - 1) * per_page;
701
+ const paged = servers.slice(start, start + per_page);
702
+ setLinkHeader(c, total, page, per_page);
703
+ c.header("X-Total-Count", String(total));
704
+ return c.json(paged.map((server) => authorizationServerResponse(baseUrl, server)));
705
+ });
706
+ app.post("/api/v1/authorizationServers", async (c) => {
707
+ const auth = requireManagementAuth(c, tokenMap);
708
+ if (auth instanceof Response) return auth;
709
+ const body = await readJsonObject(c);
710
+ const name = typeof body.name === "string" ? body.name.trim() : "";
711
+ if (!name) return oktaError(c, 400, "E0000001", "name is required");
712
+ const serverId = typeof body.id === "string" ? body.id : normalizeServerId(name);
713
+ if (oktaStore.authorizationServers.findOneBy("server_id", serverId)) {
714
+ return oktaError(c, 400, "E0000001", `Authorization server '${serverId}' already exists`);
715
+ }
716
+ const audiences = Array.isArray(body.audiences) ? body.audiences.filter((entry) => typeof entry === "string") : [DEFAULT_AUDIENCE];
717
+ const created = oktaStore.authorizationServers.insert({
718
+ server_id: serverId,
719
+ name,
720
+ description: typeof body.description === "string" ? body.description : "",
721
+ audiences: audiences.length > 0 ? audiences : [DEFAULT_AUDIENCE],
722
+ status: normalizeAuthServerStatus(typeof body.status === "string" ? body.status : void 0, "ACTIVE")
723
+ });
724
+ return c.json(authorizationServerResponse(baseUrl, created), 201);
725
+ });
726
+ app.post("/api/v1/authorizationServers/:authServerId/lifecycle/activate", (c) => {
727
+ const auth = requireManagementAuth(c, tokenMap);
728
+ if (auth instanceof Response) return auth;
729
+ const server = findAuthorizationServerByRef(oktaStore, c.req.param("authServerId"));
730
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: authorization server");
731
+ const updated = oktaStore.authorizationServers.update(server.id, { status: "ACTIVE" });
732
+ return c.json(authorizationServerResponse(baseUrl, updated ?? server));
733
+ });
734
+ app.post("/api/v1/authorizationServers/:authServerId/lifecycle/deactivate", (c) => {
735
+ const auth = requireManagementAuth(c, tokenMap);
736
+ if (auth instanceof Response) return auth;
737
+ const server = findAuthorizationServerByRef(oktaStore, c.req.param("authServerId"));
738
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: authorization server");
739
+ const updated = oktaStore.authorizationServers.update(server.id, { status: "INACTIVE" });
740
+ return c.json(authorizationServerResponse(baseUrl, updated ?? server));
741
+ });
742
+ app.get("/api/v1/authorizationServers/:authServerId", (c) => {
743
+ const auth = requireManagementAuth(c, tokenMap);
744
+ if (auth instanceof Response) return auth;
745
+ const server = findAuthorizationServerByRef(oktaStore, c.req.param("authServerId"));
746
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: authorization server");
747
+ return c.json(authorizationServerResponse(baseUrl, server));
748
+ });
749
+ app.put("/api/v1/authorizationServers/:authServerId", async (c) => {
750
+ const auth = requireManagementAuth(c, tokenMap);
751
+ if (auth instanceof Response) return auth;
752
+ const server = findAuthorizationServerByRef(oktaStore, c.req.param("authServerId"));
753
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: authorization server");
754
+ const body = await readJsonObject(c);
755
+ const audiences = Array.isArray(body.audiences) ? body.audiences.filter((entry) => typeof entry === "string") : server.audiences;
756
+ const updated = oktaStore.authorizationServers.update(server.id, {
757
+ name: typeof body.name === "string" ? body.name : server.name,
758
+ description: typeof body.description === "string" ? body.description : server.description,
759
+ audiences: audiences.length > 0 ? audiences : server.audiences,
760
+ status: normalizeAuthServerStatus(typeof body.status === "string" ? body.status : void 0, server.status)
761
+ });
762
+ return c.json(authorizationServerResponse(baseUrl, updated ?? server));
763
+ });
764
+ app.delete("/api/v1/authorizationServers/:authServerId", (c) => {
765
+ const auth = requireManagementAuth(c, tokenMap);
766
+ if (auth instanceof Response) return auth;
767
+ const server = findAuthorizationServerByRef(oktaStore, c.req.param("authServerId"));
768
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: authorization server");
769
+ for (const client of oktaStore.oauthClients.findBy("auth_server_id", server.server_id)) {
770
+ oktaStore.oauthClients.delete(client.id);
771
+ }
772
+ oktaStore.authorizationServers.delete(server.id);
773
+ return new Response(null, { status: 204 });
774
+ });
775
+ }
776
+
777
+ // src/routes/groups.ts
778
+ function groupRoutes({ app, store, baseUrl, tokenMap }) {
779
+ const oktaStore = getOktaStore(store);
780
+ app.get("/api/v1/groups", (c) => {
781
+ const auth = requireManagementAuth(c, tokenMap);
782
+ if (auth instanceof Response) return auth;
783
+ const q = (c.req.query("q") ?? "").toLowerCase();
784
+ let groups = oktaStore.groups.all();
785
+ if (q) {
786
+ groups = groups.filter(
787
+ (group) => `${group.name} ${group.description ?? ""}`.toLowerCase().includes(q)
788
+ );
789
+ }
790
+ const { page, per_page } = parsePagination(c);
791
+ const total = groups.length;
792
+ const start = (page - 1) * per_page;
793
+ const paged = groups.slice(start, start + per_page);
794
+ setLinkHeader(c, total, page, per_page);
795
+ c.header("X-Total-Count", String(total));
796
+ return c.json(paged.map((group) => groupResponse(baseUrl, group)));
797
+ });
798
+ app.post("/api/v1/groups", async (c) => {
799
+ const auth = requireManagementAuth(c, tokenMap);
800
+ if (auth instanceof Response) return auth;
801
+ const body = await readJsonObject(c);
802
+ const profile = body.profile && typeof body.profile === "object" ? body.profile : {};
803
+ const name = typeof profile.name === "string" ? profile.name.trim() : "";
804
+ if (!name) {
805
+ return oktaError(c, 400, "E0000001", "profile.name is required");
806
+ }
807
+ if (oktaStore.groups.findOneBy("name", name)) {
808
+ return oktaError(c, 400, "E0000001", "A group with the same name already exists");
809
+ }
810
+ const created = oktaStore.groups.insert({
811
+ okta_id: generateOktaId("00g"),
812
+ type: normalizeGroupType(typeof body.type === "string" ? body.type : void 0, "OKTA_GROUP"),
813
+ name,
814
+ description: typeof profile.description === "string" ? profile.description : null
815
+ });
816
+ return c.json(groupResponse(baseUrl, created), 201);
817
+ });
818
+ app.get("/api/v1/groups/:groupId/users", (c) => {
819
+ const auth = requireManagementAuth(c, tokenMap);
820
+ if (auth instanceof Response) return auth;
821
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
822
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
823
+ const memberships = oktaStore.groupMemberships.findBy("group_okta_id", group.okta_id);
824
+ const users = memberships.map((membership) => oktaStore.users.findOneBy("okta_id", membership.user_okta_id)).filter((user) => Boolean(user));
825
+ return c.json(users.map((user) => userResponse(baseUrl, user)));
826
+ });
827
+ app.put("/api/v1/groups/:groupId/users/:userId", (c) => {
828
+ const auth = requireManagementAuth(c, tokenMap);
829
+ if (auth instanceof Response) return auth;
830
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
831
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
832
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
833
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
834
+ const existing = oktaStore.groupMemberships.findBy("group_okta_id", group.okta_id).find((membership) => membership.user_okta_id === user.okta_id);
835
+ if (!existing) {
836
+ oktaStore.groupMemberships.insert({
837
+ group_okta_id: group.okta_id,
838
+ user_okta_id: user.okta_id
839
+ });
840
+ }
841
+ return new Response(null, { status: 204 });
842
+ });
843
+ app.delete("/api/v1/groups/:groupId/users/:userId", (c) => {
844
+ const auth = requireManagementAuth(c, tokenMap);
845
+ if (auth instanceof Response) return auth;
846
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
847
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
848
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
849
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
850
+ const existing = oktaStore.groupMemberships.findBy("group_okta_id", group.okta_id).find((membership) => membership.user_okta_id === user.okta_id);
851
+ if (existing) {
852
+ oktaStore.groupMemberships.delete(existing.id);
853
+ }
854
+ return new Response(null, { status: 204 });
855
+ });
856
+ app.get("/api/v1/groups/:groupId", (c) => {
857
+ const auth = requireManagementAuth(c, tokenMap);
858
+ if (auth instanceof Response) return auth;
859
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
860
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
861
+ return c.json(groupResponse(baseUrl, group));
862
+ });
863
+ app.put("/api/v1/groups/:groupId", async (c) => {
864
+ const auth = requireManagementAuth(c, tokenMap);
865
+ if (auth instanceof Response) return auth;
866
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
867
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
868
+ const body = await readJsonObject(c);
869
+ const profile = body.profile && typeof body.profile === "object" ? body.profile : {};
870
+ const nextName = typeof profile.name === "string" ? profile.name.trim() : group.name;
871
+ if (nextName !== group.name) {
872
+ const existing = oktaStore.groups.findOneBy("name", nextName);
873
+ if (existing && existing.okta_id !== group.okta_id) {
874
+ return oktaError(c, 400, "E0000001", "A group with the same name already exists");
875
+ }
876
+ }
877
+ const updated = oktaStore.groups.update(group.id, {
878
+ name: nextName,
879
+ description: typeof profile.description === "string" ? profile.description : group.description,
880
+ type: normalizeGroupType(typeof body.type === "string" ? body.type : void 0, group.type)
881
+ });
882
+ return c.json(groupResponse(baseUrl, updated ?? group));
883
+ });
884
+ app.delete("/api/v1/groups/:groupId", (c) => {
885
+ const auth = requireManagementAuth(c, tokenMap);
886
+ if (auth instanceof Response) return auth;
887
+ const group = findGroupByRef(oktaStore, c.req.param("groupId"));
888
+ if (!group) return oktaError(c, 404, "E0000007", "Not found: group");
889
+ for (const membership of oktaStore.groupMemberships.findBy("group_okta_id", group.okta_id)) {
890
+ oktaStore.groupMemberships.delete(membership.id);
891
+ }
892
+ oktaStore.groups.delete(group.id);
893
+ return new Response(null, { status: 204 });
894
+ });
895
+ }
896
+
897
+ // src/routes/oauth.ts
898
+ import { createHash, randomBytes } from "crypto";
899
+ import { SignJWT, exportJWK, generateKeyPair } from "jose";
900
+ var keyPairPromise = generateKeyPair("RS256");
901
+ var KID = "emulate-okta-1";
902
+ var CODE_TTL_MS = 10 * 60 * 1e3;
903
+ function getPendingCodes(store) {
904
+ let map = store.getData("okta.oauth.pendingCodes");
905
+ if (!map) {
906
+ map = /* @__PURE__ */ new Map();
907
+ store.setData("okta.oauth.pendingCodes", map);
908
+ }
909
+ return map;
910
+ }
911
+ function getAccessTokens(store) {
912
+ let map = store.getData("okta.oauth.accessTokens");
913
+ if (!map) {
914
+ map = /* @__PURE__ */ new Map();
915
+ store.setData("okta.oauth.accessTokens", map);
916
+ }
917
+ return map;
918
+ }
919
+ function getRefreshTokens(store) {
920
+ let map = store.getData("okta.oauth.refreshTokens");
921
+ if (!map) {
922
+ map = /* @__PURE__ */ new Map();
923
+ store.setData("okta.oauth.refreshTokens", map);
924
+ }
925
+ return map;
926
+ }
927
+ function isCodeExpired(code) {
928
+ return Date.now() - code.createdAt > CODE_TTL_MS;
929
+ }
930
+ function buildOAuthBasePath(authServerId) {
931
+ if (authServerId === ORG_AUTH_SERVER_ID) return "/oauth2/v1";
932
+ return `/oauth2/${encodeURIComponent(authServerId)}/v1`;
933
+ }
934
+ function getClientsForServer(clients, authServerId) {
935
+ return clients.filter((client) => client.auth_server_id === authServerId);
936
+ }
937
+ function resolveServer(authServerId, baseUrl, store) {
938
+ if (authServerId === ORG_AUTH_SERVER_ID) {
939
+ return {
940
+ authServerId,
941
+ issuer: baseUrl,
942
+ audiences: [DEFAULT_AUDIENCE]
943
+ };
944
+ }
945
+ const server = store.authorizationServers.findOneBy("server_id", authServerId);
946
+ if (!server) return null;
947
+ return {
948
+ authServerId,
949
+ issuer: resolveOktaIssuer(baseUrl, authServerId),
950
+ audiences: server.audiences.length > 0 ? server.audiences : [DEFAULT_AUDIENCE]
951
+ };
952
+ }
953
+ function buildOidcConfiguration(baseUrl, server) {
954
+ const oauthBase = buildOAuthBasePath(server.authServerId);
955
+ const oauthUrlBase = `${baseUrl}${oauthBase}`;
956
+ const tokenEndpointAuthMethods = ["client_secret_post", "client_secret_basic", "none"];
957
+ return {
958
+ issuer: server.issuer,
959
+ authorization_endpoint: `${oauthUrlBase}/authorize`,
960
+ token_endpoint: `${oauthUrlBase}/token`,
961
+ userinfo_endpoint: `${oauthUrlBase}/userinfo`,
962
+ jwks_uri: `${oauthUrlBase}/keys`,
963
+ end_session_endpoint: `${oauthUrlBase}/logout`,
964
+ revocation_endpoint: `${oauthUrlBase}/revoke`,
965
+ introspection_endpoint: `${oauthUrlBase}/introspect`,
966
+ registration_endpoint: `${oauthUrlBase}/clients`,
967
+ response_types_supported: ["code"],
968
+ response_modes_supported: ["query", "fragment", "form_post"],
969
+ grant_types_supported: ["authorization_code", "refresh_token", "client_credentials"],
970
+ subject_types_supported: ["public"],
971
+ id_token_signing_alg_values_supported: ["RS256"],
972
+ scopes_supported: ["openid", "profile", "email", "offline_access", "groups"],
973
+ token_endpoint_auth_methods_supported: tokenEndpointAuthMethods,
974
+ revocation_endpoint_auth_methods_supported: tokenEndpointAuthMethods,
975
+ introspection_endpoint_auth_methods_supported: tokenEndpointAuthMethods,
976
+ request_parameter_supported: false,
977
+ request_uri_parameter_supported: false,
978
+ claims_parameter_supported: false,
979
+ request_object_signing_alg_values_supported: ["RS256"],
980
+ claims_supported: [
981
+ "sub",
982
+ "iss",
983
+ "aud",
984
+ "exp",
985
+ "iat",
986
+ "auth_time",
987
+ "nonce",
988
+ "name",
989
+ "preferred_username",
990
+ "email",
991
+ "email_verified",
992
+ "locale",
993
+ "zoneinfo",
994
+ "groups"
995
+ ],
996
+ code_challenge_methods_supported: ["plain", "S256"]
997
+ };
998
+ }
999
+ async function parseTokenLikeBody(c) {
1000
+ const contentType = c.req.header("Content-Type") ?? "";
1001
+ const raw = await c.req.text();
1002
+ if (contentType.includes("application/json")) {
1003
+ try {
1004
+ const parsed = JSON.parse(raw);
1005
+ const out = {};
1006
+ for (const [key, value] of Object.entries(parsed)) {
1007
+ if (typeof value === "string") out[key] = value;
1008
+ }
1009
+ return out;
1010
+ } catch {
1011
+ return {};
1012
+ }
1013
+ }
1014
+ return Object.fromEntries(new URLSearchParams(raw));
1015
+ }
1016
+ function parseClientCredentials(c, body) {
1017
+ let clientId = body.client_id ?? "";
1018
+ let clientSecret = body.client_secret ?? "";
1019
+ const authHeader = c.req.header("Authorization") ?? "";
1020
+ if (authHeader.startsWith("Basic ")) {
1021
+ const decoded = Buffer.from(authHeader.slice(6), "base64").toString("utf8");
1022
+ const sep = decoded.indexOf(":");
1023
+ if (sep !== -1) {
1024
+ const headerId = decodeURIComponent(decoded.slice(0, sep));
1025
+ const headerSecret = decodeURIComponent(decoded.slice(sep + 1));
1026
+ if (!clientId) clientId = headerId;
1027
+ if (!clientSecret) clientSecret = headerSecret;
1028
+ }
1029
+ }
1030
+ return { clientId, clientSecret };
1031
+ }
1032
+ function validateClient(clients, authServerId, clientId, clientSecret) {
1033
+ const scopedClients = getClientsForServer(clients, authServerId);
1034
+ if (scopedClients.length === 0) {
1035
+ return { client: null, error: null };
1036
+ }
1037
+ const client = scopedClients.find((entry) => entry.client_id === clientId);
1038
+ if (!client) {
1039
+ return {
1040
+ client: null,
1041
+ error: {
1042
+ body: { error: "invalid_client", error_description: "Unknown client." },
1043
+ status: 401
1044
+ }
1045
+ };
1046
+ }
1047
+ if (client.token_endpoint_auth_method === "none") {
1048
+ return { client, error: null };
1049
+ }
1050
+ if (!constantTimeSecretEqual(client.client_secret ?? "", clientSecret)) {
1051
+ return {
1052
+ client: null,
1053
+ error: {
1054
+ body: { error: "invalid_client", error_description: "Invalid client credentials." },
1055
+ status: 401
1056
+ }
1057
+ };
1058
+ }
1059
+ return { client, error: null };
1060
+ }
1061
+ function parseScope(scope) {
1062
+ return scope.split(/\s+/).map((part) => part.trim()).filter(Boolean);
1063
+ }
1064
+ function collectUserGroups(oktaStore, user) {
1065
+ const memberships = oktaStore.groupMemberships.findBy("user_okta_id", user.okta_id);
1066
+ const names = [];
1067
+ for (const membership of memberships) {
1068
+ const group = oktaStore.groups.findOneBy("okta_id", membership.group_okta_id);
1069
+ if (group) names.push(group.name);
1070
+ }
1071
+ return names;
1072
+ }
1073
+ async function createIdToken(oktaStore, user, clientId, nonce, issuer, scope) {
1074
+ const { privateKey } = await keyPairPromise;
1075
+ const now = Math.floor(Date.now() / 1e3);
1076
+ const scopes = parseScope(scope);
1077
+ const claims = {
1078
+ sub: user.okta_id,
1079
+ name: userDisplayName(user),
1080
+ preferred_username: user.login,
1081
+ email: user.email,
1082
+ email_verified: true,
1083
+ locale: user.locale,
1084
+ zoneinfo: user.time_zone,
1085
+ auth_time: now
1086
+ };
1087
+ if (nonce) claims.nonce = nonce;
1088
+ if (scopes.includes("groups")) {
1089
+ claims.groups = collectUserGroups(oktaStore, user);
1090
+ }
1091
+ return new SignJWT(claims).setProtectedHeader({ alg: "RS256", kid: KID, typ: "JWT" }).setIssuer(issuer).setAudience(clientId).setIssuedAt(now).setExpirationTime("1h").sign(privateKey);
1092
+ }
1093
+ function unauthorizedOAuthError() {
1094
+ return new Response(
1095
+ JSON.stringify({ error: "invalid_token", error_description: "The access token is invalid." }),
1096
+ { status: 401, headers: { "Content-Type": "application/json" } }
1097
+ );
1098
+ }
1099
+ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1100
+ const oktaStore = getOktaStore(store);
1101
+ const SERVICE_LABEL = "Okta";
1102
+ app.get("/.well-known/openid-configuration", (c) => {
1103
+ const server = resolveServer(ORG_AUTH_SERVER_ID, baseUrl, oktaStore);
1104
+ if (!server) return oktaError(c, 404, "E0000007", "Not found: org authorization server");
1105
+ return c.json(buildOidcConfiguration(baseUrl, server));
1106
+ });
1107
+ app.get("/oauth2/:authServerId/.well-known/openid-configuration", (c) => {
1108
+ const authServerId = c.req.param("authServerId");
1109
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1110
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1111
+ return c.json(buildOidcConfiguration(baseUrl, server));
1112
+ });
1113
+ app.get("/oauth2/v1/keys", async (c) => {
1114
+ const { publicKey } = await keyPairPromise;
1115
+ const jwk = await exportJWK(publicKey);
1116
+ return c.json({
1117
+ keys: [{ ...jwk, kid: KID, use: "sig", alg: "RS256" }]
1118
+ });
1119
+ });
1120
+ app.get("/oauth2/:authServerId/v1/keys", async (c) => {
1121
+ const authServerId = c.req.param("authServerId");
1122
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1123
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1124
+ const { publicKey } = await keyPairPromise;
1125
+ const jwk = await exportJWK(publicKey);
1126
+ return c.json({
1127
+ keys: [{ ...jwk, kid: KID, use: "sig", alg: "RS256" }]
1128
+ });
1129
+ });
1130
+ const renderAuthorizePage = (c, authServerId) => {
1131
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1132
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1133
+ const clientId = c.req.query("client_id") ?? "";
1134
+ const redirectUri = c.req.query("redirect_uri") ?? "";
1135
+ const scope = c.req.query("scope") ?? "openid profile email";
1136
+ const state = c.req.query("state") ?? "";
1137
+ const nonce = c.req.query("nonce") ?? "";
1138
+ const responseMode = c.req.query("response_mode") ?? "query";
1139
+ const responseType = c.req.query("response_type") ?? "code";
1140
+ const codeChallenge = c.req.query("code_challenge") ?? "";
1141
+ const codeChallengeMethod = c.req.query("code_challenge_method") ?? "";
1142
+ if (responseType !== "code") {
1143
+ return c.html(
1144
+ renderErrorPage("Unsupported response_type", "Only response_type=code is supported.", SERVICE_LABEL),
1145
+ 400
1146
+ );
1147
+ }
1148
+ if (!redirectUri) {
1149
+ return c.html(
1150
+ renderErrorPage("Missing redirect URI", "The redirect_uri parameter is required.", SERVICE_LABEL),
1151
+ 400
1152
+ );
1153
+ }
1154
+ const configuredClients = getClientsForServer(oktaStore.oauthClients.all(), authServerId);
1155
+ let clientName = "";
1156
+ if (configuredClients.length > 0) {
1157
+ const client = configuredClients.find((entry) => entry.client_id === clientId);
1158
+ if (!client) {
1159
+ return c.html(
1160
+ renderErrorPage("Application not found", `The client_id '${clientId}' is not registered.`, SERVICE_LABEL),
1161
+ 400
1162
+ );
1163
+ }
1164
+ if (!matchesRedirectUri(redirectUri, client.redirect_uris)) {
1165
+ return c.html(
1166
+ renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
1167
+ 400
1168
+ );
1169
+ }
1170
+ clientName = client.name;
1171
+ }
1172
+ const users = oktaStore.users.all();
1173
+ const callbackPath = `${buildOAuthBasePath(authServerId)}/authorize/callback`;
1174
+ const buttons = users.map((user) => renderUserButton({
1175
+ letter: (user.login[0] ?? "?").toUpperCase(),
1176
+ login: user.login,
1177
+ name: userDisplayName(user),
1178
+ email: user.email,
1179
+ formAction: callbackPath,
1180
+ hiddenFields: {
1181
+ user_ref: user.okta_id,
1182
+ redirect_uri: redirectUri,
1183
+ scope,
1184
+ state,
1185
+ nonce,
1186
+ client_id: clientId,
1187
+ response_mode: responseMode,
1188
+ code_challenge: codeChallenge,
1189
+ code_challenge_method: codeChallengeMethod,
1190
+ auth_server_id: authServerId
1191
+ }
1192
+ })).join("\n");
1193
+ const subtitle = clientName ? `Sign in to <strong>${escapeHtml(clientName)}</strong> with your Okta account.` : "Choose a seeded user to continue.";
1194
+ return c.html(
1195
+ renderCardPage(
1196
+ "Sign in with Okta",
1197
+ subtitle,
1198
+ users.length > 0 ? buttons : '<p class="empty">No users in the emulator store.</p>',
1199
+ SERVICE_LABEL
1200
+ )
1201
+ );
1202
+ };
1203
+ app.get("/oauth2/v1/authorize", (c) => renderAuthorizePage(c, ORG_AUTH_SERVER_ID));
1204
+ app.get("/oauth2/:authServerId/v1/authorize", (c) => renderAuthorizePage(c, c.req.param("authServerId")));
1205
+ const handleAuthorizeCallback = async (c, authServerId) => {
1206
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1207
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1208
+ const body = await c.req.parseBody();
1209
+ const userRef = bodyStr(body.user_ref);
1210
+ const redirectUri = bodyStr(body.redirect_uri);
1211
+ const scope = bodyStr(body.scope) || "openid profile email";
1212
+ const state = bodyStr(body.state);
1213
+ const nonce = bodyStr(body.nonce);
1214
+ const clientId = bodyStr(body.client_id);
1215
+ const responseMode = bodyStr(body.response_mode) || "query";
1216
+ const codeChallenge = bodyStr(body.code_challenge);
1217
+ const codeChallengeMethod = bodyStr(body.code_challenge_method);
1218
+ if (!redirectUri) {
1219
+ return c.html(
1220
+ renderErrorPage("Missing redirect URI", "The redirect_uri parameter is required.", SERVICE_LABEL),
1221
+ 400
1222
+ );
1223
+ }
1224
+ const user = findUserByRef(oktaStore, userRef);
1225
+ if (!user) {
1226
+ return c.html(
1227
+ renderErrorPage("Unknown user", "The selected user is not available.", SERVICE_LABEL),
1228
+ 400
1229
+ );
1230
+ }
1231
+ const configuredClients = getClientsForServer(oktaStore.oauthClients.all(), authServerId);
1232
+ if (configuredClients.length > 0) {
1233
+ const client = configuredClients.find((entry) => entry.client_id === clientId);
1234
+ if (!client) {
1235
+ return c.html(
1236
+ renderErrorPage("Application not found", `The client_id '${clientId}' is not registered.`, SERVICE_LABEL),
1237
+ 400
1238
+ );
1239
+ }
1240
+ if (!matchesRedirectUri(redirectUri, client.redirect_uris)) {
1241
+ return c.html(
1242
+ renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
1243
+ 400
1244
+ );
1245
+ }
1246
+ }
1247
+ const code = randomBytes(20).toString("hex");
1248
+ getPendingCodes(store).set(code, {
1249
+ userRef: user.okta_id,
1250
+ scope,
1251
+ redirectUri,
1252
+ clientId,
1253
+ nonce: nonce || null,
1254
+ codeChallenge: codeChallenge || null,
1255
+ codeChallengeMethod: codeChallengeMethod || null,
1256
+ authServerId,
1257
+ createdAt: Date.now()
1258
+ });
1259
+ debug("okta.oauth", `[callback] code=${code.slice(0, 8)}... user=${user.login} server=${authServerId}`);
1260
+ if (responseMode === "form_post") {
1261
+ const html = `<!DOCTYPE html>
1262
+ <html>
1263
+ <head><title>Submit</title></head>
1264
+ <body onload="document.forms[0].submit()">
1265
+ <form method="POST" action="${escapeAttr(redirectUri)}">
1266
+ <input type="hidden" name="code" value="${escapeAttr(code)}" />
1267
+ <input type="hidden" name="state" value="${escapeAttr(state)}" />
1268
+ </form>
1269
+ </body>
1270
+ </html>`;
1271
+ return c.html(html);
1272
+ }
1273
+ const url = new URL(redirectUri);
1274
+ url.searchParams.set("code", code);
1275
+ if (state) url.searchParams.set("state", state);
1276
+ return c.redirect(url.toString(), 302);
1277
+ };
1278
+ app.post("/oauth2/v1/authorize/callback", (c) => handleAuthorizeCallback(c, ORG_AUTH_SERVER_ID));
1279
+ app.post("/oauth2/:authServerId/v1/authorize/callback", (c) => handleAuthorizeCallback(c, c.req.param("authServerId")));
1280
+ const handleToken = async (c, authServerId) => {
1281
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1282
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1283
+ const body = await parseTokenLikeBody(c);
1284
+ const grantType = body.grant_type ?? "";
1285
+ const code = body.code ?? "";
1286
+ const redirectUri = body.redirect_uri ?? "";
1287
+ const codeVerifier = body.code_verifier;
1288
+ const refreshToken = body.refresh_token ?? "";
1289
+ const requestedScope = body.scope ?? "";
1290
+ const creds = parseClientCredentials(c, body);
1291
+ const validation = validateClient(oktaStore.oauthClients.all(), authServerId, creds.clientId, creds.clientSecret);
1292
+ if (validation.error) {
1293
+ return c.json(validation.error.body, validation.error.status);
1294
+ }
1295
+ const validatedClient = validation.client;
1296
+ if (grantType === "authorization_code") {
1297
+ const pending = getPendingCodes(store).get(code);
1298
+ if (!pending || isCodeExpired(pending)) {
1299
+ if (pending) getPendingCodes(store).delete(code);
1300
+ return c.json({ error: "invalid_grant", error_description: "Authorization code is invalid or expired." }, 400);
1301
+ }
1302
+ if (pending.authServerId !== authServerId) {
1303
+ return c.json({ error: "invalid_grant", error_description: "Authorization server mismatch." }, 400);
1304
+ }
1305
+ if (redirectUri && redirectUri !== pending.redirectUri) {
1306
+ return c.json({ error: "invalid_grant", error_description: "redirect_uri does not match." }, 400);
1307
+ }
1308
+ if (validatedClient && validatedClient.client_id !== pending.clientId) {
1309
+ return c.json({ error: "invalid_grant", error_description: "Authorization code was not issued to this client." }, 400);
1310
+ }
1311
+ if (pending.codeChallenge !== null) {
1312
+ if (!codeVerifier) {
1313
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
1314
+ }
1315
+ const method = (pending.codeChallengeMethod ?? "plain").toLowerCase();
1316
+ if (method === "s256") {
1317
+ const expected = createHash("sha256").update(codeVerifier).digest("base64url");
1318
+ if (expected !== pending.codeChallenge) {
1319
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
1320
+ }
1321
+ } else if (method === "plain") {
1322
+ if (codeVerifier !== pending.codeChallenge) {
1323
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
1324
+ }
1325
+ } else {
1326
+ return c.json({ error: "invalid_grant", error_description: "PKCE verification failed." }, 400);
1327
+ }
1328
+ }
1329
+ const user = findUserByRef(oktaStore, pending.userRef);
1330
+ if (!user) return c.json({ error: "invalid_grant", error_description: "Unknown user." }, 400);
1331
+ getPendingCodes(store).delete(code);
1332
+ const now = Math.floor(Date.now() / 1e3);
1333
+ const audienceClient = pending.clientId || creds.clientId || "okta-client";
1334
+ const scope = pending.scope || "openid profile email";
1335
+ const accessToken = `okta_${randomBytes(20).toString("base64url")}`;
1336
+ const newRefreshToken = `r_okta_${randomBytes(20).toString("base64url")}`;
1337
+ getAccessTokens(store).set(accessToken, {
1338
+ authServerId,
1339
+ clientId: audienceClient,
1340
+ scope,
1341
+ issuedAt: now,
1342
+ expiresAt: now + 3600,
1343
+ userOktaId: user.okta_id,
1344
+ username: user.login
1345
+ });
1346
+ getRefreshTokens(store).set(newRefreshToken, {
1347
+ authServerId,
1348
+ clientId: audienceClient,
1349
+ scope,
1350
+ userOktaId: user.okta_id,
1351
+ username: user.login,
1352
+ nonce: pending.nonce
1353
+ });
1354
+ tokenMap?.set(accessToken, {
1355
+ login: user.login,
1356
+ id: user.id,
1357
+ scopes: parseScope(scope)
1358
+ });
1359
+ const idToken = await createIdToken(
1360
+ oktaStore,
1361
+ user,
1362
+ audienceClient,
1363
+ pending.nonce,
1364
+ server.issuer,
1365
+ scope
1366
+ );
1367
+ return c.json({
1368
+ token_type: "Bearer",
1369
+ expires_in: 3600,
1370
+ access_token: accessToken,
1371
+ refresh_token: newRefreshToken,
1372
+ id_token: idToken,
1373
+ scope
1374
+ });
1375
+ }
1376
+ if (grantType === "refresh_token") {
1377
+ const existing = getRefreshTokens(store).get(refreshToken);
1378
+ if (!existing) {
1379
+ return c.json({ error: "invalid_grant", error_description: "Invalid refresh token." }, 400);
1380
+ }
1381
+ if (existing.authServerId !== authServerId) {
1382
+ return c.json({ error: "invalid_grant", error_description: "Authorization server mismatch." }, 400);
1383
+ }
1384
+ if (validatedClient && validatedClient.client_id !== existing.clientId) {
1385
+ return c.json({ error: "invalid_grant", error_description: "Refresh token was not issued to this client." }, 400);
1386
+ }
1387
+ const user = oktaStore.users.findOneBy("okta_id", existing.userOktaId);
1388
+ if (!user) return c.json({ error: "invalid_grant", error_description: "Unknown user." }, 400);
1389
+ getRefreshTokens(store).delete(refreshToken);
1390
+ const now = Math.floor(Date.now() / 1e3);
1391
+ const nextAccessToken = `okta_${randomBytes(20).toString("base64url")}`;
1392
+ const nextRefreshToken = `r_okta_${randomBytes(20).toString("base64url")}`;
1393
+ const scope = requestedScope || existing.scope;
1394
+ getAccessTokens(store).set(nextAccessToken, {
1395
+ authServerId,
1396
+ clientId: existing.clientId,
1397
+ scope,
1398
+ issuedAt: now,
1399
+ expiresAt: now + 3600,
1400
+ userOktaId: user.okta_id,
1401
+ username: user.login
1402
+ });
1403
+ getRefreshTokens(store).set(nextRefreshToken, {
1404
+ ...existing,
1405
+ scope
1406
+ });
1407
+ tokenMap?.set(nextAccessToken, {
1408
+ login: user.login,
1409
+ id: user.id,
1410
+ scopes: parseScope(scope)
1411
+ });
1412
+ const response = {
1413
+ token_type: "Bearer",
1414
+ expires_in: 3600,
1415
+ access_token: nextAccessToken,
1416
+ refresh_token: nextRefreshToken,
1417
+ scope
1418
+ };
1419
+ if (parseScope(scope).includes("openid")) {
1420
+ response.id_token = await createIdToken(
1421
+ oktaStore,
1422
+ user,
1423
+ existing.clientId,
1424
+ existing.nonce,
1425
+ server.issuer,
1426
+ scope
1427
+ );
1428
+ }
1429
+ return c.json(response);
1430
+ }
1431
+ if (grantType === "client_credentials") {
1432
+ if (oktaStore.oauthClients.all().length > 0 && !validatedClient) {
1433
+ return c.json({ error: "invalid_client", error_description: "Unknown client." }, 401);
1434
+ }
1435
+ const scope = requestedScope || ".default";
1436
+ const now = Math.floor(Date.now() / 1e3);
1437
+ const accessToken = `okta_${randomBytes(20).toString("base64url")}`;
1438
+ const clientId = validatedClient?.client_id ?? creds.clientId;
1439
+ if (!clientId) {
1440
+ return c.json({ error: "invalid_client", error_description: "client_id is required." }, 401);
1441
+ }
1442
+ getAccessTokens(store).set(accessToken, {
1443
+ authServerId,
1444
+ clientId,
1445
+ scope,
1446
+ issuedAt: now,
1447
+ expiresAt: now + 3600,
1448
+ userOktaId: null,
1449
+ username: null
1450
+ });
1451
+ tokenMap?.set(accessToken, {
1452
+ login: clientId,
1453
+ id: 0,
1454
+ scopes: parseScope(scope)
1455
+ });
1456
+ return c.json({
1457
+ token_type: "Bearer",
1458
+ expires_in: 3600,
1459
+ access_token: accessToken,
1460
+ scope
1461
+ });
1462
+ }
1463
+ return c.json({ error: "unsupported_grant_type" }, 400);
1464
+ };
1465
+ app.post("/oauth2/v1/token", (c) => handleToken(c, ORG_AUTH_SERVER_ID));
1466
+ app.post("/oauth2/:authServerId/v1/token", (c) => handleToken(c, c.req.param("authServerId")));
1467
+ const handleUserInfo = (c, authServerId) => {
1468
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1469
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1470
+ const token = c.get("authToken") ?? "";
1471
+ const access = getAccessTokens(store).get(token);
1472
+ if (!access || access.authServerId !== authServerId || !access.userOktaId) {
1473
+ return unauthorizedOAuthError();
1474
+ }
1475
+ const user = oktaStore.users.findOneBy("okta_id", access.userOktaId);
1476
+ if (!user) return unauthorizedOAuthError();
1477
+ const claims = {
1478
+ sub: user.okta_id,
1479
+ name: userDisplayName(user),
1480
+ preferred_username: user.login,
1481
+ email: user.email,
1482
+ email_verified: true,
1483
+ locale: user.locale,
1484
+ zoneinfo: user.time_zone
1485
+ };
1486
+ if (parseScope(access.scope).includes("groups")) {
1487
+ claims.groups = collectUserGroups(oktaStore, user);
1488
+ }
1489
+ return c.json(claims);
1490
+ };
1491
+ app.get("/oauth2/v1/userinfo", (c) => handleUserInfo(c, ORG_AUTH_SERVER_ID));
1492
+ app.get("/oauth2/:authServerId/v1/userinfo", (c) => handleUserInfo(c, c.req.param("authServerId")));
1493
+ const handleRevoke = async (c, authServerId) => {
1494
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1495
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1496
+ const body = await parseTokenLikeBody(c);
1497
+ const token = body.token ?? "";
1498
+ getAccessTokens(store).delete(token);
1499
+ getRefreshTokens(store).delete(token);
1500
+ tokenMap?.delete(token);
1501
+ return c.body("", 200);
1502
+ };
1503
+ app.post("/oauth2/v1/revoke", (c) => handleRevoke(c, ORG_AUTH_SERVER_ID));
1504
+ app.post("/oauth2/:authServerId/v1/revoke", (c) => handleRevoke(c, c.req.param("authServerId")));
1505
+ const handleIntrospect = async (c, authServerId) => {
1506
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1507
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1508
+ const body = await parseTokenLikeBody(c);
1509
+ const token = body.token ?? "";
1510
+ const creds = parseClientCredentials(c, body);
1511
+ const validation = validateClient(oktaStore.oauthClients.all(), authServerId, creds.clientId, creds.clientSecret);
1512
+ if (validation.error) {
1513
+ return c.json(validation.error.body, validation.error.status);
1514
+ }
1515
+ const now = Math.floor(Date.now() / 1e3);
1516
+ const access = getAccessTokens(store).get(token);
1517
+ if (access && access.authServerId === authServerId && access.expiresAt > now) {
1518
+ return c.json({
1519
+ active: true,
1520
+ token_type: "Bearer",
1521
+ scope: access.scope,
1522
+ client_id: access.clientId,
1523
+ username: access.username,
1524
+ sub: access.userOktaId,
1525
+ aud: server.audiences,
1526
+ iss: server.issuer,
1527
+ exp: access.expiresAt,
1528
+ iat: access.issuedAt
1529
+ });
1530
+ }
1531
+ const refresh = getRefreshTokens(store).get(token);
1532
+ if (refresh && refresh.authServerId === authServerId) {
1533
+ return c.json({
1534
+ active: true,
1535
+ token_type: "refresh_token",
1536
+ scope: refresh.scope,
1537
+ client_id: refresh.clientId,
1538
+ username: refresh.username,
1539
+ sub: refresh.userOktaId,
1540
+ aud: server.audiences,
1541
+ iss: server.issuer
1542
+ });
1543
+ }
1544
+ return c.json({ active: false });
1545
+ };
1546
+ app.post("/oauth2/v1/introspect", (c) => handleIntrospect(c, ORG_AUTH_SERVER_ID));
1547
+ app.post("/oauth2/:authServerId/v1/introspect", (c) => handleIntrospect(c, c.req.param("authServerId")));
1548
+ const handleLogout = (c, authServerId) => {
1549
+ const server = resolveServer(authServerId, baseUrl, oktaStore);
1550
+ if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
1551
+ const postLogoutRedirectUri = c.req.query("post_logout_redirect_uri");
1552
+ if (!postLogoutRedirectUri) return c.text("Logged out");
1553
+ const scopedClients = getClientsForServer(oktaStore.oauthClients.all(), authServerId);
1554
+ if (scopedClients.length > 0) {
1555
+ const isAllowed = scopedClients.some(
1556
+ (client) => matchesRedirectUri(postLogoutRedirectUri, client.redirect_uris)
1557
+ );
1558
+ if (!isAllowed) return c.text("Invalid post_logout_redirect_uri", 400);
1559
+ }
1560
+ return c.redirect(postLogoutRedirectUri, 302);
1561
+ };
1562
+ app.get("/oauth2/v1/logout", (c) => handleLogout(c, ORG_AUTH_SERVER_ID));
1563
+ app.get("/oauth2/:authServerId/v1/logout", (c) => handleLogout(c, c.req.param("authServerId")));
1564
+ }
1565
+
1566
+ // src/routes/users.ts
1567
+ function updateUserProfile(user, profile) {
1568
+ const nextFirstName = typeof profile.firstName === "string" ? profile.firstName : user.first_name;
1569
+ const nextLastName = typeof profile.lastName === "string" ? profile.lastName : user.last_name;
1570
+ const nextDisplayName = typeof profile.displayName === "string" ? profile.displayName : typeof profile.nickName === "string" ? profile.nickName : user.display_name;
1571
+ return {
1572
+ login: typeof profile.login === "string" ? profile.login : user.login,
1573
+ email: typeof profile.email === "string" ? profile.email : user.email,
1574
+ first_name: nextFirstName,
1575
+ last_name: nextLastName,
1576
+ display_name: nextDisplayName || `${nextFirstName} ${nextLastName}`.trim(),
1577
+ locale: typeof profile.locale === "string" ? profile.locale : user.locale,
1578
+ time_zone: typeof profile.timeZone === "string" ? profile.timeZone : user.time_zone
1579
+ };
1580
+ }
1581
+ function setLifecycleStatus(user, target) {
1582
+ const now = nowIso();
1583
+ const activatedAt = target === "ACTIVE" ? user.activated_at ?? now : user.activated_at;
1584
+ return {
1585
+ status: target,
1586
+ transitioning_to_status: null,
1587
+ status_changed_at: now,
1588
+ activated_at: activatedAt
1589
+ };
1590
+ }
1591
+ function userRoutes({ app, store, baseUrl, tokenMap }) {
1592
+ const oktaStore = getOktaStore(store);
1593
+ app.get("/api/v1/users", (c) => {
1594
+ const auth = requireManagementAuth(c, tokenMap);
1595
+ if (auth instanceof Response) return auth;
1596
+ const q = (c.req.query("q") ?? "").toLowerCase();
1597
+ const search = (c.req.query("search") ?? "").toLowerCase();
1598
+ const filter = c.req.query("filter") ?? "";
1599
+ let users = oktaStore.users.all();
1600
+ if (q) {
1601
+ users = users.filter(
1602
+ (user) => [user.login, user.email, user.first_name, user.last_name, user.display_name].join(" ").toLowerCase().includes(q)
1603
+ );
1604
+ }
1605
+ if (search) {
1606
+ users = users.filter(
1607
+ (user) => [user.login, user.email, user.first_name, user.last_name, user.display_name].join(" ").toLowerCase().includes(search)
1608
+ );
1609
+ }
1610
+ if (filter) {
1611
+ const statusMatch = filter.match(/status\s+eq\s+"?([A-Z_]+)"?/i);
1612
+ if (statusMatch?.[1]) {
1613
+ users = users.filter((user) => user.status === statusMatch[1]);
1614
+ }
1615
+ }
1616
+ const { page, per_page } = parsePagination(c);
1617
+ const total = users.length;
1618
+ const start = (page - 1) * per_page;
1619
+ const paged = users.slice(start, start + per_page);
1620
+ setLinkHeader(c, total, page, per_page);
1621
+ c.header("X-Total-Count", String(total));
1622
+ return c.json(paged.map((user) => userResponse(baseUrl, user)));
1623
+ });
1624
+ app.post("/api/v1/users", async (c) => {
1625
+ const auth = requireManagementAuth(c, tokenMap);
1626
+ if (auth instanceof Response) return auth;
1627
+ const body = await readJsonObject(c);
1628
+ const profile = body.profile && typeof body.profile === "object" ? body.profile : {};
1629
+ const login = typeof profile.login === "string" ? profile.login.trim() : "";
1630
+ const email = typeof profile.email === "string" ? profile.email.trim() : login;
1631
+ if (!login || !email) {
1632
+ return oktaError(c, 400, "E0000001", "profile.login and profile.email are required");
1633
+ }
1634
+ if (oktaStore.users.findOneBy("login", login) || oktaStore.users.findOneBy("email", email)) {
1635
+ return oktaError(c, 400, "E0000001", "A user with the same login or email already exists");
1636
+ }
1637
+ const activate = boolFromQuery(c.req.query("activate"), true);
1638
+ const now = nowIso();
1639
+ const firstName = typeof profile.firstName === "string" ? profile.firstName : "Test";
1640
+ const lastName = typeof profile.lastName === "string" ? profile.lastName : "User";
1641
+ const displayName = typeof profile.displayName === "string" ? profile.displayName : `${firstName} ${lastName}`.trim() || login;
1642
+ const created = oktaStore.users.insert({
1643
+ okta_id: generateOktaId("00u"),
1644
+ status: activate ? "ACTIVE" : "STAGED",
1645
+ activated_at: activate ? now : null,
1646
+ status_changed_at: now,
1647
+ last_login_at: null,
1648
+ password_changed_at: null,
1649
+ transitioning_to_status: null,
1650
+ login,
1651
+ email,
1652
+ first_name: firstName,
1653
+ last_name: lastName,
1654
+ display_name: displayName,
1655
+ locale: typeof profile.locale === "string" ? profile.locale : "en-US",
1656
+ time_zone: typeof profile.timeZone === "string" ? profile.timeZone : "UTC"
1657
+ });
1658
+ return c.json(userResponse(baseUrl, created), 201);
1659
+ });
1660
+ app.get("/api/v1/users/me", (c) => {
1661
+ const auth = requireManagementAuth(c, tokenMap);
1662
+ if (auth instanceof Response) return auth;
1663
+ const user = oktaStore.users.findOneBy("login", auth.login) ?? oktaStore.users.all()[0];
1664
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1665
+ const response = userResponse(baseUrl, user);
1666
+ return c.json({
1667
+ ...response,
1668
+ profile: {
1669
+ ...response.profile,
1670
+ displayName: userDisplayName(user)
1671
+ }
1672
+ });
1673
+ });
1674
+ app.get("/api/v1/users/:userId/groups", (c) => {
1675
+ const auth = requireManagementAuth(c, tokenMap);
1676
+ if (auth instanceof Response) return auth;
1677
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1678
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1679
+ const memberships = oktaStore.groupMemberships.findBy("user_okta_id", user.okta_id);
1680
+ const groups = memberships.map((membership) => oktaStore.groups.findOneBy("okta_id", membership.group_okta_id)).filter((group) => Boolean(group));
1681
+ return c.json(groups.map((group) => ({
1682
+ id: group.okta_id,
1683
+ profile: {
1684
+ name: group.name,
1685
+ description: group.description
1686
+ },
1687
+ type: group.type
1688
+ })));
1689
+ });
1690
+ app.post("/api/v1/users/:userId/lifecycle/activate", (c) => {
1691
+ const auth = requireManagementAuth(c, tokenMap);
1692
+ if (auth instanceof Response) return auth;
1693
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1694
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1695
+ const updated = oktaStore.users.update(user.id, setLifecycleStatus(user, "ACTIVE"));
1696
+ return c.json(userResponse(baseUrl, updated ?? user));
1697
+ });
1698
+ app.post("/api/v1/users/:userId/lifecycle/deactivate", (c) => {
1699
+ const auth = requireManagementAuth(c, tokenMap);
1700
+ if (auth instanceof Response) return auth;
1701
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1702
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1703
+ const updated = oktaStore.users.update(user.id, setLifecycleStatus(user, "DEPROVISIONED"));
1704
+ return c.json(userResponse(baseUrl, updated ?? user));
1705
+ });
1706
+ app.post("/api/v1/users/:userId/lifecycle/suspend", (c) => {
1707
+ const auth = requireManagementAuth(c, tokenMap);
1708
+ if (auth instanceof Response) return auth;
1709
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1710
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1711
+ const updated = oktaStore.users.update(user.id, setLifecycleStatus(user, "SUSPENDED"));
1712
+ return c.json(userResponse(baseUrl, updated ?? user));
1713
+ });
1714
+ app.post("/api/v1/users/:userId/lifecycle/unsuspend", (c) => {
1715
+ const auth = requireManagementAuth(c, tokenMap);
1716
+ if (auth instanceof Response) return auth;
1717
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1718
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1719
+ const updated = oktaStore.users.update(user.id, setLifecycleStatus(user, "ACTIVE"));
1720
+ return c.json(userResponse(baseUrl, updated ?? user));
1721
+ });
1722
+ app.get("/api/v1/users/:userId", (c) => {
1723
+ const auth = requireManagementAuth(c, tokenMap);
1724
+ if (auth instanceof Response) return auth;
1725
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1726
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1727
+ return c.json(userResponse(baseUrl, user));
1728
+ });
1729
+ app.put("/api/v1/users/:userId", async (c) => {
1730
+ const auth = requireManagementAuth(c, tokenMap);
1731
+ if (auth instanceof Response) return auth;
1732
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1733
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1734
+ const body = await readJsonObject(c);
1735
+ const profile = body.profile && typeof body.profile === "object" ? body.profile : {};
1736
+ const updates = updateUserProfile(user, profile);
1737
+ if (updates.login !== user.login && oktaStore.users.findOneBy("login", updates.login ?? "") || updates.email !== user.email && oktaStore.users.findOneBy("email", updates.email ?? "")) {
1738
+ return oktaError(c, 400, "E0000001", "A user with the same login or email already exists");
1739
+ }
1740
+ const updated = oktaStore.users.update(user.id, updates);
1741
+ return c.json(userResponse(baseUrl, updated ?? user));
1742
+ });
1743
+ app.post("/api/v1/users/:userId", async (c) => {
1744
+ const auth = requireManagementAuth(c, tokenMap);
1745
+ if (auth instanceof Response) return auth;
1746
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1747
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1748
+ const body = await readJsonObject(c);
1749
+ const profile = body.profile && typeof body.profile === "object" ? body.profile : {};
1750
+ const updates = updateUserProfile(user, profile);
1751
+ const updated = oktaStore.users.update(user.id, updates);
1752
+ return c.json(userResponse(baseUrl, updated ?? user));
1753
+ });
1754
+ app.delete("/api/v1/users/:userId", (c) => {
1755
+ const auth = requireManagementAuth(c, tokenMap);
1756
+ if (auth instanceof Response) return auth;
1757
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1758
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1759
+ if (user.status !== "DEPROVISIONED") {
1760
+ oktaStore.users.update(user.id, setLifecycleStatus(user, "DEPROVISIONED"));
1761
+ return new Response(null, { status: 204 });
1762
+ }
1763
+ for (const membership of oktaStore.groupMemberships.findBy("user_okta_id", user.okta_id)) {
1764
+ oktaStore.groupMemberships.delete(membership.id);
1765
+ }
1766
+ for (const assignment of oktaStore.appAssignments.findBy("user_okta_id", user.okta_id)) {
1767
+ oktaStore.appAssignments.delete(assignment.id);
1768
+ }
1769
+ oktaStore.users.delete(user.id);
1770
+ return new Response(null, { status: 204 });
1771
+ });
1772
+ app.post("/api/v1/users/:userId/lifecycle/reactivate", (c) => {
1773
+ const auth = requireManagementAuth(c, tokenMap);
1774
+ if (auth instanceof Response) return auth;
1775
+ const user = findUserByRef(oktaStore, c.req.param("userId"));
1776
+ if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1777
+ const updated = oktaStore.users.update(user.id, {
1778
+ status: "PROVISIONED",
1779
+ status_changed_at: nowIso(),
1780
+ transitioning_to_status: null
1781
+ });
1782
+ return c.json(userResponse(baseUrl, updated ?? user));
1783
+ });
1784
+ }
1785
+
1786
+ // src/index.ts
1787
+ function ensureMembership(store, groupOktaId, userOktaId) {
1788
+ const existing = store.groupMemberships.findBy("group_okta_id", groupOktaId).find((entry) => entry.user_okta_id === userOktaId);
1789
+ if (!existing) {
1790
+ store.groupMemberships.insert({
1791
+ group_okta_id: groupOktaId,
1792
+ user_okta_id: userOktaId
1793
+ });
1794
+ }
1795
+ }
1796
+ function ensureAppAssignment(store, appOktaId, userOktaId) {
1797
+ const existing = store.appAssignments.findBy("app_okta_id", appOktaId).find((entry) => entry.user_okta_id === userOktaId);
1798
+ if (!existing) {
1799
+ store.appAssignments.insert({
1800
+ app_okta_id: appOktaId,
1801
+ user_okta_id: userOktaId
1802
+ });
1803
+ }
1804
+ }
1805
+ function seedDefaults(store, _baseUrl) {
1806
+ const okta = getOktaStore(store);
1807
+ const defaultServer = okta.authorizationServers.findOneBy("server_id", DEFAULT_AUTH_SERVER_ID);
1808
+ if (!defaultServer) {
1809
+ okta.authorizationServers.insert(createDefaultAuthorizationServer());
1810
+ }
1811
+ let everyone = okta.groups.findOneBy("okta_id", DEFAULT_EVERYONE_GROUP_ID);
1812
+ if (!everyone) {
1813
+ everyone = okta.groups.insert(createDefaultGroup());
1814
+ }
1815
+ let user = okta.users.findOneBy("login", "testuser@okta.local");
1816
+ if (!user) {
1817
+ user = okta.users.insert(createDefaultUser());
1818
+ }
1819
+ if (!okta.oauthClients.findOneBy("client_id", "okta-test-client")) {
1820
+ okta.oauthClients.insert({
1821
+ client_id: "okta-test-client",
1822
+ client_secret: "okta-test-secret",
1823
+ name: "Sample OIDC Client",
1824
+ redirect_uris: ["http://localhost:3000/callback"],
1825
+ response_types: ["code"],
1826
+ grant_types: ["authorization_code", "refresh_token", "client_credentials"],
1827
+ token_endpoint_auth_method: "client_secret_post",
1828
+ auth_server_id: DEFAULT_AUTH_SERVER_ID
1829
+ });
1830
+ }
1831
+ if (!okta.oauthClients.findOneBy("client_id", "okta-test-app")) {
1832
+ okta.oauthClients.insert({
1833
+ client_id: "okta-test-app",
1834
+ client_secret: "",
1835
+ name: "Sample Public PKCE Client",
1836
+ redirect_uris: [
1837
+ "http://localhost:3000/official-sdk/callback",
1838
+ "http://localhost:3000/official-sdk"
1839
+ ],
1840
+ response_types: ["code"],
1841
+ grant_types: ["authorization_code", "refresh_token"],
1842
+ token_endpoint_auth_method: "none",
1843
+ auth_server_id: DEFAULT_AUTH_SERVER_ID
1844
+ });
1845
+ }
1846
+ if (okta.apps.all().length === 0) {
1847
+ okta.apps.insert(createDefaultApp());
1848
+ }
1849
+ ensureMembership(okta, everyone.okta_id, user.okta_id);
1850
+ }
1851
+ function seedFromConfig(store, _baseUrl, config) {
1852
+ const okta = getOktaStore(store);
1853
+ if (config.authorization_servers) {
1854
+ for (const server of config.authorization_servers) {
1855
+ const existing = okta.authorizationServers.findOneBy("server_id", server.id);
1856
+ if (existing) continue;
1857
+ okta.authorizationServers.insert({
1858
+ server_id: server.id,
1859
+ name: server.name,
1860
+ description: server.description ?? "",
1861
+ audiences: server.audiences ?? ["api://default"],
1862
+ status: normalizeAuthServerStatus(server.status, "ACTIVE")
1863
+ });
1864
+ }
1865
+ }
1866
+ if (config.users) {
1867
+ for (const user of config.users) {
1868
+ const byLogin = okta.users.findOneBy("login", user.login);
1869
+ if (byLogin) continue;
1870
+ const resolvedStatus = normalizeStatus(user.status, "ACTIVE");
1871
+ okta.users.insert({
1872
+ okta_id: user.okta_id ?? generateOktaId("00u"),
1873
+ status: resolvedStatus,
1874
+ activated_at: resolvedStatus === "ACTIVE" ? (/* @__PURE__ */ new Date()).toISOString() : null,
1875
+ status_changed_at: (/* @__PURE__ */ new Date()).toISOString(),
1876
+ last_login_at: null,
1877
+ password_changed_at: null,
1878
+ transitioning_to_status: null,
1879
+ login: user.login,
1880
+ email: user.email ?? user.login,
1881
+ first_name: user.first_name ?? "Test",
1882
+ last_name: user.last_name ?? "User",
1883
+ display_name: user.display_name ?? `${user.first_name ?? "Test"} ${user.last_name ?? "User"}`.trim(),
1884
+ locale: user.locale ?? "en-US",
1885
+ time_zone: user.time_zone ?? "UTC"
1886
+ });
1887
+ }
1888
+ }
1889
+ if (config.groups) {
1890
+ for (const group of config.groups) {
1891
+ const byName = okta.groups.findOneBy("name", group.name);
1892
+ if (byName) continue;
1893
+ okta.groups.insert({
1894
+ okta_id: group.okta_id ?? generateOktaId("00g"),
1895
+ type: normalizeGroupType(group.type, "OKTA_GROUP"),
1896
+ name: group.name,
1897
+ description: group.description ?? null
1898
+ });
1899
+ }
1900
+ }
1901
+ if (config.apps) {
1902
+ for (const app of config.apps) {
1903
+ const byName = okta.apps.findOneBy("name", app.name);
1904
+ if (byName) continue;
1905
+ okta.apps.insert({
1906
+ okta_id: app.okta_id ?? generateOktaId("0oa"),
1907
+ name: app.name,
1908
+ label: app.label ?? app.name,
1909
+ status: normalizeAppStatus(app.status, "ACTIVE"),
1910
+ sign_on_mode: app.sign_on_mode ?? "OPENID_CONNECT",
1911
+ settings: app.settings ?? {},
1912
+ credentials: app.credentials ?? {}
1913
+ });
1914
+ }
1915
+ }
1916
+ if (config.oauth_clients) {
1917
+ for (const client of config.oauth_clients) {
1918
+ const existing = okta.oauthClients.findOneBy("client_id", client.client_id);
1919
+ if (existing) continue;
1920
+ const tokenEndpointAuthMethod = client.token_endpoint_auth_method ?? "client_secret_post";
1921
+ okta.oauthClients.insert({
1922
+ client_id: client.client_id,
1923
+ client_secret: client.client_secret ?? "",
1924
+ name: client.name,
1925
+ redirect_uris: client.redirect_uris,
1926
+ response_types: client.response_types ?? ["code"],
1927
+ grant_types: client.grant_types ?? ["authorization_code", "refresh_token", "client_credentials"],
1928
+ token_endpoint_auth_method: tokenEndpointAuthMethod,
1929
+ auth_server_id: client.auth_server_id ?? DEFAULT_AUTH_SERVER_ID
1930
+ });
1931
+ }
1932
+ }
1933
+ if (config.group_memberships) {
1934
+ for (const membership of config.group_memberships) {
1935
+ const group = okta.groups.findOneBy("okta_id", membership.group_okta_id);
1936
+ const user = okta.users.findOneBy("okta_id", membership.user_okta_id);
1937
+ if (!group || !user) continue;
1938
+ ensureMembership(okta, group.okta_id, user.okta_id);
1939
+ }
1940
+ }
1941
+ if (config.app_assignments) {
1942
+ for (const assignment of config.app_assignments) {
1943
+ const app = okta.apps.findOneBy("okta_id", assignment.app_okta_id);
1944
+ const user = okta.users.findOneBy("okta_id", assignment.user_okta_id);
1945
+ if (!app || !user) continue;
1946
+ ensureAppAssignment(okta, app.okta_id, user.okta_id);
1947
+ }
1948
+ }
1949
+ }
1950
+ var oktaPlugin = {
1951
+ name: "okta",
1952
+ register(app, store, webhooks, baseUrl, tokenMap) {
1953
+ const ctx = { app, store, webhooks, baseUrl, tokenMap };
1954
+ oauthRoutes(ctx);
1955
+ userRoutes(ctx);
1956
+ groupRoutes(ctx);
1957
+ appRoutes(ctx);
1958
+ authorizationServerRoutes(ctx);
1959
+ },
1960
+ seed(store, baseUrl) {
1961
+ seedDefaults(store, baseUrl);
1962
+ }
1963
+ };
1964
+ var index_default = oktaPlugin;
1965
+ export {
1966
+ index_default as default,
1967
+ getOktaStore,
1968
+ oktaPlugin,
1969
+ seedFromConfig
1970
+ };
1971
+ //# sourceMappingURL=index.js.map