@emulators/okta 0.4.0 → 0.5.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/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @emulators/okta
2
+
3
+ Okta identity provider emulation with OAuth 2.0 / OIDC, user management, groups, apps, and authorization servers.
4
+
5
+ Part of [emulate](https://github.com/vercel-labs/emulate) — local drop-in replacement services for CI and no-network sandboxes.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @emulators/okta
11
+ ```
12
+
13
+ ## Endpoints
14
+
15
+ ### OAuth / OIDC
16
+
17
+ Default org server and custom authorization server paths (`/oauth2/:authServerId/...`):
18
+
19
+ - `GET /.well-known/openid-configuration` — OIDC discovery (default)
20
+ - `GET /oauth2/:authServerId/.well-known/openid-configuration` — per-server discovery
21
+ - `GET /oauth2/v1/keys` — JSON Web Key Set (JWKS)
22
+ - `GET /oauth2/v1/authorize` — authorization endpoint
23
+ - `POST /oauth2/v1/token` — token endpoint
24
+ - `GET /oauth2/v1/userinfo` — user info
25
+ - `POST /oauth2/v1/revoke` — token revocation
26
+ - `POST /oauth2/v1/introspect` — token introspection
27
+ - `GET /oauth2/v1/logout` — end session
28
+
29
+ ### Users
30
+ - `GET /api/v1/users` — list users
31
+ - `POST /api/v1/users` — create user
32
+ - `GET /api/v1/users/me` — current user (from token)
33
+ - `GET /api/v1/users/:userId` — get user
34
+ - `PUT /api/v1/users/:userId` — replace user
35
+ - `POST /api/v1/users/:userId` — partial update
36
+ - `DELETE /api/v1/users/:userId` — delete user
37
+ - `GET /api/v1/users/:userId/groups` — list user groups
38
+ - `POST /api/v1/users/:userId/lifecycle/activate` — activate
39
+ - `POST /api/v1/users/:userId/lifecycle/deactivate` — deactivate
40
+ - `POST /api/v1/users/:userId/lifecycle/suspend` — suspend
41
+ - `POST /api/v1/users/:userId/lifecycle/unsuspend` — unsuspend
42
+ - `POST /api/v1/users/:userId/lifecycle/reactivate` — reactivate
43
+
44
+ ### Groups
45
+ - `GET /api/v1/groups` — list groups
46
+ - `POST /api/v1/groups` — create group
47
+ - `GET /api/v1/groups/:groupId` — get group
48
+ - `PUT /api/v1/groups/:groupId` — update group
49
+ - `DELETE /api/v1/groups/:groupId` — delete group
50
+ - `GET /api/v1/groups/:groupId/users` — list group members
51
+ - `PUT /api/v1/groups/:groupId/users/:userId` — add user to group
52
+ - `DELETE /api/v1/groups/:groupId/users/:userId` — remove user from group
53
+
54
+ ### Apps
55
+ - `GET /api/v1/apps` — list apps
56
+ - `POST /api/v1/apps` — create app
57
+ - `GET /api/v1/apps/:appId` — get app
58
+ - `PUT /api/v1/apps/:appId` — update app
59
+ - `DELETE /api/v1/apps/:appId` — delete app
60
+ - `GET /api/v1/apps/:appId/users` — list assigned users
61
+ - `PUT /api/v1/apps/:appId/users/:userId` — assign user
62
+ - `DELETE /api/v1/apps/:appId/users/:userId` — unassign user
63
+ - `POST /api/v1/apps/:appId/lifecycle/activate` — activate app
64
+ - `POST /api/v1/apps/:appId/lifecycle/deactivate` — deactivate app
65
+
66
+ ### Authorization Servers
67
+ - `GET /api/v1/authorizationServers` — list
68
+ - `POST /api/v1/authorizationServers` — create
69
+ - `GET /api/v1/authorizationServers/:authServerId` — get
70
+ - `PUT /api/v1/authorizationServers/:authServerId` — update
71
+ - `DELETE /api/v1/authorizationServers/:authServerId` — delete
72
+ - `POST /api/v1/authorizationServers/:authServerId/lifecycle/activate` — activate
73
+ - `POST /api/v1/authorizationServers/:authServerId/lifecycle/deactivate` — deactivate
74
+
75
+ ## Seed Configuration
76
+
77
+ ```yaml
78
+ okta:
79
+ users:
80
+ - login: testuser@example.com
81
+ email: testuser@example.com
82
+ firstName: Test
83
+ lastName: User
84
+ groups:
85
+ - name: Everyone
86
+ description: All users
87
+ apps:
88
+ - name: My App
89
+ label: My App
90
+ authorization_servers:
91
+ - name: default
92
+ audiences: ["api://default"]
93
+ ```
94
+
95
+ ## Links
96
+
97
+ - [Full documentation](https://emulate.dev)
98
+ - [GitHub](https://github.com/vercel-labs/emulate)
Binary file
package/dist/index.js CHANGED
@@ -126,6 +126,7 @@ var FONTS = {
126
126
  "geist-sans.woff2": readFileSync(join(__dirname, "fonts", "geist-sans.woff2")),
127
127
  "GeistPixel-Square.woff2": readFileSync(join(__dirname, "fonts", "GeistPixel-Square.woff2"))
128
128
  };
129
+ var FAVICON = readFileSync(join(__dirname, "fonts", "favicon.ico"));
129
130
  function parsePagination(c) {
130
131
  const page = Math.max(1, parseInt(c.req.query("page") ?? "1", 10) || 1);
131
132
  const per_page = Math.min(100, Math.max(1, parseInt(c.req.query("per_page") ?? "30", 10) || 30));
@@ -303,6 +304,132 @@ body{
303
304
  .app-link-name{font-weight:600;font-size:.875rem;color:#33ff00;}
304
305
  .app-link-scopes{font-size:.6875rem;color:#1a8c00;margin-top:1px;}
305
306
  .empty{color:#1a8c00;text-align:center;padding:28px 0;font-size:.875rem;}
307
+
308
+ .inspector-layout{max-width:960px;margin:0 auto;padding:28px 20px;}
309
+ .inspector-tabs{display:flex;gap:4px;margin-bottom:20px;}
310
+ .inspector-tabs a{
311
+ padding:7px 16px;border-radius:6px;text-decoration:none;
312
+ font-size:.8125rem;color:#1a8c00;border:1px solid transparent;
313
+ transition:color .15s,border-color .15s;
314
+ }
315
+ .inspector-tabs a:hover{color:#33ff00;}
316
+ .inspector-tabs a.active{color:#33ff00;font-weight:600;border-color:#0a3300;background:#0a3300;}
317
+ .inspector-section{margin-bottom:24px;}
318
+ .inspector-section h2{
319
+ font-family:'Geist Pixel',monospace;
320
+ font-size:1rem;font-weight:600;color:#33ff00;margin-bottom:10px;
321
+ }
322
+ .inspector-section h3{
323
+ font-family:'Geist Pixel',monospace;
324
+ font-size:.875rem;font-weight:600;color:#1a8c00;margin:16px 0 8px;
325
+ }
326
+ .inspector-table{width:100%;border-collapse:collapse;margin-bottom:12px;}
327
+ .inspector-table th,.inspector-table td{
328
+ text-align:left;padding:8px 12px;border-bottom:1px solid #0a3300;
329
+ font-size:.8125rem;
330
+ }
331
+ .inspector-table th{color:#1a8c00;font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.04em;}
332
+ .inspector-table td{color:#33ff00;}
333
+ .inspector-table tbody tr{transition:background .1s;}
334
+ .inspector-table tbody tr:hover{background:#0a3300;}
335
+ .inspector-empty{color:#1a8c00;text-align:center;padding:20px 0;font-size:.8125rem;}
336
+
337
+ .checkout-layout{
338
+ display:flex;min-height:calc(100vh - 42px);
339
+ }
340
+ .checkout-summary{
341
+ flex:1;background:#020;padding:48px 40px 48px 10%;
342
+ display:flex;flex-direction:column;justify-content:center;
343
+ border-right:1px solid #0a3300;
344
+ }
345
+ .checkout-form-side{
346
+ flex:1;background:#000;padding:48px 10% 48px 40px;
347
+ display:flex;flex-direction:column;justify-content:center;
348
+ }
349
+ .checkout-merchant{
350
+ display:flex;align-items:center;gap:10px;margin-bottom:6px;
351
+ }
352
+ .checkout-merchant-name{
353
+ font-family:'Geist Pixel',monospace;
354
+ font-size:.9375rem;font-weight:600;color:#33ff00;
355
+ }
356
+ .checkout-test-badge{
357
+ font-size:.625rem;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
358
+ background:#0a3300;color:#1a8c00;padding:2px 8px;border-radius:4px;
359
+ }
360
+ .checkout-total{
361
+ font-family:'Geist Pixel',monospace;
362
+ font-size:2rem;font-weight:700;color:#33ff00;margin:8px 0 28px;
363
+ }
364
+ .checkout-line-item{
365
+ display:flex;align-items:center;gap:14px;padding:14px 0;
366
+ border-bottom:1px solid #0a3300;
367
+ }
368
+ .checkout-line-item:first-child{border-top:1px solid #0a3300;}
369
+ .checkout-item-icon{
370
+ width:42px;height:42px;border-radius:6px;background:#0a3300;
371
+ display:flex;align-items:center;justify-content:center;flex-shrink:0;
372
+ font-family:'Geist Pixel',monospace;font-size:.875rem;font-weight:700;color:#116600;
373
+ }
374
+ .checkout-item-details{flex:1;min-width:0;}
375
+ .checkout-item-name{font-size:.875rem;font-weight:600;color:#33ff00;}
376
+ .checkout-item-qty{font-size:.75rem;color:#1a8c00;margin-top:2px;}
377
+ .checkout-item-price{
378
+ font-size:.875rem;font-weight:600;color:#33ff00;text-align:right;white-space:nowrap;
379
+ }
380
+ .checkout-item-unit{font-size:.6875rem;color:#1a8c00;text-align:right;margin-top:2px;}
381
+ .checkout-totals{margin-top:20px;}
382
+ .checkout-totals-row{
383
+ display:flex;justify-content:space-between;padding:6px 0;
384
+ font-size:.8125rem;color:#1a8c00;
385
+ }
386
+ .checkout-totals-row.total{
387
+ border-top:1px solid #0a3300;margin-top:8px;padding-top:14px;
388
+ font-size:.9375rem;font-weight:600;color:#33ff00;
389
+ }
390
+ .checkout-form-section{margin-bottom:24px;}
391
+ .checkout-form-label{
392
+ font-size:.8125rem;font-weight:600;color:#33ff00;margin-bottom:8px;display:block;
393
+ }
394
+ .checkout-input{
395
+ width:100%;padding:10px 12px;border:1px solid #0a3300;border-radius:6px;
396
+ background:#020;color:#33ff00;font:inherit;font-size:.875rem;
397
+ transition:border-color .15s;outline:none;
398
+ }
399
+ .checkout-input:focus{border-color:#33ff00;}
400
+ .checkout-input::placeholder{color:#116600;}
401
+ .checkout-card-box{
402
+ border:1px solid #0a3300;border-radius:6px;padding:14px;
403
+ background:#020;
404
+ }
405
+ .checkout-card-row{
406
+ display:flex;gap:12px;margin-top:10px;
407
+ }
408
+ .checkout-card-row .checkout-input{flex:1;}
409
+ .checkout-sim-note{
410
+ font-size:.6875rem;color:#1a8c00;margin-top:10px;text-align:center;
411
+ font-style:italic;
412
+ }
413
+ .checkout-pay-btn{
414
+ width:100%;padding:14px;border:none;border-radius:8px;
415
+ background:#33ff00;color:#000;font:inherit;font-size:.9375rem;font-weight:700;
416
+ cursor:pointer;transition:background .15s;
417
+ font-family:'Geist Pixel',monospace;
418
+ }
419
+ .checkout-pay-btn:hover{background:#44ff22;}
420
+ .checkout-cancel{
421
+ text-align:center;margin-top:14px;
422
+ }
423
+ .checkout-cancel a{
424
+ color:#1a8c00;text-decoration:none;font-size:.8125rem;
425
+ transition:color .15s;
426
+ }
427
+ .checkout-cancel a:hover{color:#33ff00;}
428
+ @media(max-width:768px){
429
+ .checkout-layout{flex-direction:column;}
430
+ .checkout-summary{padding:32px 20px;border-right:none;border-bottom:1px solid #0a3300;}
431
+ .checkout-form-side{padding:32px 20px;}
432
+ }
306
433
  `;
307
434
  var POWERED_BY = `<div class="powered-by">Powered by <a href="https://emulate.dev" target="_blank" rel="noopener">emulate</a></div>`;
308
435
  function emuBar(service) {
@@ -322,6 +449,7 @@ function head(title) {
322
449
  <head>
323
450
  <meta charset="utf-8"/>
324
451
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
452
+ <link rel="icon" href="/_emulate/favicon.ico"/>
325
453
  <title>${escapeHtml(title)} | emulate</title>
326
454
  <style>${CSS}</style>
327
455
  </head>`;
@@ -353,6 +481,25 @@ ${emuBar(service)}
353
481
  ${POWERED_BY}
354
482
  </body></html>`;
355
483
  }
484
+ function renderFormPostPage(action, fields, service) {
485
+ const hiddens = Object.entries(fields).filter(([, v]) => v != null).map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}"/>`).join("\n");
486
+ return `${head("Redirecting")}
487
+ <body onload="document.forms[0].submit()">
488
+ ${emuBar(service)}
489
+ <div class="content">
490
+ <div class="content-inner" style="text-align:center">
491
+ <div class="card-subtitle">Redirecting&hellip;</div>
492
+ <form method="POST" action="${escapeAttr(action)}">
493
+ ${hiddens}
494
+ <noscript><button type="submit" class="user-btn" style="margin-top:12px;justify-content:center">
495
+ <span class="user-login">Continue</span>
496
+ </button></noscript>
497
+ </form>
498
+ </div>
499
+ </div>
500
+ ${POWERED_BY}
501
+ </body></html>`;
502
+ }
356
503
  function renderUserButton(opts) {
357
504
  const hiddens = Object.entries(opts.hiddenFields).map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}"/>`).join("");
358
505
  const nameLine = opts.name ? `<div class="user-meta">${escapeHtml(opts.name)}</div>` : "";
@@ -539,7 +686,10 @@ function getOktaStore(store) {
539
686
  apps: store.collection("okta.apps", ["okta_id", "name"]),
540
687
  oauthClients: store.collection("okta.oauth_clients", ["client_id", "auth_server_id"]),
541
688
  authorizationServers: store.collection("okta.auth_servers", ["server_id"]),
542
- groupMemberships: store.collection("okta.group_memberships", ["group_okta_id", "user_okta_id"]),
689
+ groupMemberships: store.collection("okta.group_memberships", [
690
+ "group_okta_id",
691
+ "user_okta_id"
692
+ ]),
543
693
  appAssignments: store.collection("okta.app_assignments", ["app_okta_id", "user_okta_id"])
544
694
  };
545
695
  }
