@atlasauth/emulator-core 0.1.0 → 0.3.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.
Files changed (300) hide show
  1. package/dist/adopt.d.ts +50 -0
  2. package/dist/adopt.js +57 -0
  3. package/dist/adopt.js.map +1 -0
  4. package/dist/coverage.d.ts +15 -0
  5. package/dist/coverage.js +31 -0
  6. package/dist/coverage.js.map +1 -0
  7. package/dist/crypto-util.d.ts +10 -0
  8. package/dist/crypto-util.js +76 -0
  9. package/dist/crypto-util.js.map +1 -0
  10. package/dist/custom-html.d.ts +57 -0
  11. package/dist/custom-html.js +491 -0
  12. package/dist/custom-html.js.map +1 -0
  13. package/dist/engine.d.ts +34 -3
  14. package/dist/engine.js +57 -8
  15. package/dist/engine.js.map +1 -1
  16. package/dist/error-catalog.js +16 -0
  17. package/dist/error-catalog.js.map +1 -1
  18. package/dist/event-gate.d.ts +3 -0
  19. package/dist/event-gate.js +430 -0
  20. package/dist/event-gate.js.map +1 -0
  21. package/dist/generic.js +14 -2
  22. package/dist/generic.js.map +1 -1
  23. package/dist/handlers/access-signals.d.ts +48 -0
  24. package/dist/handlers/access-signals.js +76 -0
  25. package/dist/handlers/access-signals.js.map +1 -0
  26. package/dist/handlers/approvals.d.ts +2 -0
  27. package/dist/handlers/approvals.js +418 -0
  28. package/dist/handlers/approvals.js.map +1 -0
  29. package/dist/handlers/authz-data.d.ts +2 -0
  30. package/dist/handlers/authz-data.js +627 -0
  31. package/dist/handlers/authz-data.js.map +1 -0
  32. package/dist/handlers/authz-layers.d.ts +229 -0
  33. package/dist/handlers/authz-layers.js +786 -0
  34. package/dist/handlers/authz-layers.js.map +1 -0
  35. package/dist/handlers/authz-state.d.ts +80 -0
  36. package/dist/handlers/authz-state.js +175 -0
  37. package/dist/handlers/authz-state.js.map +1 -0
  38. package/dist/handlers/authz.d.ts +2 -0
  39. package/dist/handlers/authz.js +410 -0
  40. package/dist/handlers/authz.js.map +1 -0
  41. package/dist/handlers/billing.d.ts +100 -0
  42. package/dist/handlers/billing.js +877 -0
  43. package/dist/handlers/billing.js.map +1 -0
  44. package/dist/handlers/cedar-ext.d.ts +35 -0
  45. package/dist/handlers/cedar-ext.js +174 -0
  46. package/dist/handlers/cedar-ext.js.map +1 -0
  47. package/dist/handlers/cedar.d.ts +146 -0
  48. package/dist/handlers/cedar.js +689 -0
  49. package/dist/handlers/cedar.js.map +1 -0
  50. package/dist/handlers/connections.d.ts +5 -0
  51. package/dist/handlers/connections.js +561 -0
  52. package/dist/handlers/connections.js.map +1 -0
  53. package/dist/handlers/control.js +6 -1
  54. package/dist/handlers/control.js.map +1 -1
  55. package/dist/handlers/device.d.ts +47 -0
  56. package/dist/handlers/device.js +665 -0
  57. package/dist/handlers/device.js.map +1 -0
  58. package/dist/handlers/emu3-misc.d.ts +38 -0
  59. package/dist/handlers/emu3-misc.js +1056 -0
  60. package/dist/handlers/emu3-misc.js.map +1 -0
  61. package/dist/handlers/emu4-actions-access.d.ts +46 -0
  62. package/dist/handlers/emu4-actions-access.js +107 -0
  63. package/dist/handlers/emu4-actions-access.js.map +1 -0
  64. package/dist/handlers/emu4-actions-catalog-extra.d.ts +3 -0
  65. package/dist/handlers/emu4-actions-catalog-extra.js +204 -0
  66. package/dist/handlers/emu4-actions-catalog-extra.js.map +1 -0
  67. package/dist/handlers/emu4-actions-catalog.d.ts +3 -0
  68. package/dist/handlers/emu4-actions-catalog.js +143 -0
  69. package/dist/handlers/emu4-actions-catalog.js.map +1 -0
  70. package/dist/handlers/emu4-actions-def.d.ts +50 -0
  71. package/dist/handlers/emu4-actions-def.js +250 -0
  72. package/dist/handlers/emu4-actions-def.js.map +1 -0
  73. package/dist/handlers/emu4-actions-sandbox.d.ts +36 -0
  74. package/dist/handlers/emu4-actions-sandbox.js +318 -0
  75. package/dist/handlers/emu4-actions-sandbox.js.map +1 -0
  76. package/dist/handlers/emu4-actions-typegen.d.ts +5 -0
  77. package/dist/handlers/emu4-actions-typegen.js +73 -0
  78. package/dist/handlers/emu4-actions-typegen.js.map +1 -0
  79. package/dist/handlers/emu4-actions.d.ts +2 -0
  80. package/dist/handlers/emu4-actions.js +506 -0
  81. package/dist/handlers/emu4-actions.js.map +1 -0
  82. package/dist/handlers/emu4-authz-trust.d.ts +40 -0
  83. package/dist/handlers/emu4-authz-trust.js +488 -0
  84. package/dist/handlers/emu4-authz-trust.js.map +1 -0
  85. package/dist/handlers/emu4-authz.d.ts +2 -0
  86. package/dist/handlers/emu4-authz.js +752 -0
  87. package/dist/handlers/emu4-authz.js.map +1 -0
  88. package/dist/handlers/emu4-billing-core.d.ts +23 -0
  89. package/dist/handlers/emu4-billing-core.js +22 -0
  90. package/dist/handlers/emu4-billing-core.js.map +1 -0
  91. package/dist/handlers/emu4-billing-x.d.ts +2 -0
  92. package/dist/handlers/emu4-billing-x.js +489 -0
  93. package/dist/handlers/emu4-billing-x.js.map +1 -0
  94. package/dist/handlers/emu4-billing.d.ts +14 -0
  95. package/dist/handlers/emu4-billing.js +846 -0
  96. package/dist/handlers/emu4-billing.js.map +1 -0
  97. package/dist/handlers/emu4-clones.d.ts +2 -0
  98. package/dist/handlers/emu4-clones.js +322 -0
  99. package/dist/handlers/emu4-clones.js.map +1 -0
  100. package/dist/handlers/emu4-compliance.d.ts +8 -0
  101. package/dist/handlers/emu4-compliance.js +39 -0
  102. package/dist/handlers/emu4-compliance.js.map +1 -0
  103. package/dist/handlers/emu4-config.d.ts +14 -0
  104. package/dist/handlers/emu4-config.js +148 -0
  105. package/dist/handlers/emu4-config.js.map +1 -0
  106. package/dist/handlers/emu4-cookies.d.ts +8 -0
  107. package/dist/handlers/emu4-cookies.js +248 -0
  108. package/dist/handlers/emu4-cookies.js.map +1 -0
  109. package/dist/handlers/emu4-erasure.d.ts +2 -0
  110. package/dist/handlers/emu4-erasure.js +587 -0
  111. package/dist/handlers/emu4-erasure.js.map +1 -0
  112. package/dist/handlers/emu4-federation.d.ts +8 -0
  113. package/dist/handlers/emu4-federation.js +18 -0
  114. package/dist/handlers/emu4-federation.js.map +1 -0
  115. package/dist/handlers/emu4-journey-math.d.ts +42 -0
  116. package/dist/handlers/emu4-journey-math.js +99 -0
  117. package/dist/handlers/emu4-journey-math.js.map +1 -0
  118. package/dist/handlers/emu4-messaging.d.ts +21 -0
  119. package/dist/handlers/emu4-messaging.js +595 -0
  120. package/dist/handlers/emu4-messaging.js.map +1 -0
  121. package/dist/handlers/emu4-minors.d.ts +7 -0
  122. package/dist/handlers/emu4-minors.js +408 -0
  123. package/dist/handlers/emu4-minors.js.map +1 -0
  124. package/dist/handlers/emu4-oauth-audit.d.ts +5 -0
  125. package/dist/handlers/emu4-oauth-audit.js +17 -0
  126. package/dist/handlers/emu4-oauth-audit.js.map +1 -0
  127. package/dist/handlers/emu4-oauth-auth.d.ts +83 -0
  128. package/dist/handlers/emu4-oauth-auth.js +363 -0
  129. package/dist/handlers/emu4-oauth-auth.js.map +1 -0
  130. package/dist/handlers/emu4-oauth-delivery.d.ts +68 -0
  131. package/dist/handlers/emu4-oauth-delivery.js +207 -0
  132. package/dist/handlers/emu4-oauth-delivery.js.map +1 -0
  133. package/dist/handlers/emu4-oauth-keys.d.ts +11 -0
  134. package/dist/handlers/emu4-oauth-keys.js +268 -0
  135. package/dist/handlers/emu4-oauth-keys.js.map +1 -0
  136. package/dist/handlers/emu4-oauth-pub.d.ts +36 -0
  137. package/dist/handlers/emu4-oauth-pub.js +420 -0
  138. package/dist/handlers/emu4-oauth-pub.js.map +1 -0
  139. package/dist/handlers/emu4-oauth.d.ts +10 -0
  140. package/dist/handlers/emu4-oauth.js +23 -0
  141. package/dist/handlers/emu4-oauth.js.map +1 -0
  142. package/dist/handlers/emu4-policies.d.ts +2 -0
  143. package/dist/handlers/emu4-policies.js +408 -0
  144. package/dist/handlers/emu4-policies.js.map +1 -0
  145. package/dist/handlers/emu4-region.d.ts +2 -0
  146. package/dist/handlers/emu4-region.js +189 -0
  147. package/dist/handlers/emu4-region.js.map +1 -0
  148. package/dist/handlers/emu4-saml.d.ts +12 -0
  149. package/dist/handlers/emu4-saml.js +532 -0
  150. package/dist/handlers/emu4-saml.js.map +1 -0
  151. package/dist/handlers/emu4-shared.d.ts +44 -0
  152. package/dist/handlers/emu4-shared.js +90 -0
  153. package/dist/handlers/emu4-shared.js.map +1 -0
  154. package/dist/handlers/emu4-snapshots.d.ts +38 -0
  155. package/dist/handlers/emu4-snapshots.js +231 -0
  156. package/dist/handlers/emu4-snapshots.js.map +1 -0
  157. package/dist/handlers/emu4-sns.d.ts +9 -0
  158. package/dist/handlers/emu4-sns.js +21 -0
  159. package/dist/handlers/emu4-sns.js.map +1 -0
  160. package/dist/handlers/emu4-trust-binding.d.ts +27 -0
  161. package/dist/handlers/emu4-trust-binding.js +344 -0
  162. package/dist/handlers/emu4-trust-binding.js.map +1 -0
  163. package/dist/handlers/emu4-trust.d.ts +35 -0
  164. package/dist/handlers/emu4-trust.js +543 -0
  165. package/dist/handlers/emu4-trust.js.map +1 -0
  166. package/dist/handlers/federation.d.ts +2 -0
  167. package/dist/handlers/federation.js +481 -0
  168. package/dist/handlers/federation.js.map +1 -0
  169. package/dist/handlers/fga-engine.d.ts +78 -0
  170. package/dist/handlers/fga-engine.js +663 -0
  171. package/dist/handlers/fga-engine.js.map +1 -0
  172. package/dist/handlers/governance.d.ts +2 -0
  173. package/dist/handlers/governance.js +460 -0
  174. package/dist/handlers/governance.js.map +1 -0
  175. package/dist/handlers/hooks.js +17 -1
  176. package/dist/handlers/hooks.js.map +1 -1
  177. package/dist/handlers/index.d.ts +5 -1
  178. package/dist/handlers/index.js +45 -3
  179. package/dist/handlers/index.js.map +1 -1
  180. package/dist/handlers/keys.js +6 -1
  181. package/dist/handlers/keys.js.map +1 -1
  182. package/dist/handlers/machines.js +61 -8
  183. package/dist/handlers/machines.js.map +1 -1
  184. package/dist/handlers/messaging.d.ts +6 -0
  185. package/dist/handlers/messaging.js +893 -0
  186. package/dist/handlers/messaging.js.map +1 -0
  187. package/dist/handlers/oauth-adv.d.ts +191 -0
  188. package/dist/handlers/oauth-adv.js +1305 -0
  189. package/dist/handlers/oauth-adv.js.map +1 -0
  190. package/dist/handlers/oauth-flows.d.ts +52 -0
  191. package/dist/handlers/oauth-flows.js +480 -0
  192. package/dist/handlers/oauth-flows.js.map +1 -0
  193. package/dist/handlers/oauth.d.ts +96 -0
  194. package/dist/handlers/oauth.js +793 -0
  195. package/dist/handlers/oauth.js.map +1 -0
  196. package/dist/handlers/orgs.js +6 -0
  197. package/dist/handlers/orgs.js.map +1 -1
  198. package/dist/handlers/passkeys.d.ts +3 -0
  199. package/dist/handlers/passkeys.js +293 -0
  200. package/dist/handlers/passkeys.js.map +1 -0
  201. package/dist/handlers/platform.d.ts +20 -0
  202. package/dist/handlers/platform.js +472 -0
  203. package/dist/handlers/platform.js.map +1 -0
  204. package/dist/handlers/round2.d.ts +8 -0
  205. package/dist/handlers/round2.js +372 -0
  206. package/dist/handlers/round2.js.map +1 -0
  207. package/dist/handlers/scim.d.ts +4 -0
  208. package/dist/handlers/scim.js +595 -0
  209. package/dist/handlers/scim.js.map +1 -0
  210. package/dist/handlers/sessions.d.ts +9 -3
  211. package/dist/handlers/sessions.js +76 -38
  212. package/dist/handlers/sessions.js.map +1 -1
  213. package/dist/handlers/support.d.ts +20 -0
  214. package/dist/handlers/support.js +62 -0
  215. package/dist/handlers/support.js.map +1 -0
  216. package/dist/handlers/users.js +7 -0
  217. package/dist/handlers/users.js.map +1 -1
  218. package/dist/handlers/workflows.d.ts +2 -0
  219. package/dist/handlers/workflows.js +428 -0
  220. package/dist/handlers/workflows.js.map +1 -0
  221. package/dist/index.d.ts +8 -0
  222. package/dist/index.js +23 -1
  223. package/dist/index.js.map +1 -1
  224. package/dist/jwe.d.ts +18 -0
  225. package/dist/jwe.js +101 -0
  226. package/dist/jwe.js.map +1 -0
  227. package/dist/jws.d.ts +41 -0
  228. package/dist/jws.js +135 -0
  229. package/dist/jws.js.map +1 -0
  230. package/dist/messages.d.ts +7 -0
  231. package/dist/messages.js +8 -4
  232. package/dist/messages.js.map +1 -1
  233. package/dist/messaging-callbacks.d.ts +100 -0
  234. package/dist/messaging-callbacks.js +767 -0
  235. package/dist/messaging-callbacks.js.map +1 -0
  236. package/dist/openapi.js +41 -1
  237. package/dist/openapi.js.map +1 -1
  238. package/dist/parity-bin.js +3 -1
  239. package/dist/parity-bin.js.map +1 -1
  240. package/dist/parity-emu3-authz.d.ts +3 -0
  241. package/dist/parity-emu3-authz.js +61 -0
  242. package/dist/parity-emu3-authz.js.map +1 -0
  243. package/dist/parity-emu3-billing.d.ts +3 -0
  244. package/dist/parity-emu3-billing.js +25 -0
  245. package/dist/parity-emu3-billing.js.map +1 -0
  246. package/dist/parity-emu3-messaging.d.ts +3 -0
  247. package/dist/parity-emu3-messaging.js +27 -0
  248. package/dist/parity-emu3-messaging.js.map +1 -0
  249. package/dist/parity-emu3-misc.d.ts +2 -0
  250. package/dist/parity-emu3-misc.js +63 -0
  251. package/dist/parity-emu3-misc.js.map +1 -0
  252. package/dist/parity-emu3-oauth.d.ts +2 -0
  253. package/dist/parity-emu3-oauth.js +39 -0
  254. package/dist/parity-emu3-oauth.js.map +1 -0
  255. package/dist/parity-emu4-actions.d.ts +2 -0
  256. package/dist/parity-emu4-actions.js +38 -0
  257. package/dist/parity-emu4-actions.js.map +1 -0
  258. package/dist/parity-emu4-authz.d.ts +3 -0
  259. package/dist/parity-emu4-authz.js +68 -0
  260. package/dist/parity-emu4-authz.js.map +1 -0
  261. package/dist/parity-emu4-billing.d.ts +6 -0
  262. package/dist/parity-emu4-billing.js +44 -0
  263. package/dist/parity-emu4-billing.js.map +1 -0
  264. package/dist/parity-emu4-compliance.d.ts +2 -0
  265. package/dist/parity-emu4-compliance.js +92 -0
  266. package/dist/parity-emu4-compliance.js.map +1 -0
  267. package/dist/parity-emu4-federation.d.ts +2 -0
  268. package/dist/parity-emu4-federation.js +33 -0
  269. package/dist/parity-emu4-federation.js.map +1 -0
  270. package/dist/parity-emu4-messaging.d.ts +3 -0
  271. package/dist/parity-emu4-messaging.js +42 -0
  272. package/dist/parity-emu4-messaging.js.map +1 -0
  273. package/dist/parity-emu4-oauth.d.ts +2 -0
  274. package/dist/parity-emu4-oauth.js +55 -0
  275. package/dist/parity-emu4-oauth.js.map +1 -0
  276. package/dist/parity-emu4-trust.d.ts +2 -0
  277. package/dist/parity-emu4-trust.js +41 -0
  278. package/dist/parity-emu4-trust.js.map +1 -0
  279. package/dist/parity-routes.d.ts +14 -0
  280. package/dist/parity-routes.js +439 -0
  281. package/dist/parity-routes.js.map +1 -0
  282. package/dist/parity.d.ts +10 -3
  283. package/dist/parity.js +14 -5
  284. package/dist/parity.js.map +1 -1
  285. package/dist/sns-cert.d.ts +9 -0
  286. package/dist/sns-cert.js +101 -0
  287. package/dist/sns-cert.js.map +1 -0
  288. package/dist/tokens.d.ts +85 -3
  289. package/dist/tokens.js +196 -24
  290. package/dist/tokens.js.map +1 -1
  291. package/dist/webauthn-virtual.d.ts +70 -0
  292. package/dist/webauthn-virtual.js +186 -0
  293. package/dist/webauthn-virtual.js.map +1 -0
  294. package/dist/webhook-ext.d.ts +16 -0
  295. package/dist/webhook-ext.js +218 -0
  296. package/dist/webhook-ext.js.map +1 -0
  297. package/dist/webhooks.d.ts +3 -0
  298. package/dist/webhooks.js +37 -3
  299. package/dist/webhooks.js.map +1 -1
  300. package/package.json +2 -2
