@atlasauth/emulator-core 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/LICENSE +21 -0
  2. package/dist/custom-html.d.ts +57 -0
  3. package/dist/custom-html.js +491 -0
  4. package/dist/custom-html.js.map +1 -0
  5. package/dist/engine.d.ts +1 -1
  6. package/dist/engine.js +5 -1
  7. package/dist/engine.js.map +1 -1
  8. package/dist/error-catalog.js +8 -0
  9. package/dist/error-catalog.js.map +1 -1
  10. package/dist/event-gate.js +60 -2
  11. package/dist/event-gate.js.map +1 -1
  12. package/dist/generic.js +5 -1
  13. package/dist/generic.js.map +1 -1
  14. package/dist/handlers/access-signals.d.ts +48 -0
  15. package/dist/handlers/access-signals.js +76 -0
  16. package/dist/handlers/access-signals.js.map +1 -0
  17. package/dist/handlers/approvals.js +54 -1
  18. package/dist/handlers/approvals.js.map +1 -1
  19. package/dist/handlers/authz-data.d.ts +2 -0
  20. package/dist/handlers/authz-data.js +627 -0
  21. package/dist/handlers/authz-data.js.map +1 -0
  22. package/dist/handlers/authz-layers.d.ts +229 -0
  23. package/dist/handlers/authz-layers.js +786 -0
  24. package/dist/handlers/authz-layers.js.map +1 -0
  25. package/dist/handlers/authz-state.d.ts +80 -0
  26. package/dist/handlers/authz-state.js +175 -0
  27. package/dist/handlers/authz-state.js.map +1 -0
  28. package/dist/handlers/authz.js +240 -177
  29. package/dist/handlers/authz.js.map +1 -1
  30. package/dist/handlers/billing.d.ts +99 -1
  31. package/dist/handlers/billing.js +556 -77
  32. package/dist/handlers/billing.js.map +1 -1
  33. package/dist/handlers/cedar-ext.d.ts +35 -0
  34. package/dist/handlers/cedar-ext.js +174 -0
  35. package/dist/handlers/cedar-ext.js.map +1 -0
  36. package/dist/handlers/cedar.d.ts +146 -0
  37. package/dist/handlers/cedar.js +689 -0
  38. package/dist/handlers/cedar.js.map +1 -0
  39. package/dist/handlers/connections.d.ts +4 -1
  40. package/dist/handlers/connections.js +267 -38
  41. package/dist/handlers/connections.js.map +1 -1
  42. package/dist/handlers/control.js +6 -1
  43. package/dist/handlers/control.js.map +1 -1
  44. package/dist/handlers/device.js +18 -1
  45. package/dist/handlers/device.js.map +1 -1
  46. package/dist/handlers/emu3-misc.d.ts +38 -0
  47. package/dist/handlers/emu3-misc.js +1056 -0
  48. package/dist/handlers/emu3-misc.js.map +1 -0
  49. package/dist/handlers/emu4-actions-access.d.ts +46 -0
  50. package/dist/handlers/emu4-actions-access.js +107 -0
  51. package/dist/handlers/emu4-actions-access.js.map +1 -0
  52. package/dist/handlers/emu4-actions-catalog-extra.d.ts +3 -0
  53. package/dist/handlers/emu4-actions-catalog-extra.js +204 -0
  54. package/dist/handlers/emu4-actions-catalog-extra.js.map +1 -0
  55. package/dist/handlers/emu4-actions-catalog.d.ts +3 -0
  56. package/dist/handlers/emu4-actions-catalog.js +143 -0
  57. package/dist/handlers/emu4-actions-catalog.js.map +1 -0
  58. package/dist/handlers/emu4-actions-def.d.ts +50 -0
  59. package/dist/handlers/emu4-actions-def.js +250 -0
  60. package/dist/handlers/emu4-actions-def.js.map +1 -0
  61. package/dist/handlers/emu4-actions-sandbox.d.ts +36 -0
  62. package/dist/handlers/emu4-actions-sandbox.js +318 -0
  63. package/dist/handlers/emu4-actions-sandbox.js.map +1 -0
  64. package/dist/handlers/emu4-actions-typegen.d.ts +5 -0
  65. package/dist/handlers/emu4-actions-typegen.js +73 -0
  66. package/dist/handlers/emu4-actions-typegen.js.map +1 -0
  67. package/dist/handlers/emu4-actions.d.ts +2 -0
  68. package/dist/handlers/emu4-actions.js +506 -0
  69. package/dist/handlers/emu4-actions.js.map +1 -0
  70. package/dist/handlers/emu4-authz-trust.d.ts +40 -0
  71. package/dist/handlers/emu4-authz-trust.js +488 -0
  72. package/dist/handlers/emu4-authz-trust.js.map +1 -0
  73. package/dist/handlers/emu4-authz.d.ts +2 -0
  74. package/dist/handlers/emu4-authz.js +752 -0
  75. package/dist/handlers/emu4-authz.js.map +1 -0
  76. package/dist/handlers/emu4-billing-core.d.ts +23 -0
  77. package/dist/handlers/emu4-billing-core.js +22 -0
  78. package/dist/handlers/emu4-billing-core.js.map +1 -0
  79. package/dist/handlers/emu4-billing-x.d.ts +2 -0
  80. package/dist/handlers/emu4-billing-x.js +489 -0
  81. package/dist/handlers/emu4-billing-x.js.map +1 -0
  82. package/dist/handlers/emu4-billing.d.ts +14 -0
  83. package/dist/handlers/emu4-billing.js +846 -0
  84. package/dist/handlers/emu4-billing.js.map +1 -0
  85. package/dist/handlers/emu4-clones.d.ts +2 -0
  86. package/dist/handlers/emu4-clones.js +322 -0
  87. package/dist/handlers/emu4-clones.js.map +1 -0
  88. package/dist/handlers/emu4-compliance.d.ts +8 -0
  89. package/dist/handlers/emu4-compliance.js +39 -0
  90. package/dist/handlers/emu4-compliance.js.map +1 -0
  91. package/dist/handlers/emu4-config.d.ts +14 -0
  92. package/dist/handlers/emu4-config.js +148 -0
  93. package/dist/handlers/emu4-config.js.map +1 -0
  94. package/dist/handlers/emu4-cookies.d.ts +8 -0
  95. package/dist/handlers/emu4-cookies.js +248 -0
  96. package/dist/handlers/emu4-cookies.js.map +1 -0
  97. package/dist/handlers/emu4-erasure.d.ts +2 -0
  98. package/dist/handlers/emu4-erasure.js +587 -0
  99. package/dist/handlers/emu4-erasure.js.map +1 -0
  100. package/dist/handlers/emu4-federation.d.ts +8 -0
  101. package/dist/handlers/emu4-federation.js +18 -0
  102. package/dist/handlers/emu4-federation.js.map +1 -0
  103. package/dist/handlers/emu4-journey-math.d.ts +42 -0
  104. package/dist/handlers/emu4-journey-math.js +99 -0
  105. package/dist/handlers/emu4-journey-math.js.map +1 -0
  106. package/dist/handlers/emu4-messaging.d.ts +21 -0
  107. package/dist/handlers/emu4-messaging.js +595 -0
  108. package/dist/handlers/emu4-messaging.js.map +1 -0
  109. package/dist/handlers/emu4-minors.d.ts +7 -0
  110. package/dist/handlers/emu4-minors.js +408 -0
  111. package/dist/handlers/emu4-minors.js.map +1 -0
  112. package/dist/handlers/emu4-oauth-audit.d.ts +5 -0
  113. package/dist/handlers/emu4-oauth-audit.js +17 -0
  114. package/dist/handlers/emu4-oauth-audit.js.map +1 -0
  115. package/dist/handlers/emu4-oauth-auth.d.ts +83 -0
  116. package/dist/handlers/emu4-oauth-auth.js +363 -0
  117. package/dist/handlers/emu4-oauth-auth.js.map +1 -0
  118. package/dist/handlers/emu4-oauth-delivery.d.ts +68 -0
  119. package/dist/handlers/emu4-oauth-delivery.js +207 -0
  120. package/dist/handlers/emu4-oauth-delivery.js.map +1 -0
  121. package/dist/handlers/emu4-oauth-keys.d.ts +11 -0
  122. package/dist/handlers/emu4-oauth-keys.js +268 -0
  123. package/dist/handlers/emu4-oauth-keys.js.map +1 -0
  124. package/dist/handlers/emu4-oauth-pub.d.ts +36 -0
  125. package/dist/handlers/emu4-oauth-pub.js +420 -0
  126. package/dist/handlers/emu4-oauth-pub.js.map +1 -0
  127. package/dist/handlers/emu4-oauth.d.ts +10 -0
  128. package/dist/handlers/emu4-oauth.js +23 -0
  129. package/dist/handlers/emu4-oauth.js.map +1 -0
  130. package/dist/handlers/emu4-policies.d.ts +2 -0
  131. package/dist/handlers/emu4-policies.js +408 -0
  132. package/dist/handlers/emu4-policies.js.map +1 -0
  133. package/dist/handlers/emu4-region.d.ts +2 -0
  134. package/dist/handlers/emu4-region.js +189 -0
  135. package/dist/handlers/emu4-region.js.map +1 -0
  136. package/dist/handlers/emu4-saml.d.ts +12 -0
  137. package/dist/handlers/emu4-saml.js +532 -0
  138. package/dist/handlers/emu4-saml.js.map +1 -0
  139. package/dist/handlers/emu4-shared.d.ts +44 -0
  140. package/dist/handlers/emu4-shared.js +90 -0
  141. package/dist/handlers/emu4-shared.js.map +1 -0
  142. package/dist/handlers/emu4-snapshots.d.ts +38 -0
  143. package/dist/handlers/emu4-snapshots.js +231 -0
  144. package/dist/handlers/emu4-snapshots.js.map +1 -0
  145. package/dist/handlers/emu4-sns.d.ts +9 -0
  146. package/dist/handlers/emu4-sns.js +21 -0
  147. package/dist/handlers/emu4-sns.js.map +1 -0
  148. package/dist/handlers/emu4-trust-binding.d.ts +27 -0
  149. package/dist/handlers/emu4-trust-binding.js +344 -0
  150. package/dist/handlers/emu4-trust-binding.js.map +1 -0
  151. package/dist/handlers/emu4-trust.d.ts +35 -0
  152. package/dist/handlers/emu4-trust.js +543 -0
  153. package/dist/handlers/emu4-trust.js.map +1 -0
  154. package/dist/handlers/fga-engine.d.ts +78 -0
  155. package/dist/handlers/fga-engine.js +663 -0
  156. package/dist/handlers/fga-engine.js.map +1 -0
  157. package/dist/handlers/governance.js +3 -2
  158. package/dist/handlers/governance.js.map +1 -1
  159. package/dist/handlers/hooks.js +13 -0
  160. package/dist/handlers/hooks.js.map +1 -1
  161. package/dist/handlers/index.js +14 -1
  162. package/dist/handlers/index.js.map +1 -1
  163. package/dist/handlers/machines.js +6 -0
  164. package/dist/handlers/machines.js.map +1 -1
  165. package/dist/handlers/messaging.d.ts +3 -1
  166. package/dist/handlers/messaging.js +455 -48
  167. package/dist/handlers/messaging.js.map +1 -1
  168. package/dist/handlers/oauth-adv.d.ts +191 -0
  169. package/dist/handlers/oauth-adv.js +1305 -0
  170. package/dist/handlers/oauth-adv.js.map +1 -0
  171. package/dist/handlers/oauth-flows.d.ts +25 -1
  172. package/dist/handlers/oauth-flows.js +111 -50
  173. package/dist/handlers/oauth-flows.js.map +1 -1
  174. package/dist/handlers/oauth.d.ts +24 -3
  175. package/dist/handlers/oauth.js +245 -60
  176. package/dist/handlers/oauth.js.map +1 -1
  177. package/dist/handlers/orgs.js +6 -0
  178. package/dist/handlers/orgs.js.map +1 -1
  179. package/dist/handlers/platform.d.ts +5 -0
  180. package/dist/handlers/platform.js +33 -15
  181. package/dist/handlers/platform.js.map +1 -1
  182. package/dist/handlers/scim.d.ts +2 -0
  183. package/dist/handlers/scim.js +198 -11
  184. package/dist/handlers/scim.js.map +1 -1
  185. package/dist/handlers/sessions.d.ts +2 -0
  186. package/dist/handlers/sessions.js +24 -16
  187. package/dist/handlers/sessions.js.map +1 -1
  188. package/dist/handlers/users.js +3 -0
  189. package/dist/handlers/users.js.map +1 -1
  190. package/dist/jwe.d.ts +18 -0
  191. package/dist/jwe.js +101 -0
  192. package/dist/jwe.js.map +1 -0
  193. package/dist/jws.js +2 -0
  194. package/dist/jws.js.map +1 -1
  195. package/dist/messaging-callbacks.d.ts +100 -0
  196. package/dist/messaging-callbacks.js +767 -0
  197. package/dist/messaging-callbacks.js.map +1 -0
  198. package/dist/openapi.d.ts +1 -0
  199. package/dist/openapi.js +44 -2
  200. package/dist/openapi.js.map +1 -1
  201. package/dist/parity-emu3-authz.d.ts +3 -0
  202. package/dist/parity-emu3-authz.js +61 -0
  203. package/dist/parity-emu3-authz.js.map +1 -0
  204. package/dist/parity-emu3-billing.d.ts +3 -0
  205. package/dist/parity-emu3-billing.js +25 -0
  206. package/dist/parity-emu3-billing.js.map +1 -0
  207. package/dist/parity-emu3-messaging.d.ts +3 -0
  208. package/dist/parity-emu3-messaging.js +27 -0
  209. package/dist/parity-emu3-messaging.js.map +1 -0
  210. package/dist/parity-emu3-misc.d.ts +2 -0
  211. package/dist/parity-emu3-misc.js +63 -0
  212. package/dist/parity-emu3-misc.js.map +1 -0
  213. package/dist/parity-emu3-oauth.d.ts +2 -0
  214. package/dist/parity-emu3-oauth.js +39 -0
  215. package/dist/parity-emu3-oauth.js.map +1 -0
  216. package/dist/parity-emu4-actions.d.ts +2 -0
  217. package/dist/parity-emu4-actions.js +38 -0
  218. package/dist/parity-emu4-actions.js.map +1 -0
  219. package/dist/parity-emu4-authz.d.ts +3 -0
  220. package/dist/parity-emu4-authz.js +68 -0
  221. package/dist/parity-emu4-authz.js.map +1 -0
  222. package/dist/parity-emu4-billing.d.ts +6 -0
  223. package/dist/parity-emu4-billing.js +44 -0
  224. package/dist/parity-emu4-billing.js.map +1 -0
  225. package/dist/parity-emu4-compliance.d.ts +2 -0
  226. package/dist/parity-emu4-compliance.js +92 -0
  227. package/dist/parity-emu4-compliance.js.map +1 -0
  228. package/dist/parity-emu4-federation.d.ts +2 -0
  229. package/dist/parity-emu4-federation.js +33 -0
  230. package/dist/parity-emu4-federation.js.map +1 -0
  231. package/dist/parity-emu4-messaging.d.ts +3 -0
  232. package/dist/parity-emu4-messaging.js +42 -0
  233. package/dist/parity-emu4-messaging.js.map +1 -0
  234. package/dist/parity-emu4-oauth.d.ts +2 -0
  235. package/dist/parity-emu4-oauth.js +55 -0
  236. package/dist/parity-emu4-oauth.js.map +1 -0
  237. package/dist/parity-emu4-trust.d.ts +2 -0
  238. package/dist/parity-emu4-trust.js +41 -0
  239. package/dist/parity-emu4-trust.js.map +1 -0
  240. package/dist/parity-routes.js +19 -1
  241. package/dist/parity-routes.js.map +1 -1
  242. package/dist/sns-cert.d.ts +9 -0
  243. package/dist/sns-cert.js +101 -0
  244. package/dist/sns-cert.js.map +1 -0
  245. package/dist/tokens.d.ts +69 -4
  246. package/dist/tokens.js +164 -38
  247. package/dist/tokens.js.map +1 -1
  248. package/package.json +2 -2