@@ -553,9 +703,7 @@ function appRoutes({ app, store, baseUrl, tokenMap }) {
553
703
  const q = (c.req.query("q") ?? "").toLowerCase();
554
704
  let apps = oktaStore.apps.all();
555
705
  if (q) {
556
- apps = apps.filter(
557
- (entry) => `${entry.name} ${entry.label}`.toLowerCase().includes(q)
558
- );
706
+ apps = apps.filter((entry) => `${entry.name} ${entry.label}`.toLowerCase().includes(q));
559
707
  }
560
708
  const { page, per_page } = parsePagination(c);
561
709
  const total = apps.length;
@@ -783,9 +931,7 @@ function groupRoutes({ app, store, baseUrl, tokenMap }) {
783
931
  const q = (c.req.query("q") ?? "").toLowerCase();
784
932
  let groups = oktaStore.groups.all();
785
933
  if (q) {
786
- groups = groups.filter(
787
- (group) => `${group.name} ${group.description ?? ""}`.toLowerCase().includes(q)
788
- );
934
+ groups = groups.filter((group) => `${group.name} ${group.description ?? ""}`.toLowerCase().includes(q));
789
935
  }
790
936
  const { page, per_page } = parsePagination(c);
791
937
  const total = groups.length;
@@ -1091,10 +1237,10 @@ async function createIdToken(oktaStore, user, clientId, nonce, issuer, scope) {
1091
1237
  return new SignJWT(claims).setProtectedHeader({ alg: "RS256", kid: KID, typ: "JWT" }).setIssuer(issuer).setAudience(clientId).setIssuedAt(now).setExpirationTime("1h").sign(privateKey);
1092
1238
  }
1093
1239
  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
- );
1240
+ return new Response(JSON.stringify({ error: "invalid_token", error_description: "The access token is invalid." }), {
1241
+ status: 401,
1242
+ headers: { "Content-Type": "application/json" }
1243
+ });
1098
1244
  }