@@ -0,0 +1,1305 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.umaEnabled = exports.REQUEST_OBJECT_PARAMS = exports.clientResponseTypes = exports.responseModeAllowed = exports.RESPONSE_TYPES_SUPPORTED = exports.UMA_GRANT_TYPE = exports.REQUEST_OBJECT_ALGS = exports.SUPPORTED_SCOPES = exports.GRANT_MANAGEMENT_SCOPES = exports.adv = exports.CODE_EXT = void 0;
4
+ exports.parseResponseType = parseResponseType;
5
+ exports.authUrl = authUrl;
6
+ exports.sidFor = sidFor;
7
+ exports.recordRp = recordRp;
8
+ exports.completeAuth = completeAuth;
9
+ exports.validateAuthorizationDetails = validateAuthorizationDetails;
10
+ exports.parseGrantManagement = parseGrantManagement;
11
+ exports.managedFor = managedFor;
12
+ exports.gmWidens = gmWidens;
13
+ exports.gmValid = gmValid;
14
+ exports.applyGrantManagement = applyGrantManagement;
15
+ exports.loadRequestObject = loadRequestObject;
16
+ exports.parseClientMeta = parseClientMeta;
17
+ exports.responseTypeProblem = responseTypeProblem;
18
+ exports.oauthAdvHandlers = oauthAdvHandlers;
19
+ exports.umaTicketGrant = umaTicketGrant;
20
+ exports.umaProtectionToken = umaProtectionToken;
21
+ const crypto_util_1 = require("../crypto-util");
22
+ const errors_1 = require("../errors");
23
+ const jws_1 = require("../jws");
24
+ const store_1 = require("../store");
25
+ const oauth_1 = require("./oauth");
26
+ const support_1 = require("./support");
27
+ const emu4_oauth_auth_1 = require("./emu4-oauth-auth");
28
+ const emu4_oauth_pub_1 = require("./emu4-oauth-pub");
29
+ const emu4_oauth_delivery_1 = require("./emu4-oauth-delivery");
30
+ exports.CODE_EXT = { rt: 'code', mode: null, details: null, gm: null };
31
+ exports.adv = (0, support_1.engineState)(() => ({
32
+ cfg: { dynamic_registration: false, require_initial_access_token: false, authorization_details_types: [], uma_enabled: false, dynamic_client_secret_ttl_seconds: 0 },
33
+ managed: new Map(),
34
+ sessions: new Map(),
35
+ rp: [],
36
+ iats: new Map(),
37
+ uma: { res: new Map(), tickets: new Map(), pct: new Map() },
38
+ }));
39
+ const NO_STORE = { 'cache-control': 'no-store', pragma: 'no-cache' };
40
+ const GRANT_QUERY_SCOPE = 'grant_management_query';
41
+ const GRANT_REVOKE_SCOPE = 'grant_management_revoke';
42
+ exports.GRANT_MANAGEMENT_SCOPES = [GRANT_QUERY_SCOPE, GRANT_REVOKE_SCOPE];
43
+ exports.SUPPORTED_SCOPES = ['openid', 'profile', 'email', 'offline_access', 'organizations', 'device_sso'];
44
+ exports.REQUEST_OBJECT_ALGS = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'ES256', 'ES384', 'ES512', 'EdDSA'];
45
+ const UMA_GRANT = 'urn:ietf:params:oauth:grant-type:uma-ticket';
46
+ exports.UMA_GRANT_TYPE = UMA_GRANT;
47
+ const UMA_PROTECTION_SCOPE = 'uma_protection';
48
+ // ── response types ──────────────────────────────────────────────────
49
+ exports.RESPONSE_TYPES_SUPPORTED = ['code', 'id_token', 'id_token token', 'code id_token', 'code token', 'code id_token token'];
50
+ const RT_ORDER = ['code', 'id_token', 'token'];
51
+ /** Parse a `response_type`; null when empty, repeating a token or naming an unknown one. */
52
+ function parseResponseType(value) {
53
+ if (!value)
54
+ return null;
55
+ const parts = value.split(' ').filter(Boolean);
56
+ if (!parts.length || new Set(parts).size !== parts.length || !parts.every((p) => RT_ORDER.includes(p)))
57
+ return null;
58
+ const has = (t) => parts.includes(t);
59
+ return { canonical: RT_ORDER.filter(has).join(' '), code: has('code'), idToken: has('id_token'), token: has('token'), frontChannelTokens: has('id_token') || has('token') };
60
+ }
61
+ /** Tokens never travel in the URL query (OAuth Multiple Response Types section 5). */
62
+ const responseModeAllowed = (rt, mode) => !rt.frontChannelTokens || (mode !== 'query' && mode !== 'query.jwt');
63
+ exports.responseModeAllowed = responseModeAllowed;
64
+ const clientResponseTypes = (client) => (Array.isArray(client.response_types) && client.response_types.length ? client.response_types : ['code']);
65
+ exports.clientResponseTypes = clientResponseTypes;
66
+ /** The redirect URL of an authorization response: query for code, fragment for any front-channel token (and for errors on such a request). */
67
+ function authUrl(e, redirectUri, params, front, mode) {
68
+ const u = new URL(redirectUri);
69
+ const entries = Object.entries(params).filter(([, v]) => v !== undefined);
70
+ if (front || mode === 'fragment')
71
+ u.hash = new URLSearchParams(entries).toString();
72
+ else
73
+ for (const [k, v] of entries)
74
+ u.searchParams.set(k, v);
75
+ return u.toString();
76
+ }
77
+ /** The OP session (the browser session whose id is the `sid` of every ID token it issued) for a user. */
78
+ function sidFor(e, userId) {
79
+ const s = (0, exports.adv)(e).sessions;
80
+ let sid = s.get(userId);
81
+ if (!sid)
82
+ s.set(userId, (sid = e.db.newId('osid')));
83
+ return sid;
84
+ }
85
+ function recordRp(e, sid, clientId, userId) {
86
+ if (!sid || !userId)
87
+ return;
88
+ const rp = (0, exports.adv)(e).rp;
89
+ if (!rp.some((r) => r.sid === sid && r.clientId === clientId))
90
+ rp.push({ sid, clientId, userId });
91
+ }
92
+ /** Finish an approved authorization request for any response type and return where to send the browser. */
93
+ async function completeAuth(e, a) {
94
+ const rt = parseResponseType(a.ext.rt);
95
+ const params = { state: a.state, iss: e.issuer };
96
+ let code;
97
+ if (rt.code)
98
+ params.code = code = (0, oauth_1.issueCode)(e, { ...a, ext: a.ext });
99
+ if (rt.frontChannelTokens) {
100
+ // Tokens minted at the authorization endpoint belong to no refresh grant and carry no refresh token.
101
+ let accessToken;
102
+ if (rt.token) {
103
+ const t = await (0, oauth_1.mintAccess)(e, { client: a.client, userId: a.userId, scope: a.scope, grant: null });
104
+ accessToken = t.token;
105
+ Object.assign(params, { access_token: t.token, token_type: 'Bearer', expires_in: '3600', scope: a.scope.join(' ') });
106
+ }
107
+ if (rt.idToken) {
108
+ const half = async (v) => (0, crypto_util_1.bytesToB64url)((await (0, crypto_util_1.sha256)(v)).slice(0, 16));
109
+ const sid = sidFor(e, a.userId);
110
+ params.id_token = await (0, oauth_1.idTokenFor)(e, a.client, a.userId, a.scope, a.nonce, e.clock.now(), sid, { ...(accessToken ? { at_hash: await half(accessToken) } : {}), ...(code ? { c_hash: await half(code) } : {}) });
111
+ recordRp(e, sid, a.client.id, a.userId);
112
+ }
113
+ }
114
+ return authUrl(e, a.redirectUri, params, rt.frontChannelTokens, a.ext.mode);
115
+ }
116
+ // ── rich authorization requests (RFC 9396) ─────────────────────────
117
+ function validateAuthorizationDetails(raw, allowlist) {
118
+ if (raw === undefined || raw === null || raw === '')
119
+ return { ok: true, value: undefined };
120
+ let parsed = raw;
121
+ if (typeof raw === 'string') {
122
+ try {
123
+ parsed = JSON.parse(raw);
124
+ }
125
+ catch {
126
+ return { ok: false, message: 'authorization_details must be a JSON array.' };
127
+ }
128
+ }
129
+ if (!Array.isArray(parsed) || !parsed.length)
130
+ return { ok: false, message: 'authorization_details must be a non-empty JSON array.' };
131
+ for (const el of parsed) {
132
+ if (typeof el !== 'object' || el === null || Array.isArray(el))
133
+ return { ok: false, message: 'Each authorization_details element must be an object.' };
134
+ const type = el.type;
135
+ if (typeof type !== 'string' || !type)
136
+ return { ok: false, message: 'Each authorization_details element needs a string "type".' };
137
+ if (!allowlist.includes(type))
138
+ return { ok: false, message: `authorization_details type is not supported: ${type}.` };
139
+ }
140
+ return { ok: true, value: parsed };
141
+ }
142
+ const stable = (v) => JSON.stringify(v, (_k, val) => (val && typeof val === 'object' && !Array.isArray(val) ? Object.fromEntries(Object.entries(val).sort(([a], [b]) => (a < b ? -1 : 1))) : val));
143
+ const dedupe = (items) => {
144
+ const seen = new Set();
145
+ return items.filter((i) => !seen.has(stable(i)) && !!seen.add(stable(i)));
146
+ };
147
+ function parseGrantManagement(action, grantId) {
148
+ const hasAction = action !== undefined && action !== null && action !== '';
149
+ const hasId = grantId !== undefined && grantId !== null && grantId !== '';
150
+ if (!hasAction)
151
+ return hasId ? { ok: false, message: 'grant_id requires grant_management_action.' } : { ok: true, value: null };
152
+ if (typeof action !== 'string' || !['create', 'merge', 'replace'].includes(action))
153
+ return { ok: false, message: 'grant_management_action must be one of create, merge, replace.' };
154
+ if (action === 'create')
155
+ return hasId ? { ok: false, message: 'grant_id must not be sent with grant_management_action=create.' } : { ok: true, value: { action } };
156
+ if (typeof grantId !== 'string' || !grantId || grantId.length > 128)
157
+ return { ok: false, message: `grant_id is required with grant_management_action=${action}.` };
158
+ return { ok: true, value: { action, grantId } };
159
+ }
160
+ const widens = (cur, req) => {
161
+ const held = new Set(cur.scopes);
162
+ const have = new Set((cur.details ?? []).map(stable));
163
+ return req.scopes.some((s) => !held.has(s)) || (req.details ?? []).some((d) => !have.has(stable(d)));
164
+ };
165
+ function planChange(action, cur, req) {
166
+ const w = widens(cur, req);
167
+ if (action === 'replace')
168
+ return { ok: true, content: req, widens: w };
169
+ const details = dedupe([...(cur.details ?? []), ...(req.details ?? [])]);
170
+ return { ok: true, widens: w, content: { scopes: [...new Set([...cur.scopes, ...req.scopes])], details: details.length ? details : null } };
171
+ }
172
+ const contentOf = (m) => ({ scopes: m.scopes, details: m.details });
173
+ /** The grant a merge or replace names: this client's and this user's, and still active; else the refusal. */
174
+ function managedFor(e, id, clientId, userId) {
175
+ const m = (0, exports.adv)(e).managed.get(id);
176
+ return m && m.status === 'active' && m.clientId === clientId && m.userId === userId ? m : undefined;
177
+ }
178
+ /** Would this request add anything to the grant it names (so the user must see the consent screen again)? */
179
+ function gmWidens(gm, m, scope, details) {
180
+ return !!m && gm.action !== 'create' && widens(contentOf(m), { scopes: scope, details });
181
+ }
182
+ function gmValid(e, gm, clientId, userId) {
183
+ return gm && gm.action !== 'create' && !managedFor(e, gm.grantId, clientId, userId) ? 'The grant_id does not name an active grant of this client and user.' : null;
184
+ }
185
+ /** Apply the create / merge / replace a redeemed code carried; the grant's tokens are issued for the result. */
186
+ function applyGrantManagement(e, grant, gm) {
187
+ const now = e.clock.now();
188
+ const req = { scopes: grant.scope, details: grant.details ?? null };
189
+ const a = (0, exports.adv)(e);
190
+ if (gm.action === 'create') {
191
+ const m = { id: e.db.newId('ogm'), clientId: grant.clientId, userId: grant.userId, scopes: req.scopes, details: req.details, status: 'active', revision: 1, createdAt: now, updatedAt: now };
192
+ a.managed.set(m.id, m);
193
+ grant.managedId = m.id;
194
+ return null;
195
+ }
196
+ const m = managedFor(e, gm.grantId, grant.clientId, grant.userId ?? '');
197
+ if (!m)
198
+ return { error: 'invalid_grant', message: 'The grant is no longer active.' };
199
+ const plan = planChange(gm.action, contentOf(m), req);
200
+ if (!plan.ok)
201
+ return plan;
202
+ m.scopes = plan.content.scopes;
203
+ m.details = plan.content.details;
204
+ m.revision++;
205
+ m.updatedAt = now;
206
+ grant.managedId = m.id;
207
+ grant.scope = m.scopes;
208
+ grant.details = m.details;
209
+ if (gm.action === 'replace')
210
+ clampLinked(e, m);
211
+ return null;
212
+ }
213
+ /** After a grant shrank: the refresh grants minted under it may no longer mint more than it holds. */
214
+ function clampLinked(e, m) {
215
+ for (const g of (0, oauth_1.st)(e).grants.values()) {
216
+ if (g.managedId !== m.id || g.revoked)
217
+ continue;
218
+ g.scope = g.scope.filter((s) => m.scopes.includes(s));
219
+ g.details = m.details;
220
+ }
221
+ }
222
+ const presentGrant = (m) => ({
223
+ grant_id: m.id,
224
+ client_id: m.clientId,
225
+ status: m.status,
226
+ scopes: m.scopes.map((scope) => ({ scope })),
227
+ ...(m.details && m.details.length ? { authorization_details: m.details } : {}),
228
+ revision: m.revision,
229
+ created_at: m.createdAt,
230
+ updated_at: m.updatedAt,
231
+ });
232
+ // ── signed request objects (RFC 9101) ──────────────────────────────
233
+ exports.REQUEST_OBJECT_PARAMS = ['response_type', 'redirect_uri', 'scope', 'state', 'nonce', 'code_challenge', 'code_challenge_method', 'audience', 'resource', 'response_mode', 'grant_management_action', 'grant_id'];
234
+ const lf = (error, description) => ({ ok: false, error, description });
235
+ /** Load and verify the request object a client sends by value (`request`) or by reference (a registered https `request_uri`). */
236
+ async function loadRequestObject(e, client, input) {
237
+ const doFetch = e.options.fetch ?? fetch;
238
+ let raw = input.request;
239
+ if (input.requestUri) {
240
+ if (!(Array.isArray(client.request_uris) ? client.request_uris : []).includes(input.requestUri))
241
+ return lf('invalid_request_uri', 'The request_uri is not registered for this client.');
242
+ try {
243
+ const res = await doFetch(input.requestUri, { headers: { accept: 'application/oauth-authz-req+jwt, application/jwt, */*' } });
244
+ if (!res.ok)
245
+ return lf('invalid_request_uri', 'The request_uri could not be fetched.');
246
+ const text = (await res.text()).trim();
247
+ if (!text || text.length > 64 * 1024)
248
+ return lf('invalid_request_uri', 'The request_uri document has an unusable size.');
249
+ raw = text;
250
+ }
251
+ catch {
252
+ return lf('invalid_request_uri', 'The request_uri could not be fetched.');
253
+ }
254
+ }
255
+ if (!raw)
256
+ return lf('invalid_request', 'No request object was sent.');
257
+ // A 5-part compact serialization is a JWE addressed to the emulator's published encryption key; the signed JWT is inside it.
258
+ if (raw.split('.').length === 5) {
259
+ await (0, emu4_oauth_auth_1.ensureRegisteredEncryptionKeys)(e);
260
+ const inner = await e.tokens.decryptEncrypted(raw);
261
+ if (!inner)
262
+ return lf('invalid_request_object', 'The encrypted request object could not be decrypted.');
263
+ raw = inner;
264
+ }
265
+ else if (client.request_object_encryption_alg) {
266
+ return lf('invalid_request_object', 'This client registered request object encryption; the request object must be encrypted.');
267
+ }
268
+ const d = (0, jws_1.decodeJwt)(raw);
269
+ if (!d)
270
+ return lf('invalid_request_object', 'The request object is malformed.');
271
+ const alg = String(d.header.alg ?? '');
272
+ if (!exports.REQUEST_OBJECT_ALGS.includes(alg))
273
+ return lf('invalid_request_object', 'The request object must be signed with an asymmetric algorithm.');
274
+ if (client.request_object_signing_alg && client.request_object_signing_alg !== alg)
275
+ return lf('invalid_request_object', 'The request object algorithm is not the one this client registered.');
276
+ let keys = Array.isArray(client.jwks?.keys) ? client.jwks.keys : [];
277
+ if (!keys.length && client.jwks_uri) {
278
+ try {
279
+ const res = await doFetch(client.jwks_uri);
280
+ keys = res.ok ? (await res.json()).keys ?? [] : [];
281
+ }
282
+ catch {
283
+ keys = [];
284
+ }
285
+ }
286
+ const cands = keys.filter((k) => k.use !== 'enc' && (!d.header.kid || k.kid === d.header.kid));
287
+ if (!cands.length)
288
+ return lf('invalid_request_object', 'This client has no registered keys for a signed request object.');
289
+ let verified = false;
290
+ for (const k of cands)
291
+ if ((verified = await (0, jws_1.verifyJws)(d, k)))
292
+ break;
293
+ const nowS = e.clock.now() / 1000;
294
+ const c = d.payload;
295
+ if (!verified || (typeof c.exp === 'number' && c.exp + 5 <= nowS) || (typeof c.nbf === 'number' && c.nbf - 5 > nowS))
296
+ return lf('invalid_request_object', 'The signed request object is invalid.');
297
+ const aud = c.aud === undefined ? [] : Array.isArray(c.aud) ? c.aud : [c.aud];
298
+ if (c.iss !== undefined && c.iss !== client.id)
299
+ return lf('invalid_request_object', 'The request object iss is not the client_id.');
300
+ if (aud.length && !aud.includes(e.issuer))
301
+ return lf('invalid_request_object', 'The request object aud is not this issuer.');
302
+ if (c.client_id !== undefined && c.client_id !== client.id)
303
+ return lf('invalid_request_object', 'The request object client_id does not match.');
304
+ return { ok: true, claims: c };
305
+ }
306
+ // ── client metadata (registration and the management endpoint) ──────
307
+ const isHttps = (u) => {
308
+ try {
309
+ return new URL(u).protocol === 'https:';
310
+ }
311
+ catch {
312
+ return false;
313
+ }
314
+ };
315
+ const loopback = (h) => h === 'localhost' || h === '127.0.0.1' || h === '[::1]';
316
+ /** https, or http on a loopback host (any port); a public native client may also use a private-use scheme; never a fragment. */
317
+ const validRedirect = (uri, allowNative) => {
318
+ let u;
319
+ try {
320
+ u = new URL(uri);
321
+ }
322
+ catch {
323
+ return false;
324
+ }
325
+ if (u.hash)
326
+ return false;
327
+ if (u.protocol === 'https:' || (u.protocol === 'http:' && loopback(u.hostname)))
328
+ return true;
329
+ return allowNative && /^[a-z][a-z0-9+.-]*:$/.test(u.protocol) && !['http:', 'javascript:', 'data:', 'file:'].includes(u.protocol);
330
+ };
331
+ const registrable = (uri) => {
332
+ try {
333
+ const u = new URL(uri);
334
+ return u.protocol === 'https:' || (u.protocol === 'http:' && (u.hostname === 'localhost' || u.hostname === '127.0.0.1'));
335
+ }
336
+ catch {
337
+ return false;
338
+ }
339
+ };
340
+ const PRIVATE_JWK = ['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth', 'k'];
341
+ /** The client-key and OIDC metadata fields on a create / update body; only the keys present are returned. Messages follow the real validator. */
342
+ function parseClientMeta(body) {
343
+ const out = {};
344
+ const bad = (message) => ({ ok: false, message });
345
+ if (body.jwks !== undefined) {
346
+ const j = body.jwks;
347
+ if (j === null)
348
+ out.jwks = null;
349
+ else {
350
+ if (typeof j !== 'object' || !Array.isArray(j.keys) || !j.keys.length || j.keys.length > 20)
351
+ return bad('jwks must be a JWK Set ({ keys: [...] }) of 1 to 20 keys.');
352
+ for (const k of j.keys) {
353
+ if (typeof k !== 'object' || k === null || typeof k.kty !== 'string')
354
+ return bad('Each jwks key must be a JWK with a kty.');
355
+ if (PRIVATE_JWK.some((p) => p in k))
356
+ return bad('jwks must hold public keys only; remove the private parameters.');
357
+ }
358
+ out.jwks = { keys: j.keys };
359
+ }
360
+ }
361
+ if (body.jwks_uri !== undefined) {
362
+ if (body.jwks_uri === null || body.jwks_uri === '')
363
+ out.jwks_uri = null;
364
+ else if (typeof body.jwks_uri !== 'string' || !isHttps(body.jwks_uri) || body.jwks_uri.length > 2048 || new URL(body.jwks_uri).hash)
365
+ return bad('jwks_uri must be an absolute https URL.');
366
+ else
367
+ out.jwks_uri = body.jwks_uri;
368
+ }
369
+ if (out.jwks && out.jwks_uri)
370
+ return bad('Register jwks or jwks_uri, not both.');
371
+ if (body.request_object_signing_alg !== undefined) {
372
+ const v = body.request_object_signing_alg;
373
+ if (v === null)
374
+ out.request_object_signing_alg = null;
375
+ else if (typeof v !== 'string' || !exports.REQUEST_OBJECT_ALGS.includes(v))
376
+ return bad(`request_object_signing_alg must be one of: ${exports.REQUEST_OBJECT_ALGS.join(', ')}.`);
377
+ else
378
+ out.request_object_signing_alg = v;
379
+ }
380
+ if (body.require_signed_request_object !== undefined) {
381
+ if (typeof body.require_signed_request_object !== 'boolean')
382
+ return bad('require_signed_request_object must be true or false.');
383
+ out.require_signed_request_object = body.require_signed_request_object;
384
+ }
385
+ if (body.response_types !== undefined) {
386
+ const rt = body.response_types;
387
+ if (!Array.isArray(rt) || !rt.length || rt.length > 6 || !rt.every((x) => typeof x === 'string'))
388
+ return bad('response_types must be a non-empty array of strings.');
389
+ const canon = [];
390
+ for (const r of rt) {
391
+ const c = r.split(' ').filter(Boolean).sort((a, b) => RT_ORDER.indexOf(a) - RT_ORDER.indexOf(b)).join(' ');
392
+ if (!exports.RESPONSE_TYPES_SUPPORTED.includes(c))
393
+ return bad(`Unsupported response_type: ${r}.`);
394
+ if (!canon.includes(c))
395
+ canon.push(c);
396
+ }
397
+ out.response_types = canon;
398
+ }
399
+ if (body.request_uris !== undefined) {
400
+ const ru = body.request_uris;
401
+ if (ru === null)
402
+ out.request_uris = null;
403
+ else if (!Array.isArray(ru) || ru.length > 20 || !ru.every((u) => typeof u === 'string' && isHttps(u)))
404
+ return bad('request_uris must be an array of at most 20 absolute https URIs.');
405
+ else
406
+ out.request_uris = ru;
407
+ }
408
+ const extra = (0, emu4_oauth_auth_1.parseExtraClientFields)(body);
409
+ if (!extra.ok)
410
+ return bad(extra.message);
411
+ Object.assign(out, extra.value);
412
+ return { ok: true, value: out };
413
+ }
414
+ /** Whether every response type the client holds is covered by its grant types (OIDC Registration section 2). */
415
+ function responseTypeProblem(responseTypes, grantTypes) {
416
+ for (const rt of responseTypes) {
417
+ const parts = rt.split(' ');
418
+ if (parts.includes('code') && !grantTypes.includes('authorization_code'))
419
+ return `response_type "${rt}" needs the authorization_code grant_type.`;
420
+ if ((parts.includes('id_token') || parts.includes('token')) && !grantTypes.includes('implicit'))
421
+ return `response_type "${rt}" needs the implicit grant_type.`;
422
+ }
423
+ return null;
424
+ }
425
+ // ── handlers ───────────────────────────────────────────────────────
426
+ function oauthAdvHandlers() {
427
+ const tokenErr = (status, error, description, headers = {}) => ({ status, body: { error, error_description: description }, headers: { ...NO_STORE, ...headers } });
428
+ const bearerOf = (c) => /^Bearer\s+(.+)$/i.exec(c.request.headers.get('authorization') ?? '')?.[1]?.trim();
429
+ /** The client's own registration, found by the Bearer registration_access_token; one 401 for every reason it cannot be reached. */
430
+ const regClient = (c) => {
431
+ const client = c.engine.db.oauthClients.get(c.params.clientId);
432
+ const bearer = bearerOf(c);
433
+ if (!bearer || !client || client.deleted || !client.registration_access_token || client.registration_access_token !== bearer)
434
+ return { error: tokenErr(401, 'invalid_token', 'Invalid registration access token.') };
435
+ return { client };
436
+ };
437
+ const clientView = (c) => ({
438
+ client_id: c.id,
439
+ client_id_issued_at: Math.floor(c.created_at / 1000),
440
+ client_secret_expires_at: c.secret_expires_at ? Math.floor(c.secret_expires_at / 1000) : 0,
441
+ redirect_uris: c.redirect_uris,
442
+ grant_types: c.grant_types,
443
+ response_types: (0, exports.clientResponseTypes)(c),
444
+ token_endpoint_auth_method: c.auth_method,
445
+ scope: (c.allowed_scopes ?? []).join(' '),
446
+ client_name: c.name,
447
+ ...(c.logo_uri ? { logo_uri: c.logo_uri } : {}),
448
+ ...(c.jwks ? { jwks: c.jwks } : {}),
449
+ ...(c.jwks_uri ? { jwks_uri: c.jwks_uri } : {}),
450
+ ...(c.request_uris ? { request_uris: c.request_uris } : {}),
451
+ ...(c.request_object_signing_alg ? { request_object_signing_alg: c.request_object_signing_alg } : {}),
452
+ ...(c.require_signed_request_object !== undefined ? { require_signed_request_object: c.require_signed_request_object } : {}),
453
+ ...(0, emu4_oauth_auth_1.clientExtras)(c),
454
+ });
455
+ const dropClient = (e, row) => {
456
+ row.deleted = true;
457
+ for (const g of (0, oauth_1.st)(e).grants.values())
458
+ if (g.clientId === row.id)
459
+ (0, oauth_1.revokeGrant)(e, g);
460
+ };
461
+ // ── UMA helpers ──
462
+ const umaOn = (e) => (0, exports.adv)(e).cfg.uma_enabled;
463
+ const umaErr = (status, code, message, headers = {}) => ({ status, body: { errors: [{ code, message }] }, headers });
464
+ const umaView = (r) => ({ _id: r.id, name: r.name, ...(r.type ? { type: r.type } : {}), ...(r.uri ? { uri: r.uri } : {}), ...(r.iconUri ? { icon_uri: r.iconUri } : {}), resource_scopes: r.scopes, ...(r.owner ? { owner: r.owner } : {}), owner_approval: false });
465
+ const parseRes = (b) => {
466
+ if (typeof b.name !== 'string' || !b.name.trim() || b.name.length > 200)
467
+ return { error: umaErr(400, 'VALIDATION_FAILED', 'name is required (at most 200 characters).') };
468
+ for (const k of ['type', 'uri', 'icon_uri'])
469
+ if (b[k] !== undefined && b[k] !== null && (typeof b[k] !== 'string' || b[k].length > 2000))
470
+ return { error: umaErr(400, 'VALIDATION_FAILED', `${k} must be a string.`) };
471
+ const s = b.resource_scopes ?? [];
472
+ if (!Array.isArray(s) || s.length > 100 || s.some((x) => typeof x !== 'string' || !/^[A-Za-z0-9._:\-/]+$/.test(x)))
473
+ return { error: umaErr(400, 'VALIDATION_FAILED', 'resource_scopes must be an array of scope strings.') };
474
+ return { value: { name: b.name, type: b.type ?? undefined, uri: b.uri ?? undefined, iconUri: b.icon_uri ?? undefined, scopes: [...new Set(s)], ...(typeof b.owner === 'string' && b.owner ? { owner: b.owner } : {}) } };
475
+ };
476
+ /** The protection API needs a PAT: a bearer access token with `uma_protection` from a confidential client. */
477
+ const rsFor = async (c) => {
478
+ const e = c.engine;
479
+ if (!umaOn(e))
480
+ return { error: umaErr(404, 'NOT_FOUND', 'UMA is not enabled for this instance.') };
481
+ const challenge = (error, status, message) => umaErr(status, error, message, { 'www-authenticate': `Bearer realm="uma", error="${error}", scope="${UMA_PROTECTION_SCOPE}"` });
482
+ const bearer = bearerOf(c);
483
+ if (!bearer)
484
+ return { error: challenge('invalid_token', 401, 'The protection API requires a Protection API Access Token.') };
485
+ const v = await e.tokens.verifyAny(bearer);
486
+ const rec = v.ok && v.claims.token_use === 'access_token' ? (0, oauth_1.st)(e).access.get(String(v.claims.jti)) : undefined;
487
+ if (!v.ok || !rec || rec.revoked)
488
+ return { error: challenge('invalid_token', 401, 'The Protection API Access Token is invalid or expired.') };
489
+ if (!rec.scope.includes(UMA_PROTECTION_SCOPE))
490
+ return { error: challenge('insufficient_scope', 403, `The token needs the ${UMA_PROTECTION_SCOPE} scope.`) };
491
+ const client = e.db.oauthClients.get(rec.clientId);
492
+ if (!client || client.deleted)
493
+ return { error: challenge('invalid_token', 401, 'Unknown client.') };
494
+ if (client.auth_method === 'none')
495
+ return { error: challenge('invalid_token', 401, 'The protection API requires a confidential client.') };
496
+ return { client, patUser: rec.userId };
497
+ };
498
+ const ownedRes = async (c) => {
499
+ const rs = await rsFor(c);
500
+ if ('error' in rs)
501
+ return rs;
502
+ const res = (0, exports.adv)(c.engine).uma.res.get(c.params.id ?? '');
503
+ return res && res.clientId === rs.client.id ? { client: rs.client, res } : { error: umaErr(404, 'not_found', 'Unknown resource.') };
504
+ };
505
+ return [
506
+ // ── emulator control ──
507
+ {
508
+ method: 'POST',
509
+ pattern: '/__emulator/oauth/config',
510
+ public: true,
511
+ handler: (c) => {
512
+ const cfg = (0, exports.adv)(c.engine).cfg;
513
+ for (const k of ['dynamic_registration', 'require_initial_access_token', 'uma_enabled']) {
514
+ if (c.body[k] === undefined)
515
+ continue;
516
+ if (typeof c.body[k] !== 'boolean')
517
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', `${k} must be true or false.`, k);
518
+ cfg[k] = c.body[k];
519
+ }
520
+ const types = c.body.authorization_details_types;
521
+ if (types !== undefined) {
522
+ if (!Array.isArray(types) || types.some((t) => typeof t !== 'string'))
523
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', 'authorization_details_types must be a list of strings.', 'authorization_details_types');
524
+ cfg.authorization_details_types = types;
525
+ }
526
+ const ttl = c.body.dynamic_client_secret_ttl_seconds;
527
+ if (ttl !== undefined) {
528
+ if (!Number.isInteger(ttl) || ttl < 0)
529
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', 'dynamic_client_secret_ttl_seconds must be a whole number of seconds (0 = no expiry).', 'dynamic_client_secret_ttl_seconds');
530
+ cfg.dynamic_client_secret_ttl_seconds = ttl;
531
+ }
532
+ return { body: { ...cfg } };
533
+ },
534
+ },
535
+ { method: 'GET', pattern: '/__emulator/oauth/config', public: true, handler: (c) => ({ body: { ...(0, exports.adv)(c.engine).cfg } }) },
536
+ {
537
+ // The resource owner's sharing policy (set through an account page on the real provider): who may hold which scopes of a resource.
538
+ method: 'POST',
539
+ pattern: '/__emulator/oauth/uma/share',
540
+ public: true,
541
+ handler: (c) => {
542
+ const res = (0, exports.adv)(c.engine).uma.res.get(String(c.body.resource_id ?? ''));
543
+ if (!res)
544
+ throw new errors_1.EmulatorError('NOT_FOUND', 'Unknown resource.');
545
+ if (typeof c.body.user_id !== 'string' || !Array.isArray(c.body.scopes) || c.body.scopes.some((s) => typeof s !== 'string'))
546
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', 'user_id and scopes are required.');
547
+ res.shares.set(c.body.user_id, c.body.scopes);
548
+ return { body: { ok: true } };
549
+ },
550
+ },
551
+ // ── initial access tokens (RFC 7591 section 3), Backend API ──
552
+ {
553
+ method: 'GET',
554
+ pattern: '/v1/oauth_initial_access_tokens',
555
+ scope: 'oauth_clients:read',
556
+ handler: (c) => ({ body: { object: 'list', data: [...(0, exports.adv)(c.engine).iats.values()].sort((a, b) => b.createdAt - a.createdAt).map((t) => presentIat(c.engine, t)), has_more: false } }),
557
+ },
558
+ {
559
+ method: 'POST',
560
+ pattern: '/v1/oauth_initial_access_tokens',
561
+ scope: 'oauth_clients:write',
562
+ handler: (c) => {
563
+ const e = c.engine;
564
+ const b = c.body;
565
+ const v = (m, p) => new errors_1.EmulatorError('VALIDATION_FAILED', m, p);
566
+ if (b.description !== undefined && b.description !== null && (typeof b.description !== 'string' || b.description.length > 200))
567
+ throw v('description must be a string of at most 200 characters.', 'description');
568
+ const int = (k, lo, hi, msg) => {
569
+ if (b[k] === undefined || b[k] === null)
570
+ return null;
571
+ if (typeof b[k] !== 'number' || !Number.isInteger(b[k]) || b[k] < lo || b[k] > hi)
572
+ throw v(msg, k);
573
+ return b[k];
574
+ };
575
+ const ttl = int('expires_in_seconds', 60, 365 * 86400, 'expires_in_seconds must be a whole number of seconds from 60 to 31536000.');
576
+ const maxUses = int('max_uses', 1, 1_000_000, 'max_uses must be a whole number from 1 to 1000000.');
577
+ const owner = b.owner === undefined || b.owner === null ? null : typeof b.owner === 'string' && b.owner.trim() && b.owner.length <= 128 ? b.owner.trim() : undefined;
578
+ if (owner === undefined)
579
+ throw v('owner must be an Atlas user or organization id.', 'owner');
580
+ let allowed = null;
581
+ if (b.allowed_grant_types !== undefined && b.allowed_grant_types !== null) {
582
+ const ok = ['authorization_code', 'refresh_token', 'urn:ietf:params:oauth:grant-type:device_code', 'implicit'];
583
+ if (!Array.isArray(b.allowed_grant_types) || !b.allowed_grant_types.length || !b.allowed_grant_types.every((x) => typeof x === 'string' && ok.includes(x)))
584
+ throw v(`allowed_grant_types must be a non-empty list of: ${ok.join(', ')}.`, 'allowed_grant_types');
585
+ allowed = [...new Set(b.allowed_grant_types)];
586
+ }
587
+ if (b.https_redirects_only !== undefined && typeof b.https_redirects_only !== 'boolean')
588
+ throw v('https_redirects_only must be true or false.', 'https_redirects_only');
589
+ const maxClients = int('max_clients', 1, 10_000, 'max_clients must be a whole number from 1 to 10000.');
590
+ if (maxClients !== null && !owner)
591
+ throw v('max_clients needs an owner to count against.', 'max_clients');
592
+ const now = e.clock.now();
593
+ const row = { id: e.db.newId('oiat'), token: `oiat_${(0, store_1.randomToken)(24)}`, description: typeof b.description === 'string' ? b.description.trim() || null : null, owner, allowedGrantTypes: allowed, httpsRedirectsOnly: b.https_redirects_only === true, maxClients, maxUses, useCount: 0, expiresAt: ttl !== null ? now + ttl * 1000 : null, revokedAt: null, lastUsedAt: null, createdAt: now };
594
+ (0, exports.adv)(e).iats.set(row.id, row);
595
+ return { status: 201, body: { ...presentIat(e, row), token: row.token, note: 'Copy the token now. Only a hash is stored, so it cannot be shown again.' } };
596
+ },
597
+ },
598
+ {
599
+ method: 'DELETE',
600
+ pattern: '/v1/oauth_initial_access_tokens/:id',
601
+ scope: 'oauth_clients:write',
602
+ handler: (c) => {
603
+ const row = (0, exports.adv)(c.engine).iats.get(c.params.id ?? '');
604
+ if (!row)
605
+ throw new errors_1.EmulatorError('NOT_FOUND', 'Unknown initial access token.');
606
+ row.revokedAt ??= c.engine.clock.now();
607
+ return { body: presentIat(c.engine, row) };
608
+ },
609
+ },
610
+ // ── dynamic client registration (RFC 7591) ──
611
+ {
612
+ method: 'POST',
613
+ pattern: '/oauth2/register',
614
+ public: true,
615
+ handler: async (c) => {
616
+ const e = c.engine;
617
+ const a = (0, exports.adv)(e);
618
+ if (!a.cfg.dynamic_registration)
619
+ return (0, oauth_1.fail)(403, 'access_denied', 'Dynamic client registration is disabled on this server.');
620
+ let body = c.body;
621
+ // RFC 7591 section 2.3: claims of a statement signed by an approved publisher override the request's.
622
+ if (body.software_statement !== undefined || (0, emu4_oauth_auth_1.dcrConfig)(e).require_software_statement) {
623
+ if (typeof body.software_statement !== 'string')
624
+ return (0, oauth_1.fail)(400, 'invalid_software_statement', 'A software statement is required to register here.');
625
+ const stmt = await (0, emu4_oauth_auth_1.verifySoftwareStatement)(e, body.software_statement);
626
+ if (!stmt.ok)
627
+ return (0, oauth_1.fail)(400, stmt.error, stmt.message);
628
+ body = { ...body, ...stmt.claims };
629
+ }
630
+ const presented = bearerOf(c);
631
+ const iatErr = (d) => tokenErr(401, 'invalid_token', d, { 'www-authenticate': 'Bearer error="invalid_token"' });
632
+ const uris = body.redirect_uris;
633
+ if (!Array.isArray(uris) || !uris.length || !uris.every((u) => typeof u === 'string'))
634
+ return (0, oauth_1.fail)(400, 'invalid_redirect_uri', 'redirect_uris is required and must be a non-empty array of strings.');
635
+ if (body.application_type !== undefined && body.application_type !== 'native' && body.application_type !== 'web')
636
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'application_type must be web or native.');
637
+ const native = body.application_type === 'native';
638
+ const method = typeof body.token_endpoint_auth_method === 'string' ? body.token_endpoint_auth_method : native ? 'none' : 'client_secret_basic';
639
+ if (!emu4_oauth_auth_1.AUTH_METHODS.includes(method))
640
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'Unsupported token_endpoint_auth_method.');
641
+ const isPublic = method === 'none';
642
+ const meta = parseClientMeta(body);
643
+ if (!meta.ok)
644
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', meta.message);
645
+ const keyedProblem = (0, emu4_oauth_auth_1.keyedClientProblem)(method, meta.value.jwks, meta.value.jwks_uri);
646
+ if (keyedProblem)
647
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', keyedProblem);
648
+ const badUri = uris.find((u) => !validRedirect(u, isPublic));
649
+ if (badUri)
650
+ return (0, oauth_1.fail)(400, 'invalid_redirect_uri', `Invalid redirect_uri: ${badUri}. It must be https or an http loopback address${isPublic ? ' or a private-use scheme' : ''}.`);
651
+ // OIDC Registration section 2 / Core section 8: subject_type and its sector.
652
+ const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: body.subject_type, sector_identifier_uri: body.sector_identifier_uri, redirect_uris: uris, redirectUrisChanged: true });
653
+ if (!subject.ok)
654
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', subject.message);
655
+ if (iatRequiredButMissing(a, presented))
656
+ return iatErr('An initial access token is required to register a client here.');
657
+ const requested = (typeof body.scope === 'string' ? body.scope : 'openid profile email').split(' ').filter(Boolean);
658
+ const unknownScopes = requested.filter((s) => !exports.SUPPORTED_SCOPES.includes(s));
659
+ if (unknownScopes.length)
660
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `Unsupported scope: ${unknownScopes.join(' ')}.`);
661
+ const scopes = requested.length ? requested : ['openid', 'profile', 'email'];
662
+ const grantTypes = Array.isArray(body.grant_types) && body.grant_types.every((g) => typeof g === 'string') ? body.grant_types : ['authorization_code', 'refresh_token'];
663
+ const unknownGrants = grantTypes.filter((g) => !['authorization_code', 'refresh_token', 'implicit'].includes(g));
664
+ if (unknownGrants.length)
665
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `Unsupported grant_types: ${unknownGrants.join(' ')}.`);
666
+ const responseTypes = (0, exports.clientResponseTypes)(meta.value);
667
+ const rtProblem = responseTypeProblem(responseTypes, grantTypes);
668
+ if (rtProblem)
669
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', rtProblem);
670
+ const name = typeof body.client_name === 'string' && body.client_name.trim() ? body.client_name : 'Dynamically Registered Client';
671
+ const logo = typeof body.logo_uri === 'string' ? body.logo_uri : null;
672
+ let postLogout = [];
673
+ if (body.post_logout_redirect_uris !== undefined) {
674
+ const p = body.post_logout_redirect_uris;
675
+ if (!Array.isArray(p) || !p.every((u) => typeof u === 'string'))
676
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'post_logout_redirect_uris must be an array of strings.');
677
+ const bad = p.find((u) => !registrable(u));
678
+ if (bad)
679
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `post_logout_redirect_uri must be an absolute https URI (or http://localhost): ${bad}`);
680
+ postLogout = p;
681
+ }
682
+ let claimsUris = [];
683
+ if (body.claims_redirect_uris !== undefined) {
684
+ const r = body.claims_redirect_uris;
685
+ if (!Array.isArray(r) || r.length > 20 || !r.every((u) => typeof u === 'string') || r.some((u) => !registrable(u)))
686
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'claims_redirect_uris must be an array of absolute https URIs (or http://localhost).');
687
+ claimsUris = r;
688
+ }
689
+ const logoutUri = (k) => {
690
+ const v = body[k];
691
+ return typeof v === 'string' && v ? (registrable(v) ? v : undefined) : null;
692
+ };
693
+ const bcl = logoutUri('backchannel_logout_uri');
694
+ if (bcl === undefined)
695
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'backchannel_logout_uri must be an absolute https URI (or http://localhost).');
696
+ const fcl = logoutUri('frontchannel_logout_uri');
697
+ if (fcl === undefined)
698
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'frontchannel_logout_uri must be an absolute https URI (or http://localhost).');
699
+ const now = e.clock.now();
700
+ // A presented token is spent only once the registration is otherwise valid, so a malformed request never burns a use.
701
+ const iat = presented ? [...a.iats.values()].find((t) => t.token === presented) : undefined;
702
+ if (iat) {
703
+ const badGrant = iat.allowedGrantTypes ? grantTypes.find((g) => !iat.allowedGrantTypes.includes(g)) : undefined;
704
+ if (badGrant)
705
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `grant_type ${badGrant} is not allowed for this developer.`);
706
+ const insecure = iat.httpsRedirectsOnly ? uris.find((u) => !u.startsWith('https://')) : undefined;
707
+ if (insecure)
708
+ return (0, oauth_1.fail)(400, 'invalid_redirect_uri', `redirect_uri ${insecure} must use https.`);
709
+ if (iat.owner && iat.maxClients != null && e.db.oauthClients.filter((x) => !x.deleted && x.owner === iat.owner).length >= iat.maxClients)
710
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `This developer already holds the maximum of ${iat.maxClients} clients.`);
711
+ }
712
+ if (presented) {
713
+ if (!iat || iat.revokedAt !== null || (iat.expiresAt !== null && iat.expiresAt <= now) || (iat.maxUses !== null && iat.useCount >= iat.maxUses))
714
+ return iatErr('The initial access token is invalid, expired, revoked or used up.');
715
+ iat.useCount++;
716
+ iat.lastUsedAt = now;
717
+ }
718
+ const id = `client_${(0, store_1.randomToken)(16)}`;
719
+ const secret = isPublic || method === 'private_key_jwt' ? undefined : `oacs_${(0, store_1.randomToken)(24)}`;
720
+ const ttl = a.cfg.dynamic_client_secret_ttl_seconds;
721
+ const secretExpires = secret && ttl > 0 ? now + ttl * 1000 : null;
722
+ const rat = `oarat_${(0, store_1.randomToken)(24)}`;
723
+ const row = e.db.oauthClients.insert({
724
+ id,
725
+ name,
726
+ redirect_uris: uris,
727
+ allowed_scopes: scopes,
728
+ grant_types: grantTypes,
729
+ auth_method: method,
730
+ first_party: false,
731
+ post_logout_redirect_uris: postLogout,
732
+ claims_redirect_uris: claimsUris,
733
+ backchannel_logout_uri: bcl,
734
+ frontchannel_logout_uri: fcl,
735
+ logo_uri: logo,
736
+ secret,
737
+ secret_expires_at: secretExpires,
738
+ registration_access_token: rat,
739
+ owner: iat?.owner ?? null,
740
+ dynamic: true,
741
+ ...stripNulls(meta.value),
742
+ ...subject.value,
743
+ created_at: now,
744
+ updated_at: now,
745
+ });
746
+ e.changed({ type: 'oauth_client.created', data: { object: 'oauth_client', id: row.id, client_id: row.id, name } });
747
+ return {
748
+ status: 201,
749
+ headers: NO_STORE,
750
+ body: {
751
+ id,
752
+ ...clientView(row),
753
+ application_type: native ? 'native' : 'web',
754
+ post_logout_redirect_uris: postLogout,
755
+ ...(claimsUris.length ? { claims_redirect_uris: claimsUris } : {}),
756
+ backchannel_logout_session_required: body.backchannel_logout_session_required === true,
757
+ frontchannel_logout_session_required: body.frontchannel_logout_session_required === true,
758
+ ...(bcl ? { backchannel_logout_uri: bcl } : {}),
759
+ ...(fcl ? { frontchannel_logout_uri: fcl } : {}),
760
+ ...(secret ? { client_secret: secret } : {}),
761
+ ...(row.owner ? { owner: row.owner } : {}),
762
+ status: 'approved',
763
+ registration_access_token: rat,
764
+ registration_client_uri: `${e.issuer}/oauth2/register/${id}`,
765
+ },
766
+ };
767
+ },
768
+ },
769
+ {
770
+ method: 'GET',
771
+ pattern: '/oauth2/register/:clientId',
772
+ public: true,
773
+ handler: (c) => {
774
+ const r = regClient(c);
775
+ return 'error' in r ? r.error : { body: clientView(r.client), headers: { 'cache-control': 'no-store' } };
776
+ },
777
+ },
778
+ {
779
+ method: 'PUT',
780
+ pattern: '/oauth2/register/:clientId',
781
+ public: true,
782
+ handler: async (c) => {
783
+ const e = c.engine;
784
+ const r = regClient(c);
785
+ if ('error' in r)
786
+ return r.error;
787
+ const client = r.client;
788
+ const body = c.body;
789
+ const patch = {};
790
+ if (Array.isArray(body.redirect_uris)) {
791
+ const uris = body.redirect_uris.filter((u) => typeof u === 'string');
792
+ const native = client.auth_method === 'none';
793
+ if (!uris.length || uris.some((u) => !validRedirect(u, native)))
794
+ return (0, oauth_1.fail)(400, 'invalid_redirect_uri', `redirect_uris must be non-empty and each be https or an http loopback address${native ? ' or a private-use scheme' : ''}.`);
795
+ patch.redirect_uris = uris;
796
+ }
797
+ if (typeof body.client_name === 'string' && body.client_name.trim())
798
+ patch.name = body.client_name;
799
+ if (typeof body.logo_uri === 'string')
800
+ patch.logo_uri = body.logo_uri;
801
+ const meta = parseClientMeta(body);
802
+ if (!meta.ok)
803
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', meta.message);
804
+ if ((meta.value.jwks && client.jwks_uri && meta.value.jwks_uri === undefined) || (meta.value.jwks_uri && client.jwks && meta.value.jwks === undefined))
805
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', 'Register jwks or jwks_uri, not both.');
806
+ Object.assign(patch, meta.value);
807
+ const keyed = (0, emu4_oauth_auth_1.keyedClientProblem)(client.auth_method, (patch.jwks !== undefined ? patch.jwks : client.jwks), (patch.jwks_uri !== undefined ? patch.jwks_uri : client.jwks_uri));
808
+ if (keyed)
809
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', keyed);
810
+ if (body.subject_type !== undefined || body.sector_identifier_uri !== undefined || patch.redirect_uris) {
811
+ const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: body.subject_type, sector_identifier_uri: body.sector_identifier_uri, redirect_uris: (patch.redirect_uris ?? client.redirect_uris), redirectUrisChanged: patch.redirect_uris !== undefined, existing: client, publisherSector: (0, emu4_oauth_pub_1.publisherSectorOf)(e, client) });
812
+ if (!subject.ok)
813
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', subject.message);
814
+ Object.assign(patch, subject.value);
815
+ }
816
+ if (typeof body.scope === 'string') {
817
+ const req = body.scope.split(' ').filter(Boolean);
818
+ const unknown = req.filter((s) => !exports.SUPPORTED_SCOPES.includes(s));
819
+ if (unknown.length)
820
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', `Unsupported scope: ${unknown.join(' ')}.`);
821
+ if (req.length)
822
+ patch.allowed_scopes = req;
823
+ }
824
+ const problem = responseTypeProblem((0, exports.clientResponseTypes)({ ...client, ...patch }), client.grant_types);
825
+ if (problem)
826
+ return (0, oauth_1.fail)(400, 'invalid_client_metadata', problem);
827
+ Object.assign(client, patch, { updated_at: e.clock.now() });
828
+ e.changed({ type: 'oauth_client.updated', data: { object: 'oauth_client', id: client.id, client_id: client.id, name: client.name } });
829
+ return { body: clientView(client), headers: { 'cache-control': 'no-store' } };
830
+ },
831
+ },
832
+ {
833
+ method: 'DELETE',
834
+ pattern: '/oauth2/register/:clientId',
835
+ public: true,
836
+ handler: (c) => {
837
+ const r = regClient(c);
838
+ if ('error' in r)
839
+ return r.error;
840
+ dropClient(c.engine, r.client);
841
+ c.engine.changed({ type: 'oauth_client.deleted', data: { object: 'oauth_client', id: r.client.id, deleted: true } });
842
+ return { status: 204 };
843
+ },
844
+ },
845
+ {
846
+ // Rotate the client's own secret with its registration_access_token; the old secret keeps working for the usual overlap.
847
+ method: 'POST',
848
+ pattern: '/oauth2/register/:clientId/secret',
849
+ public: true,
850
+ handler: (c) => {
851
+ const e = c.engine;
852
+ const r = regClient(c);
853
+ if ('error' in r)
854
+ return r.error;
855
+ const client = r.client;
856
+ if (client.auth_method === 'none')
857
+ return (0, oauth_1.fail)(400, 'invalid_request', 'This client has no secret to rotate.');
858
+ const now = e.clock.now();
859
+ const ttl = (0, exports.adv)(e).cfg.dynamic_client_secret_ttl_seconds;
860
+ client.previous_secret = client.secret;
861
+ client.previous_until = now + 86_400_000;
862
+ client.secret = `oacs_${(0, store_1.randomToken)(24)}`;
863
+ client.secret_expires_at = ttl > 0 ? now + ttl * 1000 : null;
864
+ client.updated_at = now;
865
+ return { headers: NO_STORE, body: { client_id: client.id, client_secret: client.secret, client_secret_expires_at: client.secret_expires_at ? Math.floor(client.secret_expires_at / 1000) : 0 } };
866
+ },
867
+ },
868
+ // ── RP-initiated logout (end_session_endpoint) ──
869
+ ...['GET', 'POST'].map((method) => ({ method, pattern: '/oauth2/logout', public: true, handler: (c) => logout(c) })),
870
+ // ── grant management ──
871
+ ...[
872
+ ['GET', GRANT_QUERY_SCOPE],
873
+ ['DELETE', GRANT_REVOKE_SCOPE],
874
+ ['PATCH', GRANT_REVOKE_SCOPE],
875
+ ].map(([method, needed]) => ({
876
+ method,
877
+ pattern: '/oauth2/grants/:id',
878
+ public: true,
879
+ handler: async (c) => {
880
+ const e = c.engine;
881
+ const presented = /^(Bearer|DPoP)\s+(.+)$/i.exec(c.request.headers.get('authorization') ?? '');
882
+ const scheme = presented?.[1] ?? 'Bearer';
883
+ const err = (status, error, d) => tokenErr(status, error, d, { 'www-authenticate': `${scheme} error="${error}"` });
884
+ if (!presented)
885
+ return err(401, 'invalid_request', 'A Bearer or DPoP access token is required.');
886
+ const chk = await (0, oauth_1.checkResourceToken)(c, presented[2], presented[1]);
887
+ if ('error' in chk)
888
+ return err(401, 'invalid_token', chk.message);
889
+ const rec = chk.rec;
890
+ if (!rec.scope.includes(needed))
891
+ return err(403, 'insufficient_scope', `The access token needs the ${needed} scope.`);
892
+ const m = (0, exports.adv)(e).managed.get(c.params.id ?? '');
893
+ const client = e.db.oauthClients.get(rec.clientId);
894
+ // The same answer for missing, revoked and someone else's, so ids cannot be probed.
895
+ if (!m || !client || m.status !== 'active' || m.clientId !== client.id || (rec.userId !== null && rec.userId !== m.userId))
896
+ return tokenErr(404, 'invalid_grant', 'Unknown grant.');
897
+ if (method === 'GET')
898
+ return { body: presentGrant(m), headers: NO_STORE };
899
+ if (method === 'DELETE') {
900
+ m.status = 'revoked';
901
+ m.updatedAt = e.clock.now();
902
+ for (const g of (0, oauth_1.st)(e).grants.values()) {
903
+ if (g.managedId !== m.id || g.revoked)
904
+ continue;
905
+ (0, oauth_1.revokeGrant)(e, g);
906
+ e.webhooks.emit('grant.revoked', { object: 'grant', id: g.id, client_id: client.id, user_id: g.userId, organization_id: null, session_ids: [] });
907
+ }
908
+ return { status: 204, headers: NO_STORE };
909
+ }
910
+ // PATCH: the client trims its own grant; it never widens it.
911
+ const b = c.body;
912
+ const scopes = b.scopes === undefined ? undefined : Array.isArray(b.scopes) && b.scopes.every((s) => typeof s === 'string') ? b.scopes : null;
913
+ const details = b.authorization_details === undefined ? undefined : Array.isArray(b.authorization_details) ? b.authorization_details : null;
914
+ if (scopes === null || details === null || (scopes === undefined && details === undefined))
915
+ return (0, oauth_1.fail)(400, 'invalid_request', 'Send scopes (array of strings) and/or authorization_details (array).');
916
+ const next = scopes ?? m.scopes;
917
+ const extra = next.filter((s) => !m.scopes.includes(s));
918
+ if (extra.length)
919
+ return (0, oauth_1.fail)(400, 'invalid_request', `A grant can only be narrowed. Sending the user through authorization with grant_management_action=merge is the way to add: ${extra.join(' ')}.`);
920
+ let nextDetails = m.details;
921
+ if (details !== undefined) {
922
+ const have = new Set((m.details ?? []).map(stable));
923
+ if (details.some((d) => !have.has(stable(d))))
924
+ return (0, oauth_1.fail)(400, 'invalid_request', 'A grant can only be narrowed; authorization_details cannot gain entries.');
925
+ nextDetails = details.length ? details : null;
926
+ }
927
+ if (!next.length)
928
+ return (0, oauth_1.fail)(400, 'invalid_request', 'A grant cannot be narrowed to nothing; revoke it instead.');
929
+ m.scopes = [...new Set(next)];
930
+ m.details = nextDetails;
931
+ m.revision++;
932
+ m.updatedAt = e.clock.now();
933
+ clampLinked(e, m);
934
+ return { body: presentGrant(m), headers: NO_STORE };
935
+ },
936
+ })),
937
+ // ── UMA 2.0 ──
938
+ {
939
+ method: 'GET',
940
+ pattern: '/.well-known/uma2-configuration',
941
+ public: true,
942
+ handler: (c) => {
943
+ const e = c.engine;
944
+ if (!umaOn(e))
945
+ return umaErr(404, 'NOT_FOUND', 'UMA is not enabled for this instance.');
946
+ const iss = e.issuer;
947
+ return {
948
+ headers: { 'cache-control': 'public, max-age=3600' },
949
+ body: {
950
+ issuer: iss,
951
+ authorization_endpoint: `${iss}/oauth2/authorize`,
952
+ token_endpoint: `${iss}/oauth2/token`,
953
+ jwks_uri: `${iss}/.well-known/jwks.json`,
954
+ introspection_endpoint: `${iss}/oauth2/introspect`,
955
+ revocation_endpoint: `${iss}/oauth2/revoke`,
956
+ resource_registration_endpoint: `${iss}/oauth2/uma/resource_set`,
957
+ permission_endpoint: `${iss}/oauth2/uma/permission`,
958
+ claims_interaction_endpoint: `${iss}/oauth2/uma/claims`,
959
+ grant_types_supported: [UMA_GRANT, 'client_credentials', 'authorization_code', 'refresh_token'],
960
+ uma_profiles_supported: [],
961
+ pat_profiles_supported: ['bearer'],
962
+ claim_token_format: [ID_TOKEN_FORMAT, ACCESS_TOKEN_FORMAT],
963
+ claim_token_profiles_supported: [ID_TOKEN_FORMAT, ACCESS_TOKEN_FORMAT],
964
+ scopes_supported: [UMA_PROTECTION_SCOPE],
965
+ response_types_supported: ['code'],
966
+ response_modes_supported: ['query'],
967
+ code_challenge_methods_supported: ['S256'],
968
+ subject_types_supported: ['public', 'pairwise'],
969
+ token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'private_key_jwt'],
970
+ introspection_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'private_key_jwt'],
971
+ ...((0, exports.adv)(e).cfg.dynamic_registration ? { registration_endpoint: `${iss}/oauth2/register` } : {}),
972
+ },
973
+ };
974
+ },
975
+ },
976
+ {
977
+ method: 'POST',
978
+ pattern: '/oauth2/uma/resource_set',
979
+ public: true,
980
+ handler: async (c) => {
981
+ const e = c.engine;
982
+ const rs = await rsFor(c);
983
+ if ('error' in rs)
984
+ return rs.error;
985
+ const p = parseRes(c.body);
986
+ if ('error' in p)
987
+ return p.error;
988
+ const id = e.db.newId('umar');
989
+ (0, exports.adv)(e).uma.res.set(id, { id, clientId: rs.client.id, name: '', scopes: [], ...p.value, ...(p.value.owner === undefined && rs.patUser ? { owner: rs.patUser } : {}), shares: new Map() });
990
+ return { status: 201, headers: { location: `${e.issuer}/oauth2/uma/resource_set/${id}` }, body: { _id: id } };
991
+ },
992
+ },
993
+ {
994
+ method: 'GET',
995
+ pattern: '/oauth2/uma/resource_set',
996
+ public: true,
997
+ handler: async (c) => {
998
+ const rs = await rsFor(c);
999
+ return 'error' in rs ? rs.error : { body: [...(0, exports.adv)(c.engine).uma.res.values()].filter((r) => r.clientId === rs.client.id).map((r) => r.id) };
1000
+ },
1001
+ },
1002
+ { method: 'GET', pattern: '/oauth2/uma/resource_set/:id', public: true, handler: async (c) => { const o = await ownedRes(c); return 'error' in o ? o.error : { body: umaView(o.res) }; } },
1003
+ {
1004
+ method: 'PUT',
1005
+ pattern: '/oauth2/uma/resource_set/:id',
1006
+ public: true,
1007
+ handler: async (c) => {
1008
+ const o = await ownedRes(c);
1009
+ if ('error' in o)
1010
+ return o.error;
1011
+ const p = parseRes(c.body);
1012
+ if ('error' in p)
1013
+ return p.error;
1014
+ Object.assign(o.res, p.value);
1015
+ return { body: umaView(o.res) };
1016
+ },
1017
+ },
1018
+ {
1019
+ method: 'DELETE',
1020
+ pattern: '/oauth2/uma/resource_set/:id',
1021
+ public: true,
1022
+ handler: async (c) => {
1023
+ const o = await ownedRes(c);
1024
+ if ('error' in o)
1025
+ return o.error;
1026
+ (0, exports.adv)(c.engine).uma.res.delete(o.res.id);
1027
+ return { status: 204 };
1028
+ },
1029
+ },
1030
+ {
1031
+ // The resource server asks for a permission ticket for what a client wants to reach.
1032
+ method: 'POST',
1033
+ pattern: '/oauth2/uma/permission',
1034
+ public: true,
1035
+ handler: async (c) => {
1036
+ const e = c.engine;
1037
+ const rs = await rsFor(c);
1038
+ if ('error' in rs)
1039
+ return rs.error;
1040
+ const raw = c.body;
1041
+ const list = Array.isArray(raw) ? raw : [raw];
1042
+ if (!list.length || list.length > 50)
1043
+ return umaErr(400, 'VALIDATION_FAILED', 'Between 1 and 50 permissions are required.');
1044
+ const perms = [];
1045
+ for (const p of list) {
1046
+ const res = typeof p?.resource_id === 'string' ? (0, exports.adv)(e).uma.res.get(p.resource_id) : undefined;
1047
+ if (!res || res.clientId !== rs.client.id)
1048
+ return umaErr(400, 'invalid_resource_id', 'Unknown resource_id.');
1049
+ const s = p.resource_scopes ?? [];
1050
+ if (!Array.isArray(s) || s.some((x) => typeof x !== 'string' || !res.scopes.includes(x)))
1051
+ return umaErr(400, 'invalid_scope', `resource_scopes must be scopes registered on ${res.id}.`);
1052
+ perms.push({ resource_id: res.id, resource_scopes: [...new Set(s)] });
1053
+ }
1054
+ const ticket = (0, store_1.randomToken)(32);
1055
+ (0, exports.adv)(e).uma.tickets.set(ticket, { clientId: rs.client.id, perms, expiresAt: e.clock.now() + UMA_TICKET_TTL_MS });
1056
+ return { status: 201, body: { ticket } };
1057
+ },
1058
+ },
1059
+ {
1060
+ // Claims gathering: the emulator signs in the requesting party (login_hint, else the emulator user, else the only user) and returns them to the claims redirect URI with a new ticket bound to them.
1061
+ method: 'GET',
1062
+ pattern: '/oauth2/uma/claims',
1063
+ public: true,
1064
+ handler: (c) => {
1065
+ const e = c.engine;
1066
+ if (!umaOn(e))
1067
+ return umaErr(404, 'NOT_FOUND', 'UMA is not enabled for this instance.');
1068
+ const bad = (m) => ({ ...page(`<h1>This request cannot continue</h1><p>${esc(m)}</p>`), status: 400 });
1069
+ const client = e.db.oauthClients.get(c.query.get('client_id') ?? undefined);
1070
+ if (!client || client.deleted)
1071
+ return bad('The application is not recognised.');
1072
+ const registered = client.claims_redirect_uris ?? [];
1073
+ const redirect = c.query.get('claims_redirect_uri') ?? (registered.length === 1 ? registered[0] : undefined);
1074
+ if (!redirect || !registered.includes(redirect))
1075
+ return bad('The claims redirect address is missing or not registered for this application.');
1076
+ const ticket = c.query.get('ticket') ?? '';
1077
+ const t = (0, exports.adv)(e).uma.tickets.get(ticket);
1078
+ if (!t || t.expiresAt <= e.clock.now())
1079
+ return bad('The access request has expired. Return to the application and start again.');
1080
+ const state = c.query.get('state') ?? undefined;
1081
+ if (state !== undefined && state.length > 1000)
1082
+ return bad('The state value is too long.');
1083
+ const go = (p) => {
1084
+ const u = new URL(redirect);
1085
+ for (const [k, v] of Object.entries(p))
1086
+ if (v !== undefined)
1087
+ u.searchParams.set(k, v);
1088
+ return { status: 302, headers: { location: u.toString(), 'cache-control': 'no-store' } };
1089
+ };
1090
+ const hint = c.query.get('login_hint');
1091
+ const live = e.db.users.filter((u) => !u.deleted);
1092
+ const user = hint ? live.find((u) => u.id === hint || u.emails.some((m) => m.email_address.toLowerCase() === hint.toLowerCase())) : (0, oauth_1.st)(e).autoUser ? live.find((u) => u.id === (0, oauth_1.st)(e).autoUser) : live.length === 1 ? live[0] : undefined;
1093
+ if (!user)
1094
+ return (0, oauth_1.fail)(400, 'login_required', 'No user to sign in. Pass login_hint or POST /__emulator/oauth/login.');
1095
+ if (c.query.get('emulator_decision') === 'deny')
1096
+ return go({ error: 'access_denied', state });
1097
+ (0, exports.adv)(e).uma.tickets.delete(ticket);
1098
+ const next = (0, store_1.randomToken)(32);
1099
+ (0, exports.adv)(e).uma.tickets.set(next, { ...t, userId: user.id, expiresAt: e.clock.now() + UMA_TICKET_TTL_MS });
1100
+ return go({ ticket: next, state });
1101
+ },
1102
+ },
1103
+ ];
1104
+ function presentIat(e, t) {
1105
+ const now = e.clock.now();
1106
+ const status = t.revokedAt !== null ? 'revoked' : t.expiresAt !== null && t.expiresAt <= now ? 'expired' : t.maxUses !== null && t.useCount >= t.maxUses ? 'used_up' : 'active';
1107
+ return { object: 'oauth_initial_access_token', id: t.id, token_prefix: t.token.slice(0, 9), description: t.description, profile: null, owner: t.owner, allowed_grant_types: t.allowedGrantTypes, https_redirects_only: t.httpsRedirectsOnly, max_clients: t.maxClients, default_status: 'development', login_url: null, consent_url: null, status, max_uses: t.maxUses, use_count: t.useCount, expires_at: t.expiresAt, revoked_at: t.revokedAt, last_used_at: t.lastUsedAt, created_at: t.createdAt };
1108
+ }
1109
+ }
1110
+ const ID_TOKEN_FORMAT = 'http://openid.net/specs/openid-connect-core-1_0.html#IDToken';
1111
+ const ACCESS_TOKEN_FORMAT = 'urn:ietf:params:oauth:token-type:access_token';
1112
+ const UMA_TICKET_TTL_MS = 5 * 60_000;
1113
+ const UMA_RPT_TTL_S = 300;
1114
+ const iatRequiredButMissing = (a, presented) => a.cfg.require_initial_access_token && !presented;
1115
+ const stripNulls = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== null));
1116
+ // ── logout ───────────────────────────────────────────────────────────
1117
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
1118
+ const page = (body) => ({ status: 200, raw: `<!doctype html><html><body>${body}</body></html>`, headers: { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'x-frame-options': 'DENY', 'content-security-policy': "frame-ancestors 'none'" } });
1119
+ const hintAudience = (claims) => {
1120
+ const aud = claims.aud;
1121
+ if (typeof aud === 'string')
1122
+ return aud;
1123
+ if (!Array.isArray(aud))
1124
+ return undefined;
1125
+ if (typeof claims.azp === 'string' && aud.includes(claims.azp))
1126
+ return claims.azp;
1127
+ return aud.length === 1 && typeof aud[0] === 'string' ? aud[0] : undefined;
1128
+ };
1129
+ /**
1130
+ * RP-Initiated Logout 1.0 with Back-Channel and Front-Channel Logout 1.0. A verified `id_token_hint` names the user and the OP
1131
+ * session (`sid`) to end; every client that holds a session under that `sid` is told. A `post_logout_redirect_uri` is
1132
+ * honoured only when it exactly matches one the hint's client registered (or the `client_id`'s, when there is no hint).
1133
+ * shortcut: the emulator has no browser session, so a request without a verified hint ends nothing and answers the confirmation page.
1134
+ */
1135
+ async function logout(c) {
1136
+ const e = c.engine;
1137
+ const a = (0, exports.adv)(e);
1138
+ const param = (n) => {
1139
+ const v = c.body[n] ?? c.query.get(n);
1140
+ return typeof v === 'string' && v ? v : undefined;
1141
+ };
1142
+ const hintToken = param('id_token_hint');
1143
+ const post = param('post_logout_redirect_uri');
1144
+ const state = param('state');
1145
+ const cid = param('client_id');
1146
+ let hint;
1147
+ let hintFailed = false;
1148
+ if (hintToken) {
1149
+ const v = await e.tokens.verifyAny(hintToken, { ignoreExp: true });
1150
+ const aud = v.ok && v.claims.token_use === 'id_token' ? hintAudience(v.claims) : undefined;
1151
+ const hc = aud ? e.db.oauthClients.get(aud) : undefined;
1152
+ if (v.ok && hc && !hc.deleted && (!cid || cid === aud))
1153
+ hint = { sub: String(v.claims.sub), sid: typeof v.claims.sid === 'string' && v.claims.sid ? v.claims.sid : undefined, client: hc };
1154
+ else
1155
+ hintFailed = true;
1156
+ }
1157
+ const named = hint ? hint.client : !hintFailed && cid ? e.db.oauthClients.get(cid) : undefined;
1158
+ const rpClient = named && !named.deleted ? named : undefined;
1159
+ const redirect = post && rpClient && (rpClient.post_logout_redirect_uris ?? []).includes(post) ? post : undefined;
1160
+ if (!hint || !hint.sid)
1161
+ return page('<p>Sign out? Send the id_token_hint of the session to end.</p>');
1162
+ const sid = hint.sid;
1163
+ // The hint's `sub` is what its client saw: for a pairwise client it names the user through the per-sector map.
1164
+ const ended = [];
1165
+ for (const r of a.rp.filter((x) => x.sid === sid))
1166
+ if ((await (0, emu4_oauth_auth_1.subjectFor)(e, hint.client, r.userId)) === hint.sub)
1167
+ ended.push(r);
1168
+ const hintUser = ended[0]?.userId ?? hint.sub;
1169
+ a.rp = a.rp.filter((r) => !ended.includes(r));
1170
+ if (a.sessions.get(hintUser) === sid)
1171
+ a.sessions.delete(hintUser);
1172
+ for (const g of (0, oauth_1.st)(e).grants.values())
1173
+ if (g.sid === sid && g.userId === hintUser && !g.revoked)
1174
+ (0, oauth_1.revokeGrant)(e, g);
1175
+ const front = [];
1176
+ await Promise.allSettled(ended.map(async (r) => {
1177
+ const cl = e.db.oauthClients.get(r.clientId);
1178
+ if (!cl || cl.deleted)
1179
+ return;
1180
+ // Queued with the real retry schedule; the first attempt is made now.
1181
+ if (cl.backchannel_logout_uri)
1182
+ await (0, emu4_oauth_delivery_1.enqueueBackchannelLogout)(e, cl, { userId: r.userId, sid: r.sid });
1183
+ if (cl.frontchannel_logout_uri) {
1184
+ const u = new URL(cl.frontchannel_logout_uri);
1185
+ u.searchParams.set('iss', e.issuer);
1186
+ u.searchParams.set('sid', r.sid);
1187
+ front.push(u.toString());
1188
+ }
1189
+ }));
1190
+ let to;
1191
+ if (redirect) {
1192
+ const u = new URL(redirect);
1193
+ if (state !== undefined)
1194
+ u.searchParams.set('state', state);
1195
+ to = u.toString();
1196
+ }
1197
+ if (to && !front.length)
1198
+ return { status: 302, headers: { location: to, 'cache-control': 'no-store' } };
1199
+ return page(`${[...new Set(front)].map((f) => `<iframe src="${esc(f)}" hidden></iframe>`).join('')}<p>You have been signed out.</p>${to ? `<script>setTimeout(function(){location.href=${JSON.stringify(to)}},300)</script><a href="${esc(to)}">Continue</a>` : ''}`);
1200
+ }
1201
+ // ── UMA ticket grant (called from the token endpoint) ─────────────────
1202
+ /** Trade a permission ticket plus the requesting party's claims for a Requesting Party Token. */
1203
+ async function umaTicketGrant(c, client) {
1204
+ const e = c.engine;
1205
+ const u = (0, exports.adv)(e).uma;
1206
+ const now = e.clock.now();
1207
+ const body = c.body;
1208
+ const f = (status, error, d, extra = {}) => (0, oauth_1.fail)(status, error, d, extra);
1209
+ if (typeof body.ticket !== 'string' || !body.ticket)
1210
+ return f(400, 'invalid_request', 'Missing ticket.');
1211
+ const ticket = u.tickets.get(body.ticket);
1212
+ const invalid = () => f(400, 'invalid_grant', 'The permission ticket is invalid, expired or already used.');
1213
+ if (!ticket || ticket.expiresAt <= now)
1214
+ return invalid();
1215
+ if (body.claim_token_format !== undefined && body.claim_token === undefined)
1216
+ return f(400, 'invalid_request', 'claim_token_format must accompany claim_token.');
1217
+ const format = body.claim_token_format ?? ID_TOKEN_FORMAT;
1218
+ if (body.claim_token && format !== ID_TOKEN_FORMAT && format !== ACCESS_TOKEN_FORMAT)
1219
+ return f(400, 'invalid_request', 'Unsupported claim_token_format.');
1220
+ let userId = ticket.userId;
1221
+ let viaPct = false;
1222
+ if (!userId && body.pct) {
1223
+ const p = u.pct.get(String(body.pct));
1224
+ if (p && p.clientId === client.id) {
1225
+ userId = p.userId;
1226
+ viaPct = true;
1227
+ }
1228
+ }
1229
+ if (!userId && body.claim_token) {
1230
+ const v = await e.tokens.verifyAny(String(body.claim_token));
1231
+ const ok = v.ok && (format === ACCESS_TOKEN_FORMAT ? v.claims.token_use === 'access_token' && v.claims.sub !== v.claims.client_id : v.claims.token_use === 'id_token');
1232
+ userId = ok && v.ok && e.db.users.get(String(v.claims.sub)) ? String(v.claims.sub) : undefined;
1233
+ if (!userId)
1234
+ return f(403, 'request_denied', 'The claim token does not identify an active user.');
1235
+ }
1236
+ if (!userId) {
1237
+ const next = (0, store_1.randomToken)(32);
1238
+ u.tickets.delete(body.ticket);
1239
+ u.tickets.set(next, { ...ticket, expiresAt: now + UMA_TICKET_TTL_MS });
1240
+ return f(403, 'need_info', 'The requesting party must be identified.', {
1241
+ ticket: next,
1242
+ required_claims: [{ name: 'sub', friendly_name: 'Requesting party', claim_type: 'urn:ietf:params:oauth:claim-type:subject', claim_token_format: [ID_TOKEN_FORMAT, ACCESS_TOKEN_FORMAT], issuer: [e.issuer] }],
1243
+ ...((client.claims_redirect_uris ?? []).length ? { redirect_user: `${e.issuer}/oauth2/uma/claims` } : {}),
1244
+ });
1245
+ }
1246
+ const user = e.db.users.get(userId);
1247
+ if (!user || user.deleted || user.banned)
1248
+ return f(403, 'request_denied', 'The claim token does not identify an active user.');
1249
+ const held = (r) => (r.owner === userId ? r.scopes : r.scopes.filter((s) => (r.shares.get(userId) ?? []).includes(s)));
1250
+ const asked = (0, oauth_1.splitScope)(body.scope);
1251
+ const granted = new Map();
1252
+ let rsId = ticket.clientId;
1253
+ for (const p of ticket.perms) {
1254
+ const r = u.res.get(p.resource_id);
1255
+ if (!r)
1256
+ continue;
1257
+ rsId = r.clientId;
1258
+ const wanted = [...new Set([...(p.resource_scopes.length ? p.resource_scopes : r.scopes), ...asked.filter((s) => r.scopes.includes(s))])];
1259
+ const ok = wanted.filter((s) => held(r).includes(s));
1260
+ if (ok.length)
1261
+ granted.set(r.id, new Set(ok));
1262
+ }
1263
+ // An earlier RPT upgrades: it must be live, this client's, this party's; each carried scope is re-checked, so a revoked share never survives.
1264
+ let upgraded = false;
1265
+ if (body.rpt && granted.size) {
1266
+ const v = await e.tokens.verifyAny(String(body.rpt));
1267
+ const rec = v.ok ? (0, oauth_1.st)(e).access.get(String(v.claims.jti)) : undefined;
1268
+ if (v.ok && rec && !rec.revoked && typeof v.claims.uma_rs === 'string' && v.claims.client_id === client.id && v.claims.sub === userId && Array.isArray(v.claims.permissions)) {
1269
+ for (const p of v.claims.permissions) {
1270
+ const r = u.res.get(String(p.resource_id));
1271
+ if (!r || r.clientId !== ticket.clientId)
1272
+ continue;
1273
+ const still = held(r).filter((s) => p.resource_scopes.includes(s));
1274
+ if (!still.length)
1275
+ continue;
1276
+ upgraded = true;
1277
+ granted.set(r.id, new Set([...(granted.get(r.id) ?? []), ...still]));
1278
+ }
1279
+ }
1280
+ }
1281
+ if (!granted.size)
1282
+ return f(403, 'request_denied', 'The requesting party has no access to the requested resources.');
1283
+ const iat = Math.floor(now / 1000);
1284
+ const permissions = [...granted].map(([resource_id, s]) => ({ resource_id, resource_scopes: [...s], exp: iat + UMA_RPT_TTL_S, iat, nbf: iat }));
1285
+ // A ticket is spent only when an RPT is issued.
1286
+ u.tickets.delete(body.ticket);
1287
+ const rs = e.db.oauthClients.get(rsId);
1288
+ const rpt = await (0, oauth_1.mintAccess)(e, { client, userId, scope: [...new Set(permissions.flatMap((p) => p.resource_scopes))], grant: null, audience: rs?.id ?? client.id, extra: { permissions, ...(rs ? { uma_rs: rs.id } : {}) }, ttl: UMA_RPT_TTL_S });
1289
+ let pct = viaPct ? String(body.pct) : undefined;
1290
+ if (!viaPct) {
1291
+ pct = (0, store_1.randomToken)(32);
1292
+ u.pct.set(pct, { clientId: client.id, userId });
1293
+ }
1294
+ return { body: { access_token: rpt.token, token_type: 'Bearer', upgraded, expires_in: UMA_RPT_TTL_S, pct }, headers: NO_STORE };
1295
+ }
1296
+ /** The protection API access token: a confidential client trades its credentials for a bearer token with `uma_protection`. */
1297
+ async function umaProtectionToken(c, client) {
1298
+ if ((0, oauth_1.splitScope)(c.body.scope).some((s) => s !== UMA_PROTECTION_SCOPE))
1299
+ return (0, oauth_1.fail)(400, 'invalid_scope', 'A protection API token carries only the uma_protection scope.');
1300
+ const t = await (0, oauth_1.mintAccess)(c.engine, { client, userId: null, scope: [UMA_PROTECTION_SCOPE], grant: null, audience: c.engine.issuer });
1301
+ return { body: { access_token: t.token, token_type: 'Bearer', expires_in: 3600, scope: UMA_PROTECTION_SCOPE }, headers: NO_STORE };
1302
+ }
1303
+ const umaEnabled = (e) => (0, exports.adv)(e).cfg.uma_enabled;
1304
+ exports.umaEnabled = umaEnabled;
1305
+ //# sourceMappingURL=oauth-adv.js.map