@@ -1,10 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.splitScope = exports.fail = exports.resetOAuth = exports.st = void 0;
3
+ exports.userClaims = exports.splitScope = exports.fail = exports.resetOAuth = exports.st = void 0;
4
4
  exports.discoveryDocument = discoveryDocument;
5
5
  exports.authenticateClient = authenticateClient;
6
6
  exports.mintAccess = mintAccess;
7
7
  exports.issueTokens = issueTokens;
8
+ exports.idTokenFor = idTokenFor;
9
+ exports.revokeGrant = revokeGrant;
10
+ exports.checkResourceToken = checkResourceToken;
8
11
  exports.issueCode = issueCode;
9
12
  exports.oauthHandlers = oauthHandlers;
10
13
  const crypto_util_1 = require("../crypto-util");
@@ -13,7 +16,10 @@ const openapi_1 = require("../openapi");
13
16
  const store_1 = require("../store");
14
17
  const jws_1 = require("../jws");
15
18
  const oauth_flows_1 = require("./oauth-flows");
19
+ const oauth_adv_1 = require("./oauth-adv");
16
20
  const util_1 = require("./util");
21
+ const emu4_oauth_auth_1 = require("./emu4-oauth-auth");
22
+ const emu4_oauth_pub_1 = require("./emu4-oauth-pub");
17
23
  /**
18
24
  * The OAuth 2.0 / OpenID Connect provider: client registry (`/v1/oauth_clients`), discovery, authorize (code + PKCE
19
25
  * S256), token (authorization_code, refresh_token with rotation and reuse detection, client_credentials, token
@@ -51,20 +57,39 @@ function discoveryDocument(e) {
51
57
  userinfo_endpoint: `${iss}/oauth2/userinfo`,
52
58
  revocation_endpoint: `${iss}/oauth2/revoke`,
53
59
  introspection_endpoint: `${iss}/oauth2/introspect`,
54
- response_types_supported: ['code'],
55
- response_modes_supported: ['query'],
56
- grant_types_supported: ['authorization_code', 'refresh_token', 'client_credentials', EXCHANGE, 'urn:ietf:params:oauth:grant-type:device_code', 'urn:openid:params:grant-type:ciba'],
60
+ end_session_endpoint: `${iss}/oauth2/logout`,
61
+ backchannel_logout_supported: true,
62
+ backchannel_logout_session_supported: true,
63
+ frontchannel_logout_supported: true,
64
+ frontchannel_logout_session_supported: true,
65
+ grant_management_endpoint: `${iss}/oauth2/grants`,
66
+ grant_management_actions_supported: ['create', 'merge', 'replace'],
67
+ grant_management_action_required: false,
68
+ ...((0, oauth_adv_1.adv)(e).cfg.dynamic_registration ? { registration_endpoint: `${iss}/oauth2/register` } : {}),
69
+ response_types_supported: [...oauth_adv_1.RESPONSE_TYPES_SUPPORTED],
70
+ response_modes_supported: ['query', 'fragment', ...emu4_oauth_auth_1.EXT_RESPONSE_MODES],
71
+ authorization_signing_alg_values_supported: [...emu4_oauth_auth_1.OP_SIGNING_ALGS],
72
+ request_object_encryption_alg_values_supported: ['RSA-OAEP-256', 'ECDH-ES'],
73
+ request_object_encryption_enc_values_supported: ['A128GCM', 'A256GCM', 'A128CBC-HS256', 'A256CBC-HS512'],
74
+ request_parameter_supported: true,
75
+ request_uri_parameter_supported: true,
76
+ require_request_uri_registration: true,
77
+ request_object_signing_alg_values_supported: [...oauth_adv_1.REQUEST_OBJECT_ALGS],
78
+ authorization_details_types_supported: (0, oauth_adv_1.adv)(e).cfg.authorization_details_types,
79
+ grant_types_supported: ['authorization_code', 'implicit', 'refresh_token', 'client_credentials', EXCHANGE, 'urn:ietf:params:oauth:grant-type:device_code', 'urn:openid:params:grant-type:ciba', ...((0, oauth_adv_1.umaEnabled)(e) ? [oauth_adv_1.UMA_GRANT_TYPE] : [])],
57
80
  subject_types_supported: ['public'],
58
- id_token_signing_alg_values_supported: ['RS256', 'ES256'],
59
- token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'none'],
81
+ id_token_signing_alg_values_supported: [...emu4_oauth_auth_1.OP_SIGNING_ALGS],
82
+ token_endpoint_auth_methods_supported: ['client_secret_basic', 'client_secret_post', 'private_key_jwt', 'none'],
83
+ token_endpoint_auth_signing_alg_values_supported: [...oauth_adv_1.REQUEST_OBJECT_ALGS],
60
84
  code_challenge_methods_supported: ['S256'],
61
85
  dpop_signing_alg_values_supported: ['ES256', 'ES384', 'RS256', 'PS256', 'EdDSA'],
62
86
  tls_client_certificate_bound_access_tokens: true,
63
87
  pushed_authorization_request_endpoint: `${iss}/oauth2/par`,
64
88
  device_authorization_endpoint: `${iss}/oauth2/device_authorization`,
65
89
  backchannel_authentication_endpoint: `${iss}/oauth2/backchannel_authentication`,
66
- backchannel_token_delivery_modes_supported: ['poll', 'ping'],
67
- scopes_supported: SCOPES,
90
+ backchannel_token_delivery_modes_supported: ['poll', 'ping', 'push'],
91
+ backchannel_user_code_parameter_supported: false,
92
+ scopes_supported: [...SCOPES, ...oauth_adv_1.GRANT_MANAGEMENT_SCOPES],
68
93
  claims_supported: ['sub', 'iss', 'aud', 'exp', 'iat', 'auth_time', 'nonce', 'email', 'email_verified', 'name', 'picture'],
69
94
  authorization_response_iss_parameter_supported: true,
70
95
  atlas_emulator: true,
@@ -72,7 +97,7 @@ function discoveryDocument(e) {
72
97
  }
73
98
  const FLOW_FLAGS = ['require_consent', 'dpop_bound_access_tokens', 'tls_client_certificate_bound_access_tokens', 'require_pushed_authorization_requests'];
74
99
  /** The client settings the consent, binding, PAR and CIBA flows read; validated here so a bad value never reaches a flow. */