1099
1245
  function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1100
1246
  const oktaStore = getOktaStore(store);
@@ -1163,7 +1309,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1163
1309
  }
1164
1310
  if (!matchesRedirectUri(redirectUri, client.redirect_uris)) {
1165
1311
  return c.html(
1166
- renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
1312
+ renderErrorPage(
1313
+ "Redirect URI mismatch",
1314
+ "The redirect_uri is not registered for this application.",
1315
+ SERVICE_LABEL
1316
+ ),
1167
1317
  400
1168
1318
  );
1169
1319
  }
@@ -1171,25 +1321,27 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1171
1321
  }
1172
1322
  const users = oktaStore.users.all();
1173
1323
  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");
1324
+ const buttons = users.map(
1325
+ (user) => renderUserButton({
1326
+ letter: (user.login[0] ?? "?").toUpperCase(),
1327
+ login: user.login,
1328
+ name: userDisplayName(user),
1329
+ email: user.email,
1330
+ formAction: callbackPath,
1331
+ hiddenFields: {
1332
+ user_ref: user.okta_id,
1333
+ redirect_uri: redirectUri,
1334
+ scope,
1335
+ state,
1336
+ nonce,
1337
+ client_id: clientId,
1338
+ response_mode: responseMode,
1339
+ code_challenge: codeChallenge,
1340
+ code_challenge_method: codeChallengeMethod,
1341
+ auth_server_id: authServerId
1342
+ }
1343
+ })
1344
+ ).join("\n");
1193
1345
  const subtitle = clientName ? `Sign in to <strong>${escapeHtml(clientName)}</strong> with your Okta account.` : "Choose a seeded user to continue.";
1194
1346
  return c.html(
1195
1347
  renderCardPage(
@@ -1223,10 +1375,7 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1223
1375
  }
1224
1376
  const user = findUserByRef(oktaStore, userRef);
1225
1377
  if (!user) {
1226
- return c.html(
1227
- renderErrorPage("Unknown user", "The selected user is not available.", SERVICE_LABEL),
1228
- 400
1229
- );
1378
+ return c.html(renderErrorPage("Unknown user", "The selected user is not available.", SERVICE_LABEL), 400);
1230
1379
  }
1231
1380
  const configuredClients = getClientsForServer(oktaStore.oauthClients.all(), authServerId);
1232
1381
  if (configuredClients.length > 0) {
@@ -1239,7 +1388,11 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1239
1388
  }
1240
1389
  if (!matchesRedirectUri(redirectUri, client.redirect_uris)) {
1241
1390
  return c.html(
1242
- renderErrorPage("Redirect URI mismatch", "The redirect_uri is not registered for this application.", SERVICE_LABEL),
1391
+ renderErrorPage(
1392
+ "Redirect URI mismatch",
1393
+ "The redirect_uri is not registered for this application.",
1394
+ SERVICE_LABEL
1395
+ ),
1243
1396
  400
1244
1397
  );
1245
1398
  }
@@ -1258,17 +1411,7 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1258
1411
  });