75
- function pickFlowFields(body) {
100
+ function pickFlowFields(body, existing) {
76
101
  const out = {};
77
102
  for (const k of FLOW_FLAGS) {
78
103
  if (body[k] === undefined)
@@ -82,12 +107,25 @@ function pickFlowFields(body) {
82
107
  out[k] = body[k];
83
108
  }
84
109
  if (body.backchannel_token_delivery_mode !== undefined) {
85
- if (!['poll', 'ping'].includes(body.backchannel_token_delivery_mode))
86
- throw new errors_1.EmulatorError('VALIDATION_FAILED', 'backchannel_token_delivery_mode must be poll or ping.', 'backchannel_token_delivery_mode');
110
+ if (!['poll', 'ping', 'push'].includes(body.backchannel_token_delivery_mode))
111
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', 'backchannel_token_delivery_mode must be poll, ping or push.', 'backchannel_token_delivery_mode');
87
112
  out.backchannel_token_delivery_mode = body.backchannel_token_delivery_mode;
88
113
  }
89
114
  if (body.backchannel_client_notification_endpoint !== undefined)
90
115
  out.backchannel_client_notification_endpoint = body.backchannel_client_notification_endpoint;
116
+ if ((out.backchannel_token_delivery_mode ?? existing?.backchannel_token_delivery_mode) !== undefined && ['ping', 'push'].includes(out.backchannel_token_delivery_mode ?? existing?.backchannel_token_delivery_mode) && !(out.backchannel_client_notification_endpoint ?? existing?.backchannel_client_notification_endpoint)) {
117
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', 'ping and push delivery need backchannel_client_notification_endpoint.', 'backchannel_client_notification_endpoint');
118
+ }
119
+ // Key, request-object and response-type metadata, validated like the real registry.
120
+ const meta = (0, oauth_adv_1.parseClientMeta)(body);
121
+ if (!meta.ok)
122
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', meta.message);
123
+ Object.assign(out, meta.value);
124
+ for (const k of ['backchannel_logout_uri', 'frontchannel_logout_uri'])
125
+ if (body[k] !== undefined)
126
+ out[k] = body[k];
127
+ if (body.claims_redirect_uris !== undefined)
128
+ out.claims_redirect_uris = body.claims_redirect_uris;
91
129
  return out;
92
130
  }
93
131
  const clientShape = (e, c, secret) => {
@@ -111,6 +149,16 @@ const clientShape = (e, c, secret) => {
111
149
  require_pushed_authorization_requests: c.require_pushed_authorization_requests === true,
112
150
  backchannel_token_delivery_mode: c.backchannel_token_delivery_mode ?? 'poll',
113
151
  backchannel_client_notification_endpoint: c.backchannel_client_notification_endpoint ?? null,
152
+ response_types: (0, oauth_adv_1.clientResponseTypes)(c),
153
+ require_signed_request_object: c.require_signed_request_object === true,
154
+ ...(0, emu4_oauth_auth_1.clientExtras)(c),
155
+ request_object_signing_alg: c.request_object_signing_alg ?? null,
156
+ request_uris: c.request_uris ?? [],
157
+ jwks: c.jwks ?? null,
158
+ jwks_uri: c.jwks_uri ?? null,
159
+ backchannel_logout_uri: c.backchannel_logout_uri ?? null,
160
+ frontchannel_logout_uri: c.frontchannel_logout_uri ?? null,
161
+ claims_redirect_uris: c.claims_redirect_uris ?? [],
114
162
  created_at: c.created_at,
115
163
  updated_at: c.updated_at,
116
164
  ...(secret ? { client_secret: secret } : {}),
@@ -122,8 +170,8 @@ const liveClient = (e, id) => {
122
170
  throw (0, errors_1.notFound)('OAuth client');
123
171
  return c;
124
172
  };
125
- /** Client authentication: Basic, or client_id (+ client_secret) in the body. `none` clients are identified only. */
126
- function authenticateClient(e, c) {
173
+ /** Client authentication with the method the client is REGISTERED for: Basic, `client_id` (+ `client_secret`) in the body, a signed `client_assertion` (private_key_jwt), or nothing for a public client. */
174
+ async function authenticateClient(e, c) {
127
175
  let id = typeof c.body.client_id === 'string' ? c.body.client_id : undefined;
128
176
  let secret = typeof c.body.client_secret === 'string' ? c.body.client_secret : undefined;
129
177
  const basic = /^Basic\s+(.+)$/i.exec(c.request.headers.get('authorization') ?? '')?.[1];
@@ -132,10 +180,18 @@ function authenticateClient(e, c) {
132
180
  id = decodeURIComponent(i ?? '');
133
181
  secret = decodeURIComponent(rest.join(':'));
134
182
  }
183
+ // A client assertion names the client itself when no client_id was sent.
184
+ id ??= (0, emu4_oauth_auth_1.assertionClientId)(c.body);
135
185
  const client = e.db.oauthClients.get(id);
136
186
  if (!client || client.deleted)
137
187
  return { error: (0, exports.fail)(401, 'invalid_client', 'Unknown client.') };
188
+ if (client.auth_method === 'private_key_jwt') {
189
+ const problem = secret !== undefined ? 'This client must authenticate with a private_key_jwt client assertion.' : await (0, emu4_oauth_auth_1.verifyClientAssertion)(e, c.body, client);
190
+ return problem ? { error: (0, exports.fail)(401, 'invalid_client', problem) } : { client };
191
+ }
138
192
  const previousOk = client.previous_secret !== undefined && client.previous_secret === secret && e.clock.now() < client.previous_until;
193
+ if (client.auth_method !== 'none' && client.secret_expires_at && e.clock.now() >= client.secret_expires_at)
194
+ return { error: (0, exports.fail)(401, 'invalid_client', 'Client authentication failed.') };
139
195
  if (client.auth_method !== 'none' && client.secret !== secret && !previousOk)
140
196
  return { error: (0, exports.fail)(401, 'invalid_client', 'Client authentication failed.') };
141
197
  return { client };
@@ -143,9 +199,10 @@ function authenticateClient(e, c) {
143
199
  const splitScope = (s) => (typeof s === 'string' ? s.split(/\s+/).filter(Boolean) : []);
144
200
  exports.splitScope = splitScope;
145
201
  async function mintAccess(e, input) {
202
+ const ttl = input.ttl ?? ACCESS_TTL_S;
146
203
  const jti = e.db.newId('oat');
147
204
  const token = await e.tokens.signClaims({
148
- sub: input.userId ?? input.client.id,
205
+ sub: input.userId ? await (0, emu4_oauth_auth_1.subjectFor)(e, input.client, input.userId) : input.client.id,
149
206
  aud: input.audience ?? input.client.id,
150
207
  client_id: input.client.id,
151
208
  scope: input.scope.join(' '),
@@ -153,9 +210,9 @@ async function mintAccess(e, input) {
153
210
  token_use: 'access_token',
154
211
  ...(input.cnf ? { cnf: input.cnf } : {}),
155
212
  ...(input.extra ?? {}),
156
- }, ACCESS_TTL_S);
213
+ }, ttl);
157
214
  const s = (0, exports.st)(e);
158
- s.access.set(jti, { clientId: input.client.id, userId: input.userId, scope: input.scope, exp: e.clock.now() + ACCESS_TTL_S * 1000, revoked: false, grantId: input.grant?.id ?? null, cnf: input.cnf });
215
+ s.access.set(jti, { clientId: input.client.id, userId: input.userId, scope: input.scope, exp: e.clock.now() + ttl * 1000, revoked: false, grantId: input.grant?.id ?? null, cnf: input.cnf });
159
216
  input.grant?.jtis.add(jti);
160
217
  return { token, jti };
161
218
  }
@@ -169,9 +226,14 @@ const userClaims = (e, userId, scope) => {
169
226
  ...(scope.includes('profile') ? { name: [u.first_name, u.last_name].filter(Boolean).join(' ') || undefined, picture: u.image_url ?? undefined, given_name: u.first_name ?? undefined, family_name: u.last_name ?? undefined } : {}),
170
227
  };
171
228
  };
229
+ exports.userClaims = userClaims;
172
230
  async function issueTokens(e, client, grant, nonce, authTime, refreshRec, cnf) {
173
- const access = await mintAccess(e, { client, userId: grant.userId, scope: grant.scope, grant, cnf });
231
+ const access = await mintAccess(e, { client, userId: grant.userId, scope: grant.scope, grant, cnf, extra: grant.managedId ? { grant_id: grant.managedId } : undefined });
174
232
  const out = { access_token: access.token, token_type: cnf?.jkt ? 'DPoP' : 'Bearer', expires_in: ACCESS_TTL_S, scope: grant.scope.join(' ') };
233
+ if (grant.managedId)
234
+ out.grant_id = grant.managedId;
235
+ if (grant.details?.length)
236
+ out.authorization_details = grant.details;
175
237
  if (grant.scope.includes('offline_access') || client.grant_types.includes('refresh_token')) {
176
238
  const rt = refreshRec ?? `ort_${(0, store_1.randomToken)(32)}`;
177
239
  if (!(0, exports.st)(e).refresh.has(rt))
@@ -179,17 +241,55 @@ async function issueTokens(e, client, grant, nonce, authTime, refreshRec, cnf) {
179
241
  out.refresh_token = rt;
180
242
  }
181
243
  if (grant.scope.includes('openid') && grant.userId) {
182
- out.id_token = await e.tokens.signClaims({ sub: grant.userId, aud: client.id, azp: client.id, auth_time: Math.floor(authTime / 1000), ...(nonce ? { nonce } : {}), token_use: 'id_token', ...userClaims(e, grant.userId, grant.scope) }, ACCESS_TTL_S);
244
+ out.id_token = await idTokenFor(e, client, grant.userId, grant.scope, nonce, authTime, grant.sid);
245
+ (0, oauth_adv_1.recordRp)(e, grant.sid, client.id, grant.userId);
183
246
  }
184
247
  return out;
185
248
  }
249
+ async function idTokenFor(e, client, userId, scope, nonce, authTime, sid, extra = {}) {
250
+ return e.tokens.signClaimsAs(client.id_token_signed_response_alg ?? null, { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, client, userId), aud: client.id, azp: client.id, auth_time: Math.floor(authTime / 1000), ...(nonce ? { nonce } : {}), ...(sid ? { sid } : {}), token_use: 'id_token', ...(0, exports.userClaims)(e, userId, scope), ...extra }, ACCESS_TTL_S);
251
+ }
252
+ /** End a grant and every access token minted from it. */
253
+ function revokeGrant(e, g) {
254
+ g.revoked = true;
255
+ for (const j of g.jtis) {
256
+ const a = (0, exports.st)(e).access.get(j);
257
+ if (a)
258
+ a.revoked = true;
259
+ }
260
+ }
261
+ /** A presented access token for a resource of this provider: signature, revocation and its sender constraint (DPoP proof or client certificate). */
262
+ async function checkResourceToken(c, bearer, scheme) {
263
+ const e = c.engine;
264
+ const v = await e.tokens.verifyAny(bearer);
265
+ const rec = v.ok ? (0, exports.st)(e).access.get(String(v.claims.jti)) : undefined;
266
+ if (!v.ok || v.claims.token_use !== 'access_token' || !rec || rec.revoked || (rec.grantId && (0, exports.st)(e).grants.get(rec.grantId)?.revoked))
267
+ return { error: true, message: 'The access token is invalid or expired.' };
268
+ const bad = { error: true, message: 'A valid DPoP proof for this token is required.' };
269
+ if (rec.cnf?.jkt) {
270
+ const proof = c.request.headers.get('dpop');
271
+ if (scheme.toLowerCase() !== 'dpop' || !proof)
272
+ return bad;
273
+ try {
274
+ const r = await (0, jws_1.verifyDpopProof)({ proof, htm: c.request.method, htu: c.request.url, nowMs: e.clock.now(), accessToken: bearer });
275
+ if (r.jkt !== rec.cnf.jkt || !(0, oauth_flows_1.claimDpopJti)(e, r.jkt, r.jti, (jws_1.DPOP_IAT_WINDOW_S + 5) * 1000))
276
+ return bad;
277
+ }
278
+ catch {
279
+ return bad;
280
+ }
281
+ }
282
+ else if (rec.cnf?.['x5t#S256'] && (await (0, jws_1.forwardedCertThumbprint)(c.request.headers)) !== rec.cnf['x5t#S256'])
283
+ return { error: true, message: 'The token is bound to a client certificate that was not presented.' };
284
+ return { rec };
285
+ }
186
286
  /** Create the grant and the single-use authorization code for an approved request (after login and, when needed, consent). */
187
287
  function issueCode(e, a) {
188
- const grant = { id: e.db.newId('oag'), clientId: a.client.id, userId: a.userId, scope: a.scope, revoked: false, jtis: new Set() };
288
+ const grant = { id: e.db.newId('oag'), clientId: a.client.id, userId: a.userId, scope: a.scope, revoked: false, jtis: new Set(), sid: (0, oauth_adv_1.sidFor)(e, a.userId), details: a.ext?.details ?? null };
189
289
  (0, exports.st)(e).grants.set(grant.id, grant);
190
290
  const code = `oc_${(0, store_1.randomToken)(24)}`;
191
291
  const now = e.clock.now();
192
- (0, exports.st)(e).codes.set(code, { grantId: grant.id, redirectUri: a.redirectUri, challenge: a.challenge, nonce: a.nonce, authTime: now, expiresAt: now + CODE_TTL_MS, used: false });
292
+ (0, exports.st)(e).codes.set(code, { grantId: grant.id, redirectUri: a.redirectUri, challenge: a.challenge, nonce: a.nonce, authTime: now, expiresAt: now + CODE_TTL_MS, used: false, ext: a.ext });
193
293
  return code;
194
294
  }
195
295
  /** What one token request proved: a DPoP key (single-use proof) and, for clients that ask for it, a client certificate. */
@@ -221,27 +321,32 @@ async function provedOf(c, client) {
221
321
  }
222
322
  const cnfFor = (p) => (p.jkt || p.x5t ? { ...(p.jkt ? { jkt: p.jkt } : {}), ...(p.x5t ? { 'x5t#S256': p.x5t } : {}) } : undefined);
223
323
  function oauthHandlers() {
224
- const redirect = (uri, params) => {
225
- const u = new URL(uri);
226
- for (const [k, v] of Object.entries(params))
227
- if (v !== undefined)
228
- u.searchParams.set(k, v);
229
- return { status: 302, headers: { location: u.toString(), 'cache-control': 'no-store' } };
230
- };
231
324
  return [
232
325
  // ── client registry ──
233
326
  {
234
327
  method: 'POST',
235
328
  pattern: '/v1/oauth_clients',
236
- handler: (c) => {
329
+ handler: async (c) => {
237
330
  const e = c.engine;
238
331
  const name = String((0, util_1.need)(c.body, 'name'));
239
332
  const uris = c.body.redirect_uris;
240
333
  if (!Array.isArray(uris) || !uris.length || !uris.every((u) => typeof u === 'string'))
241
334
  throw new errors_1.EmulatorError('VALIDATION_FAILED', 'redirect_uris needs at least one URI.', 'redirect_uris');
242
335
  const method = c.body.token_endpoint_auth_method ?? 'client_secret_basic';
336
+ if (!emu4_oauth_auth_1.AUTH_METHODS.includes(method))
337
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', `token_endpoint_auth_method must be one of: ${emu4_oauth_auth_1.AUTH_METHODS.join(', ')}.`, 'token_endpoint_auth_method');
338
+ const flowFields = pickFlowFields(c.body);
339
+ const keyedProblem = (0, emu4_oauth_auth_1.keyedClientProblem)(method, flowFields.jwks, flowFields.jwks_uri);
340
+ if (keyedProblem)
341
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', keyedProblem, 'jwks');
342
+ const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: c.body.subject_type, sector_identifier_uri: c.body.sector_identifier_uri, redirect_uris: uris, redirectUrisChanged: true });
343
+ if (!subject.ok)
344
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', subject.message, subject.param);
345
+ const rtProblem = (0, oauth_adv_1.responseTypeProblem)((0, oauth_adv_1.clientResponseTypes)(c.body), c.body.grant_types ?? ['authorization_code', 'refresh_token']);
346
+ if (rtProblem)
347
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', rtProblem, 'response_types');
243
348
  const now = e.clock.now();
244
- const secret = method === 'none' ? undefined : `oacs_${(0, store_1.randomToken)(32)}`;
349
+ const secret = method === 'none' || method === 'private_key_jwt' ? undefined : `oacs_${(0, store_1.randomToken)(32)}`;
245
350
  const row = e.db.oauthClients.insert({
246
351
  name,
247
352
  redirect_uris: uris,
@@ -250,7 +355,8 @@ function oauthHandlers() {
250
355
  auth_method: method,
251
356
  first_party: c.body.first_party === true,
252
357
  post_logout_redirect_uris: c.body.post_logout_redirect_uris ?? [],
253
- ...pickFlowFields(c.body),
358
+ ...flowFields,
359
+ ...subject.value,
254
360
  secret,
255
361
  created_at: now,
256
362
  updated_at: now,
@@ -264,13 +370,27 @@ function oauthHandlers() {
264
370
  {
265
371
  method: 'PATCH',
266
372
  pattern: '/v1/oauth_clients/:id',
267
- handler: (c) => {
373
+ handler: async (c) => {
268
374
  const e = c.engine;
269
375
  const row = liveClient(e, c.params.id);
270
- for (const [k, to] of [['name', 'name'], ['redirect_uris', 'redirect_uris'], ['allowed_scopes', 'allowed_scopes'], ['grant_types', 'grant_types'], ['first_party', 'first_party'], ['post_logout_redirect_uris', 'post_logout_redirect_uris']])
376
+ const next = { ...row };
377
+ for (const k of ['name', 'redirect_uris', 'allowed_scopes', 'grant_types', 'first_party', 'post_logout_redirect_uris'])
271
378
  if (k in c.body)
272
- row[to] = c.body[k];
273
- Object.assign(row, pickFlowFields(c.body));
379
+ next[k] = c.body[k];
380
+ Object.assign(next, pickFlowFields(c.body, row));
381
+ const keyedProblem = (0, emu4_oauth_auth_1.keyedClientProblem)(next.auth_method, next.jwks, next.jwks_uri);
382
+ if (keyedProblem)
383
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', keyedProblem, 'jwks');
384
+ if (c.body.subject_type !== undefined || c.body.sector_identifier_uri !== undefined || c.body.redirect_uris !== undefined) {
385
+ const subject = await (0, emu4_oauth_auth_1.resolveSubject)(e, { subject_type: c.body.subject_type, sector_identifier_uri: c.body.sector_identifier_uri, redirect_uris: next.redirect_uris, redirectUrisChanged: c.body.redirect_uris !== undefined, existing: row, publisherSector: (0, emu4_oauth_pub_1.publisherSectorOf)(e, row) });
386
+ if (!subject.ok)
387
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', subject.message, subject.param);
388
+ Object.assign(next, subject.value);
389
+ }
390
+ const rtProblem = (0, oauth_adv_1.responseTypeProblem)((0, oauth_adv_1.clientResponseTypes)(next), next.grant_types);
391
+ if (rtProblem)
392
+ throw new errors_1.EmulatorError('VALIDATION_FAILED', rtProblem, 'response_types');
393
+ Object.assign(row, next); // validated first: a refused PATCH changes nothing
274
394
  row.updated_at = e.clock.now();
275
395
  e.changed({ type: 'oauth_client.updated', data: clientShape(e, row) });
276
396
  return { body: clientShape(e, row) };
@@ -322,33 +442,76 @@ function oauthHandlers() {
322
442
  method: 'GET',
323
443
  pattern: '/oauth2/authorize',
324
444
  public: true,
325
- handler: (c) => {
445
+ handler: async (c) => {
326
446
  const e = c.engine;
327
- const resolved = (0, oauth_flows_1.resolveRequestUri)(e, c.query);
447
+ const rawUri = c.query.get('request_uri');
448
+ // An https request_uri names a request object to fetch (RFC 9101); anything else is a pushed request (RFC 9126).
449
+ const jarUri = rawUri && /^https:\/\//i.test(rawUri) ? rawUri : null;
450
+ const resolved = jarUri ? { query: c.query } : (0, oauth_flows_1.resolveRequestUri)(e, c.query);
328
451
  if ('error' in resolved)
329
452
  return (0, exports.fail)(400, resolved.error.code, resolved.error.description);
453
+ const parUsed = !!rawUri && !jarUri;
330
454
  const q = resolved.query;
331
455
  const client = e.db.oauthClients.get(q.get('client_id') ?? undefined);
332
456
  if (!client || client.deleted)
333
457
  return (0, exports.fail)(400, 'invalid_request', 'Unknown client_id.');
334
- const redirectUri = q.get('redirect_uri') ?? (client.redirect_uris.length === 1 ? client.redirect_uris[0] : null);
458
+ if (client.require_pushed_authorization_requests && !parUsed)
459
+ return (0, exports.fail)(400, 'invalid_request', 'This client must push its authorization request (POST /oauth2/par) and send the request_uri.');
460
+ const p = Object.fromEntries(q.entries());
461
+ let rarRaw = p.authorization_details;
462
+ if (client.require_signed_request_object === true && !parUsed && !p.request && !jarUri)
463
+ return (0, exports.fail)(400, 'invalid_request', 'This client must send a signed request object.');
464
+ // A signed request object supersedes the query parameters; it is verified BEFORE the redirect_uri is trusted.
465
+ if (p.request || jarUri) {
466
+ const loaded = await (0, oauth_adv_1.loadRequestObject)(e, client, { request: p.request, requestUri: jarUri ?? undefined });
467
+ if (!loaded.ok)
468
+ return (0, exports.fail)(400, loaded.error, loaded.description);
469
+ for (const k of oauth_adv_1.REQUEST_OBJECT_PARAMS)
470
+ if (typeof loaded.claims[k] === 'string')
471
+ p[k] = loaded.claims[k];
472
+ if (loaded.claims.authorization_details !== undefined)
473
+ rarRaw = loaded.claims.authorization_details;
474
+ delete p.request;
475
+ }
476
+ const redirectUri = p.redirect_uri ?? (client.redirect_uris.length === 1 ? client.redirect_uris[0] : null);
335
477
  if (!redirectUri || !client.redirect_uris.includes(redirectUri))
336
478
  return (0, exports.fail)(400, 'invalid_request', 'redirect_uri is not registered for this client.');
337
- if (client.require_pushed_authorization_requests && !c.query.get('request_uri'))
338
- return (0, exports.fail)(400, 'invalid_request', 'This client must push its authorization request (POST /oauth2/par) and send the request_uri.');
339
- const state = q.get('state') ?? undefined;
340
- const back = (params) => redirect(redirectUri, { ...params, state, iss: e.issuer });
341
- if (q.get('response_type') !== 'code')
342
- return back({ error: 'unsupported_response_type', error_description: 'Only response_type=code is supported.' });
343
- const scope = (0, exports.splitScope)(q.get('scope') ?? 'openid');
479
+ const state = p.state;
480
+ const rt = (0, oauth_adv_1.parseResponseType)(p.response_type);
481
+ const mode = p.response_mode ?? null;
482
+ const back = (params) => (0, emu4_oauth_auth_1.authReply)(e, client, redirectUri, (0, oauth_adv_1.authUrl)(e, redirectUri, { ...params, state, iss: e.issuer }, !!rt?.frontChannelTokens, mode), mode);
483
+ if (!rt)
484
+ return back({ error: 'unsupported_response_type', error_description: 'The response_type is not supported.' });
485
+ if (!(0, oauth_adv_1.clientResponseTypes)(client).includes(rt.canonical))
486
+ return back({ error: 'unauthorized_client', error_description: 'This client is not registered for this response_type.' });
487
+ if (mode && !['query', 'fragment', ...emu4_oauth_auth_1.EXT_RESPONSE_MODES].includes(mode))
488
+ return back({ error: 'invalid_request', error_description: 'The response_mode is not supported.' });
489
+ if (mode && !(0, oauth_adv_1.responseModeAllowed)(rt, mode))
490
+ return back({ error: 'invalid_request', error_description: 'A response_type that returns tokens cannot use the query response mode.' });
491
+ // OIDC Core 3.2.2.1 / 3.3.2.1: the nonce is required whenever an ID token comes back from the authorization endpoint.
492
+ if (rt.idToken && !p.nonce)
493
+ return back({ error: 'invalid_request', error_description: 'The nonce parameter is required for this response_type.' });
494
+ const gmParsed = (0, oauth_adv_1.parseGrantManagement)(p.grant_management_action, p.grant_id);
495
+ if (!gmParsed.ok)
496
+ return back({ error: 'invalid_request', error_description: gmParsed.message });
497
+ const gm = gmParsed.value;
498
+ if (rt.code && !client.grant_types.includes('authorization_code'))
499
+ return back({ error: 'unauthorized_client', error_description: 'This client is not permitted the authorization_code grant.' });
500
+ if (rt.frontChannelTokens && !client.grant_types.includes('implicit'))
501
+ return back({ error: 'unauthorized_client', error_description: 'This client is not permitted the implicit grant.' });
502
+ const scope = (0, exports.splitScope)(p.scope ?? 'openid');
344
503
  const outside = scope.filter((s) => !client.allowed_scopes.includes(s) && !SCOPES.includes(s));
345
504
  if (outside.length)
346
505
  return back({ error: 'invalid_scope', error_description: `Scope not allowed: ${outside.join(' ')}.` });
347
- const challenge = q.get('code_challenge');
348
- if (challenge && (q.get('code_challenge_method') ?? 'plain') !== 'S256')
506
+ const challenge = p.code_challenge ?? null;
507
+ if (challenge && (p.code_challenge_method ?? 'plain') !== 'S256')
349
508
  return back({ error: 'invalid_request', error_description: 'code_challenge_method must be S256.' });
350
- if (!challenge && client.auth_method === 'none')
509
+ if (!challenge && rt.code && client.auth_method === 'none')
351
510
  return back({ error: 'invalid_request', error_description: 'A public client must use PKCE (code_challenge).' });
511
+ // RFC 9396: authorization_details against the instance's type allowlist (an empty allowlist refuses any type).
512
+ const rar = (0, oauth_adv_1.validateAuthorizationDetails)(rarRaw, (0, oauth_adv_1.adv)(e).cfg.authorization_details_types);
513
+ if (!rar.ok)
514
+ return back({ error: 'invalid_authorization_details', error_description: rar.message });
352
515
  if (q.get('emulator_decision') === 'deny')
353
516
  return back({ error: 'access_denied', error_description: 'The user denied the request.' });
354
517
  const hint = q.get('login_hint');
@@ -361,10 +524,17 @@ function oauthHandlers() {
361
524
  return back({ error: 'login_required', error_description: 'No user to sign in. Pass login_hint or POST /__emulator/oauth/login.' });
362
525
  if (user.banned)
363
526
  return back({ error: 'access_denied', error_description: 'The user is banned.' });
364
- const gate = (0, oauth_flows_1.consentGate)(e, { client, user, scope, redirectUri, challenge, nonce: q.get('nonce'), state, prompt: q.get('prompt') });
527
+ const details = rar.value ?? null;
528
+ // Grant management: a merge or replace names this client's and this user's active grant; adding anything to it asks the user again.
529
+ const gmProblem = (0, oauth_adv_1.gmValid)(e, gm, client.id, user.id);
530
+ if (gmProblem)
531
+ return back({ error: 'invalid_grant', error_description: gmProblem });
532
+ const widens = !!gm && gm.action !== 'create' && (0, oauth_adv_1.gmWidens)(gm, (0, oauth_adv_1.managedFor)(e, gm.grantId, client.id, user.id), scope, details);
533
+ const ext = { rt: rt.canonical, mode, details, gm };
534
+ const gate = await (0, oauth_flows_1.consentGate)(e, { client, user, scope, redirectUri, challenge, nonce: p.nonce ?? null, state, prompt: q.get('prompt'), ext, force: widens });
365
535
  if (gate)
366
536
  return gate;
367
- return back({ code: issueCode(e, { client, userId: user.id, scope, redirectUri, challenge, nonce: q.get('nonce') }) });
537
+ return (0, emu4_oauth_auth_1.authReply)(e, client, redirectUri, await (0, oauth_adv_1.completeAuth)(e, { client, userId: user.id, scope, redirectUri, challenge, nonce: p.nonce ?? null, state, ext }), mode);
368
538
  },
369
539
  },
370
540
  { method: 'POST', pattern: '/__emulator/oauth/login', public: true, handler: (c) => (((0, exports.st)(c.engine).autoUser = typeof c.body.user_id === 'string' ? c.body.user_id : null), { body: { ok: true } }) },
@@ -375,14 +545,15 @@ function oauthHandlers() {
375
545
  public: true,
376
546
  handler: async (c) => {
377
547
  const e = c.engine;
378
- const auth = authenticateClient(e, c);
548
+ const auth = await authenticateClient(e, c);
379
549
  if ('error' in auth)
380
550
  return auth.error;
381
551
  const client = auth.client;
382
552
  const type = String(c.body.grant_type ?? '');
383
553
  if (!type)
384
554
  return (0, exports.fail)(400, 'invalid_request', 'grant_type is required.');
385
- if (type !== EXCHANGE && !client.grant_types.includes(type) && !(type === 'refresh_token'))
555
+ const umaPat = type === 'client_credentials' && !c.body.audience && (0, exports.splitScope)(c.body.scope).includes('uma_protection') && (0, oauth_adv_1.umaEnabled)(e);
556
+ if (type !== EXCHANGE && type !== oauth_adv_1.UMA_GRANT_TYPE && !umaPat && !client.grant_types.includes(type) && !(type === 'refresh_token'))
386
557
  return (0, exports.fail)(400, 'unauthorized_client', `This client may not use ${type}.`);
387
558
  const s = (0, exports.st)(e);
388
559
  const now = e.clock.now();
@@ -393,6 +564,10 @@ function oauthHandlers() {
393
564
  const ext = await (0, oauth_flows_1.extensionGrant)(c, client, type, cnf);
394
565
  if (ext)
395
566
  return ext;
567
+ if (umaPat)
568
+ return client.auth_method === 'none' ? (0, exports.fail)(401, 'invalid_client', 'A protection API token requires a confidential client.') : (0, oauth_adv_1.umaProtectionToken)(c, client);
569
+ if (type === oauth_adv_1.UMA_GRANT_TYPE && (0, oauth_adv_1.umaEnabled)(e))
570
+ return (0, oauth_adv_1.umaTicketGrant)(c, client);
396
571
  if (type === 'authorization_code') {
397
572
  const rec = s.codes.get(String(c.body.code ?? ''));
398
573
  if (!rec)
@@ -422,6 +597,12 @@ function oauthHandlers() {
422
597
  rec.used = true;
423
598
  if (pr.proved.jkt)
424
599
  grant.jkt = pr.proved.jkt;
600
+ // OpenID grant management: apply the create / merge / replace the code carried; the tokens are for the grant as it stands afterwards.
601
+ if (rec.ext?.gm) {
602
+ const applied = (0, oauth_adv_1.applyGrantManagement)(e, grant, rec.ext.gm);
603
+ if (applied)
604
+ return (0, exports.fail)(400, applied.error, applied.message);
605
+ }
425
606
  return { body: await issueTokens(e, client, grant, rec.nonce, rec.authTime, undefined, cnf), headers: { 'cache-control': 'no-store', pragma: 'no-cache' } };
426
607
  }
427
608
  if (type === 'refresh_token') {
@@ -430,7 +611,7 @@ function oauthHandlers() {
430
611
  const grant = rec ? s.grants.get(rec.grantId) : undefined;
431
612
  if (!rec || !grant || grant.clientId !== client.id)
432
613
  return (0, exports.fail)(400, 'invalid_grant', 'The refresh token is invalid.');
433
- if (grant.revoked)
614
+ if (grant.revoked || (grant.managedId && (0, oauth_adv_1.adv)(e).managed.get(grant.managedId)?.status !== 'active'))
434
615
  return (0, exports.fail)(400, 'invalid_grant', 'The grant was revoked.');
435
616
  // A DPoP-bound refresh token is usable only with the key it was bound to (RFC 9449 section 8).
436
617
  if (grant.jkt && pr.proved.jkt !== grant.jkt)
@@ -466,6 +647,8 @@ function oauthHandlers() {
466
647
  const a = await mintAccess(e, { client, userId: null, scope, grant: null, audience: typeof c.body.audience === 'string' ? c.body.audience : undefined, cnf });
467
648
  return { body: { access_token: a.token, token_type: cnf?.jkt ? 'DPoP' : 'Bearer', expires_in: ACCESS_TTL_S, scope: scope.join(' ') }, headers: { 'cache-control': 'no-store' } };
468
649
  }
650
+ if (type === EXCHANGE && c.body.subject_token_type === emu4_oauth_pub_1.GOOGLE_ACCESS_TOKEN_TYPE)
651
+ return (0, emu4_oauth_pub_1.googleTokenExchange)(c, client);
469
652
  if (type === EXCHANGE) {
470
653
  const subject = String(c.body.subject_token ?? '');
471
654
  if (!subject)
@@ -501,9 +684,9 @@ function oauthHandlers() {
501
684
  method: 'POST',
502
685
  pattern: '/oauth2/revoke',
503
686
  public: true,
504
- handler: (c) => {
687
+ handler: async (c) => {
505
688
  const e = c.engine;
506
- const auth = authenticateClient(e, c);
689
+ const auth = await authenticateClient(e, c);
507
690
  if ('error' in auth)
508
691
  return auth.error;
509
692
  const token = String(c.body.token ?? '');
@@ -541,7 +724,7 @@ function oauthHandlers() {
541
724
  public: true,
542
725
  handler: async (c) => {
543
726
  const e = c.engine;
544
- const auth = authenticateClient(e, c);
727
+ const auth = await authenticateClient(e, c);
545
728
  if ('error' in auth)
546
729
  return auth.error;
547
730
  const token = String(c.body.token ?? '');
@@ -552,7 +735,7 @@ function oauthHandlers() {
552
735
  const g = s.grants.get(rt.grantId);
553
736
  if (!g || g.revoked || rt.rotatedAt !== undefined || g.clientId !== auth.client.id)
554
737
  return inactive;
555
- return { body: { active: true, token_use: 'refresh_token', client_id: g.clientId, sub: g.userId, scope: g.scope.join(' ') }, headers: { 'cache-control': 'no-store' } };
738
+ return { body: { active: true, token_use: 'refresh_token', client_id: g.clientId, ...(g.userId ? { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, auth.client, g.userId) } : {}), scope: g.scope.join(' '), ...(g.details?.length ? { authorization_details: g.details } : {}), ...(g.managedId ? { grant_id: g.managedId } : {}) }, headers: { 'cache-control': 'no-store' } };
556
739
  }
557
740
  const v = await e.tokens.verifyAny(token);
558
741
  if (!v.ok || v.claims.token_use !== 'access_token')
@@ -560,9 +743,11 @@ function oauthHandlers() {
560
743
  const rec = s.access.get(String(v.claims.jti));
561
744
  if (!rec || rec.revoked || (rec.grantId && s.grants.get(rec.grantId)?.revoked))
562
745
  return inactive;
563
- if (rec.clientId !== auth.client.id)
564
- return inactive; // another client's token: do not leak
565
- return { body: { active: true, token_type: rec.cnf?.jkt ? 'DPoP' : 'Bearer', ...(rec.cnf ? { cnf: rec.cnf } : {}), token_use: 'access_token', client_id: rec.clientId, sub: v.claims.sub, aud: v.claims.aud, iss: v.claims.iss, scope: rec.scope.join(' '), exp: v.claims.exp, iat: v.claims.iat, jti: v.claims.jti, ...(v.claims.act ? { act: v.claims.act } : {}) }, headers: { 'cache-control': 'no-store' } };
746
+ // another client's token: do not leak (a resource server may introspect the tokens issued for it)
747
+ if (rec.clientId !== auth.client.id && v.claims.uma_rs !== auth.client.id)
748
+ return inactive;
749
+ const granted = rec.grantId ? s.grants.get(rec.grantId) : undefined;
750
+ return { body: { active: true, token_type: rec.cnf?.jkt ? 'DPoP' : 'Bearer', ...(granted?.details?.length ? { authorization_details: granted.details } : {}), ...(v.claims.grant_id ? { grant_id: v.claims.grant_id } : {}), ...(v.claims.permissions ? { permissions: v.claims.permissions, uma_rs: v.claims.uma_rs } : {}), ...(rec.cnf ? { cnf: rec.cnf } : {}), token_use: 'access_token', client_id: rec.clientId, sub: v.claims.sub, aud: v.claims.aud, iss: v.claims.iss, scope: rec.scope.join(' '), exp: v.claims.exp, iat: v.claims.iat, jti: v.claims.jti, ...(v.claims.act ? { act: v.claims.act } : {}) }, headers: { 'cache-control': 'no-store' } };
566
751
  },
567
752
  },
568
753
  ...['GET', 'POST'].map((method) => ({
@@ -600,7 +785,7 @@ function oauthHandlers() {
600
785
  }
601
786
  if (!rec.scope.includes('openid'))
602
787
  return { status: 403, headers: { 'www-authenticate': 'Bearer error="insufficient_scope", scope="openid"' }, body: { error: 'insufficient_scope', error_description: 'The openid scope is required.' } };
603
- return { body: { sub: rec.userId, ...userClaims(e, rec.userId, rec.scope) } };
788
+ return { body: { sub: await (0, emu4_oauth_auth_1.subjectFor)(e, e.db.oauthClients.get(rec.clientId), rec.userId), ...(0, exports.userClaims)(e, rec.userId, rec.scope) } };
604
789
  },
605
790
  })),
606
791
  ];