1259
1412
  debug("okta.oauth", `[callback] code=${code.slice(0, 8)}... user=${user.login} server=${authServerId}`);
1260
1413
  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);
1414
+ return c.html(renderFormPostPage(redirectUri, { code, state }, SERVICE_LABEL));
1272
1415
  }
1273
1416
  const url = new URL(redirectUri);
1274
1417
  url.searchParams.set("code", code);
@@ -1276,7 +1419,10 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1276
1419
  return c.redirect(url.toString(), 302);
1277
1420
  };
1278
1421
  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")));
1422
+ app.post(
1423
+ "/oauth2/:authServerId/v1/authorize/callback",
1424
+ (c) => handleAuthorizeCallback(c, c.req.param("authServerId"))
1425
+ );
1280
1426
  const handleToken = async (c, authServerId) => {
1281
1427
  const server = resolveServer(authServerId, baseUrl, oktaStore);
1282
1428
  if (!server) return oktaError(c, 404, "E0000007", `Not found: authorization server '${authServerId}'`);
@@ -1306,7 +1452,10 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1306
1452
  return c.json({ error: "invalid_grant", error_description: "redirect_uri does not match." }, 400);
1307
1453
  }
1308
1454
  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);
1455
+ return c.json(
1456
+ { error: "invalid_grant", error_description: "Authorization code was not issued to this client." },
1457
+ 400
1458
+ );
1310
1459
  }
1311
1460
  if (pending.codeChallenge !== null) {
1312
1461
  if (!codeVerifier) {
@@ -1356,14 +1505,7 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1356
1505
  id: user.id,
1357
1506
  scopes: parseScope(scope)
1358
1507
  });
1359
- const idToken = await createIdToken(
1360
- oktaStore,
1361
- user,
1362
- audienceClient,
1363
- pending.nonce,
1364
- server.issuer,
1365
- scope
1366
- );
1508
+ const idToken = await createIdToken(oktaStore, user, audienceClient, pending.nonce, server.issuer, scope);
1367
1509
  return c.json({
1368
1510
  token_type: "Bearer",
1369
1511
  expires_in: 3600,
@@ -1382,7 +1524,10 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1382
1524
  return c.json({ error: "invalid_grant", error_description: "Authorization server mismatch." }, 400);
1383
1525
  }
1384
1526
  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);
1527
+ return c.json(
1528
+ { error: "invalid_grant", error_description: "Refresh token was not issued to this client." },
1529
+ 400
1530
+ );
1386
1531
  }
1387
1532
  const user = oktaStore.users.findOneBy("okta_id", existing.userOktaId);
1388
1533
  if (!user) return c.json({ error: "invalid_grant", error_description: "Unknown user." }, 400);
@@ -1552,9 +1697,7 @@ function oauthRoutes({ app, store, baseUrl, tokenMap }) {
1552
1697
  if (!postLogoutRedirectUri) return c.text("Logged out");
1553
1698
  const scopedClients = getClientsForServer(oktaStore.oauthClients.all(), authServerId);
1554
1699
  if (scopedClients.length > 0) {
1555
- const isAllowed = scopedClients.some(
1556
- (client) => matchesRedirectUri(postLogoutRedirectUri, client.redirect_uris)
1557
- );
1700
+ const isAllowed = scopedClients.some((client) => matchesRedirectUri(postLogoutRedirectUri, client.redirect_uris));
1558
1701
  if (!isAllowed) return c.text("Invalid post_logout_redirect_uri", 400);
1559
1702
  }
1560
1703
  return c.redirect(postLogoutRedirectUri, 302);
@@ -1678,14 +1821,16 @@ function userRoutes({ app, store, baseUrl, tokenMap }) {
1678
1821
  if (!user) return oktaError(c, 404, "E0000007", "Not found: user");
1679
1822
  const memberships = oktaStore.groupMemberships.findBy("user_okta_id", user.okta_id);
1680
1823
  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
- })));
1824
+ return c.json(
1825
+ groups.map((group) => ({
1826
+ id: group.okta_id,
1827
+ profile: {
1828
+ name: group.name,
1829
+ description: group.description
1830
+ },
1831
+ type: group.type
1832
+ }))
1833
+ );
1689
1834
  });
1690
1835
  app.post("/api/v1/users/:userId/lifecycle/activate", (c) => {
1691
1836
  const auth = requireManagementAuth(c, tokenMap);
@@ -1833,10 +1978,7 @@ function seedDefaults(store, _baseUrl) {
1833
1978
  client_id: "okta-test-app",
1834
1979
  client_secret: "",
1835
1980
  name: "Sample Public PKCE Client",
1836
- redirect_uris: [
1837
- "http://localhost:3000/official-sdk/callback",
1838
- "http://localhost:3000/official-sdk"
1839
- ],
1981
+ redirect_uris: ["http://localhost:3000/official-sdk/callback", "http://localhost:3000/official-sdk"],
1840
1982
  response_types: ["code"],
1841
1983
  grant_types: ["authorization_code", "refresh_token"],
1842
1984
  token_endpoint_auth_method: "none",