@udibo/oauth2 0.1.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 (375) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +174 -0
  3. package/SECURITY.md +77 -0
  4. package/esm/_dnt.polyfills.d.ts +17 -0
  5. package/esm/_dnt.polyfills.js +15 -0
  6. package/esm/adapters/hono/_common.d.ts +66 -0
  7. package/esm/adapters/hono/_common.js +108 -0
  8. package/esm/adapters/hono/authorization-server.d.ts +211 -0
  9. package/esm/adapters/hono/authorization-server.js +214 -0
  10. package/esm/adapters/hono/bff/auth-request-store.d.ts +164 -0
  11. package/esm/adapters/hono/bff/auth-request-store.js +154 -0
  12. package/esm/adapters/hono/bff/bff.d.ts +852 -0
  13. package/esm/adapters/hono/bff/bff.js +1194 -0
  14. package/esm/adapters/hono/bff/cookie-prefix.d.ts +72 -0
  15. package/esm/adapters/hono/bff/cookie-prefix.js +85 -0
  16. package/esm/adapters/hono/bff/mod.d.ts +19 -0
  17. package/esm/adapters/hono/bff/mod.js +19 -0
  18. package/esm/adapters/hono/bff/proxy.d.ts +108 -0
  19. package/esm/adapters/hono/bff/proxy.js +314 -0
  20. package/esm/adapters/hono/bff/session-store.d.ts +250 -0
  21. package/esm/adapters/hono/bff/session-store.js +216 -0
  22. package/esm/adapters/hono/bff/testing.d.ts +165 -0
  23. package/esm/adapters/hono/bff/testing.js +254 -0
  24. package/esm/adapters/hono/identity.d.ts +117 -0
  25. package/esm/adapters/hono/identity.js +215 -0
  26. package/esm/adapters/hono/log.d.ts +49 -0
  27. package/esm/adapters/hono/log.js +66 -0
  28. package/esm/adapters/hono/resource-server.d.ts +101 -0
  29. package/esm/adapters/hono/resource-server.js +108 -0
  30. package/esm/client/_http.d.ts +91 -0
  31. package/esm/client/_http.js +303 -0
  32. package/esm/client/base.d.ts +184 -0
  33. package/esm/client/base.js +93 -0
  34. package/esm/client/bff-client.d.ts +235 -0
  35. package/esm/client/bff-client.js +280 -0
  36. package/esm/client/browser-storage.d.ts +152 -0
  37. package/esm/client/browser-storage.js +246 -0
  38. package/esm/client/check.d.ts +114 -0
  39. package/esm/client/check.js +97 -0
  40. package/esm/client/direct-client.d.ts +707 -0
  41. package/esm/client/direct-client.js +1237 -0
  42. package/esm/client/discovery-cache.d.ts +138 -0
  43. package/esm/client/discovery-cache.js +124 -0
  44. package/esm/client/events.d.ts +54 -0
  45. package/esm/client/events.js +37 -0
  46. package/esm/client/mod.d.ts +35 -0
  47. package/esm/client/mod.js +33 -0
  48. package/esm/client/storage.d.ts +127 -0
  49. package/esm/client/storage.js +81 -0
  50. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
  51. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
  52. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
  53. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
  54. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
  55. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
  56. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
  57. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
  58. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
  59. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +193 -0
  60. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
  61. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
  62. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
  63. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
  64. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
  65. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
  66. package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
  67. package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
  68. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
  69. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
  70. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
  71. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
  72. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
  73. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
  74. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
  75. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
  76. package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
  77. package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
  78. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
  79. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
  80. package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
  81. package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
  82. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
  83. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
  84. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
  85. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
  86. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
  87. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
  88. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
  89. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
  90. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
  91. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
  92. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
  93. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
  94. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
  95. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
  96. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
  97. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
  98. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
  99. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
  100. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
  101. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
  102. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
  103. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
  104. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
  105. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
  106. package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +44 -0
  107. package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +84 -0
  108. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +39 -0
  109. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
  110. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +22 -0
  111. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +51 -0
  112. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +34 -0
  113. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +192 -0
  114. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +34 -0
  115. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +113 -0
  116. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +3 -0
  117. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +13 -0
  118. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +8 -0
  119. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +2 -0
  120. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +1 -0
  121. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +26 -0
  122. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +60 -0
  123. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +152 -0
  124. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +39 -0
  125. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +87 -0
  126. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +39 -0
  127. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +131 -0
  128. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +39 -0
  129. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +82 -0
  130. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +39 -0
  131. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +72 -0
  132. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +38 -0
  133. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +87 -0
  134. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +97 -0
  135. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +99 -0
  136. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +119 -0
  137. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +209 -0
  138. package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +343 -0
  139. package/esm/deps/jsr.io/@std/http/1.1.3/status.js +343 -0
  140. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +159 -0
  141. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +223 -0
  142. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
  143. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
  144. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
  145. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
  146. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
  147. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
  148. package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +29 -0
  149. package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +68 -0
  150. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
  151. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +206 -0
  152. package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
  153. package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
  154. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +91 -0
  155. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +347 -0
  156. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +382 -0
  157. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +1110 -0
  158. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +363 -0
  159. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +616 -0
  160. package/esm/errors.d.ts +203 -0
  161. package/esm/errors.js +287 -0
  162. package/esm/identity/captcha.d.ts +147 -0
  163. package/esm/identity/captcha.js +64 -0
  164. package/esm/identity/delivery.d.ts +96 -0
  165. package/esm/identity/delivery.js +41 -0
  166. package/esm/identity/errors.d.ts +39 -0
  167. package/esm/identity/errors.js +46 -0
  168. package/esm/identity/events.d.ts +242 -0
  169. package/esm/identity/events.js +39 -0
  170. package/esm/identity/external/_shared.d.ts +86 -0
  171. package/esm/identity/external/_shared.js +107 -0
  172. package/esm/identity/external/_token-exchange.d.ts +61 -0
  173. package/esm/identity/external/_token-exchange.js +179 -0
  174. package/esm/identity/external/apple-client-secret.d.ts +82 -0
  175. package/esm/identity/external/apple-client-secret.js +133 -0
  176. package/esm/identity/external/apple.d.ts +64 -0
  177. package/esm/identity/external/apple.js +248 -0
  178. package/esm/identity/external/discord.d.ts +38 -0
  179. package/esm/identity/external/discord.js +62 -0
  180. package/esm/identity/external/errors.d.ts +48 -0
  181. package/esm/identity/external/errors.js +36 -0
  182. package/esm/identity/external/flow.d.ts +107 -0
  183. package/esm/identity/external/flow.js +158 -0
  184. package/esm/identity/external/github.d.ts +42 -0
  185. package/esm/identity/external/github.js +164 -0
  186. package/esm/identity/external/google.d.ts +52 -0
  187. package/esm/identity/external/google.js +38 -0
  188. package/esm/identity/external/mod.d.ts +63 -0
  189. package/esm/identity/external/mod.js +61 -0
  190. package/esm/identity/external/oauth2.d.ts +110 -0
  191. package/esm/identity/external/oauth2.js +158 -0
  192. package/esm/identity/external/oidc.d.ts +81 -0
  193. package/esm/identity/external/oidc.js +250 -0
  194. package/esm/identity/external/provider.d.ts +132 -0
  195. package/esm/identity/external/provider.js +12 -0
  196. package/esm/identity/hibp.d.ts +67 -0
  197. package/esm/identity/hibp.js +87 -0
  198. package/esm/identity/identifier.d.ts +44 -0
  199. package/esm/identity/identifier.js +47 -0
  200. package/esm/identity/lockout.d.ts +155 -0
  201. package/esm/identity/lockout.js +117 -0
  202. package/esm/identity/mfa/mod.d.ts +42 -0
  203. package/esm/identity/mfa/mod.js +42 -0
  204. package/esm/identity/mfa/recovery-codes.d.ts +96 -0
  205. package/esm/identity/mfa/recovery-codes.js +80 -0
  206. package/esm/identity/mfa/service.d.ts +338 -0
  207. package/esm/identity/mfa/service.js +351 -0
  208. package/esm/identity/mfa/totp.d.ts +186 -0
  209. package/esm/identity/mfa/totp.js +194 -0
  210. package/esm/identity/migration.d.ts +155 -0
  211. package/esm/identity/migration.js +243 -0
  212. package/esm/identity/mod.d.ts +65 -0
  213. package/esm/identity/mod.js +64 -0
  214. package/esm/identity/otp.d.ts +255 -0
  215. package/esm/identity/otp.js +245 -0
  216. package/esm/identity/password-policy.d.ts +45 -0
  217. package/esm/identity/password-policy.js +60 -0
  218. package/esm/identity/password.d.ts +171 -0
  219. package/esm/identity/password.js +160 -0
  220. package/esm/identity/rate-limit.d.ts +192 -0
  221. package/esm/identity/rate-limit.js +168 -0
  222. package/esm/identity/service.d.ts +579 -0
  223. package/esm/identity/service.js +835 -0
  224. package/esm/identity/session.d.ts +122 -0
  225. package/esm/identity/session.js +48 -0
  226. package/esm/identity/token-flow.d.ts +214 -0
  227. package/esm/identity/token-flow.js +173 -0
  228. package/esm/models/authentication.d.ts +24 -0
  229. package/esm/models/authentication.js +1 -0
  230. package/esm/models/authorization-code.d.ts +63 -0
  231. package/esm/models/authorization-code.js +1 -0
  232. package/esm/models/authorization.d.ts +165 -0
  233. package/esm/models/authorization.js +160 -0
  234. package/esm/models/client.d.ts +78 -0
  235. package/esm/models/client.js +1 -0
  236. package/esm/models/device-authorization.d.ts +30 -0
  237. package/esm/models/device-authorization.js +1 -0
  238. package/esm/models/responses.d.ts +93 -0
  239. package/esm/models/responses.js +1 -0
  240. package/esm/models/scope.d.ts +81 -0
  241. package/esm/models/scope.js +125 -0
  242. package/esm/models/token.d.ts +135 -0
  243. package/esm/models/token.js +1 -0
  244. package/esm/package.json +3 -0
  245. package/esm/react/callback.d.ts +52 -0
  246. package/esm/react/callback.js +71 -0
  247. package/esm/react/components/class-names.d.ts +91 -0
  248. package/esm/react/components/class-names.js +118 -0
  249. package/esm/react/components/internal.d.ts +62 -0
  250. package/esm/react/components/internal.js +102 -0
  251. package/esm/react/components/mfa-challenge-form.d.ts +66 -0
  252. package/esm/react/components/mfa-challenge-form.js +46 -0
  253. package/esm/react/components/mfa-enrollment-form.d.ts +83 -0
  254. package/esm/react/components/mfa-enrollment-form.js +41 -0
  255. package/esm/react/components/mod.d.ts +103 -0
  256. package/esm/react/components/mod.js +93 -0
  257. package/esm/react/components/request-password-reset-form.d.ts +61 -0
  258. package/esm/react/components/request-password-reset-form.js +41 -0
  259. package/esm/react/components/reset-password-form.d.ts +66 -0
  260. package/esm/react/components/reset-password-form.js +56 -0
  261. package/esm/react/components/sign-in-form.d.ts +74 -0
  262. package/esm/react/components/sign-in-form.js +43 -0
  263. package/esm/react/components/sign-up-form.d.ts +85 -0
  264. package/esm/react/components/sign-up-form.js +57 -0
  265. package/esm/react/components/types.d.ts +208 -0
  266. package/esm/react/components/types.js +14 -0
  267. package/esm/react/components/use-auth-form.d.ts +118 -0
  268. package/esm/react/components/use-auth-form.js +172 -0
  269. package/esm/react/components/user-menu.d.ts +79 -0
  270. package/esm/react/components/user-menu.js +73 -0
  271. package/esm/react/context.d.ts +51 -0
  272. package/esm/react/context.js +16 -0
  273. package/esm/react/mod.d.ts +26 -0
  274. package/esm/react/mod.js +26 -0
  275. package/esm/react/provider.d.ts +40 -0
  276. package/esm/react/provider.js +189 -0
  277. package/esm/react/require-auth.d.ts +31 -0
  278. package/esm/react/require-auth.js +37 -0
  279. package/esm/react/testing.d.ts +322 -0
  280. package/esm/react/testing.js +461 -0
  281. package/esm/react/use-authorization.d.ts +31 -0
  282. package/esm/react/use-authorization.js +36 -0
  283. package/esm/react/use-oauth2.d.ts +112 -0
  284. package/esm/react/use-oauth2.js +127 -0
  285. package/esm/server/authorization-server.d.ts +736 -0
  286. package/esm/server/authorization-server.js +1304 -0
  287. package/esm/server/client-authentication.d.ts +28 -0
  288. package/esm/server/client-authentication.js +51 -0
  289. package/esm/server/grants/authorization-code.d.ts +179 -0
  290. package/esm/server/grants/authorization-code.js +232 -0
  291. package/esm/server/grants/client-credentials.d.ts +52 -0
  292. package/esm/server/grants/client-credentials.js +53 -0
  293. package/esm/server/grants/device-authorization.d.ts +103 -0
  294. package/esm/server/grants/device-authorization.js +156 -0
  295. package/esm/server/grants/grant.d.ts +165 -0
  296. package/esm/server/grants/grant.js +173 -0
  297. package/esm/server/grants/password.d.ts +80 -0
  298. package/esm/server/grants/password.js +92 -0
  299. package/esm/server/grants/refresh-token.d.ts +80 -0
  300. package/esm/server/grants/refresh-token.js +136 -0
  301. package/esm/server/introspection-token-reader.d.ts +153 -0
  302. package/esm/server/introspection-token-reader.js +164 -0
  303. package/esm/server/jwks-token-reader.d.ts +263 -0
  304. package/esm/server/jwks-token-reader.js +508 -0
  305. package/esm/server/mod.d.ts +23 -0
  306. package/esm/server/mod.js +15 -0
  307. package/esm/server/public-suffix/mod.d.ts +62 -0
  308. package/esm/server/public-suffix/mod.js +103 -0
  309. package/esm/server/public-suffix/rules.d.ts +23 -0
  310. package/esm/server/public-suffix/rules.js +23 -0
  311. package/esm/server/redirect-uri.d.ts +187 -0
  312. package/esm/server/redirect-uri.js +331 -0
  313. package/esm/server/resource-server.d.ts +286 -0
  314. package/esm/server/resource-server.js +365 -0
  315. package/esm/server/services/authorization-code.d.ts +84 -0
  316. package/esm/server/services/authorization-code.js +35 -0
  317. package/esm/server/services/client.d.ts +35 -0
  318. package/esm/server/services/client.js +1 -0
  319. package/esm/server/services/device-authorization.d.ts +134 -0
  320. package/esm/server/services/device-authorization.js +7 -0
  321. package/esm/server/services/token.d.ts +242 -0
  322. package/esm/server/services/token.js +78 -0
  323. package/esm/server/services/user.d.ts +29 -0
  324. package/esm/server/services/user.js +7 -0
  325. package/esm/server/signing-keys.d.ts +201 -0
  326. package/esm/server/signing-keys.js +225 -0
  327. package/esm/server/utils/hash.d.ts +6 -0
  328. package/esm/server/utils/hash.js +11 -0
  329. package/esm/testing/contract/authorization-code.d.ts +37 -0
  330. package/esm/testing/contract/authorization-code.js +93 -0
  331. package/esm/testing/contract/client.d.ts +85 -0
  332. package/esm/testing/contract/client.js +126 -0
  333. package/esm/testing/contract/device-authorization.d.ts +37 -0
  334. package/esm/testing/contract/device-authorization.js +193 -0
  335. package/esm/testing/contract/lockout-store.d.ts +47 -0
  336. package/esm/testing/contract/lockout-store.js +122 -0
  337. package/esm/testing/contract/mfa-store.d.ts +50 -0
  338. package/esm/testing/contract/mfa-store.js +226 -0
  339. package/esm/testing/contract/mod.d.ts +39 -0
  340. package/esm/testing/contract/mod.js +39 -0
  341. package/esm/testing/contract/otp-store.d.ts +48 -0
  342. package/esm/testing/contract/otp-store.js +197 -0
  343. package/esm/testing/contract/rate-limit-store.d.ts +52 -0
  344. package/esm/testing/contract/rate-limit-store.js +105 -0
  345. package/esm/testing/contract/token-flow-store.d.ts +54 -0
  346. package/esm/testing/contract/token-flow-store.js +179 -0
  347. package/esm/testing/contract/token-reader.d.ts +77 -0
  348. package/esm/testing/contract/token-reader.js +78 -0
  349. package/esm/testing/contract/token.d.ts +61 -0
  350. package/esm/testing/contract/token.js +287 -0
  351. package/esm/testing/contract/user.d.ts +63 -0
  352. package/esm/testing/contract/user.js +75 -0
  353. package/esm/testing/mod.d.ts +34 -0
  354. package/esm/testing/mod.js +34 -0
  355. package/esm/testing/server.d.ts +197 -0
  356. package/esm/testing/server.js +193 -0
  357. package/esm/testing/services.d.ts +373 -0
  358. package/esm/testing/services.js +703 -0
  359. package/esm/utils/_buffer.d.ts +10 -0
  360. package/esm/utils/_buffer.js +12 -0
  361. package/esm/utils/_timing-safe.d.ts +13 -0
  362. package/esm/utils/_timing-safe.js +22 -0
  363. package/esm/utils/authentication-context.d.ts +3 -0
  364. package/esm/utils/authentication-context.js +14 -0
  365. package/esm/utils/basic-auth.d.ts +50 -0
  366. package/esm/utils/basic-auth.js +106 -0
  367. package/esm/utils/crypto.d.ts +97 -0
  368. package/esm/utils/crypto.js +162 -0
  369. package/esm/utils/pkce.d.ts +151 -0
  370. package/esm/utils/pkce.js +179 -0
  371. package/esm/utils/text.d.ts +24 -0
  372. package/esm/utils/text.js +28 -0
  373. package/esm/utils/url.d.ts +66 -0
  374. package/esm/utils/url.js +90 -0
  375. package/package.json +92 -0
@@ -0,0 +1,1304 @@
1
+ /**
2
+ * Authorization Server implementation for issuing OAuth2 tokens.
3
+ *
4
+ * Implements the OAuth 2.0 Authorization Framework (RFC 6749) with support for:
5
+ * - Token endpoint (RFC 6749 Section 3.2)
6
+ * - Authorization endpoint (RFC 6749 Section 3.1)
7
+ * - Token revocation (RFC 7009)
8
+ * - Token introspection (RFC 7662)
9
+ * - Server metadata (RFC 8414)
10
+ * - Device authorization (RFC 8628)
11
+ *
12
+ * **Configuration.** A single instance serves every tenant/origin. The only
13
+ * configuration seam is `resolve(request)`, which returns the services the
14
+ * server's own endpoints need (`{ clientService, tokenService }`) plus the
15
+ * issuer/endpoints. Each grant resolves its own services independently. For a
16
+ * single-tenant server, `resolve` is a constant function (create the services
17
+ * once, outside it, and return them every time).
18
+ *
19
+ * @see https://datatracker.ietf.org/doc/html/rfc6749
20
+ * @module
21
+ */
22
+ import "../_dnt.polyfills.js";
23
+ import { snapshotAuthenticationContext } from "../utils/authentication-context.js";
24
+ import { AccessDeniedError, InvalidRequestError, InvalidTokenError, isOAuth2Error, OAuth2Error, ServerError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, } from "../errors.js";
25
+ import { authenticateClientCredentials, extractClientCredentials, } from "./client-authentication.js";
26
+ import { isRedirectUriPattern, matchRedirectUri } from "./redirect-uri.js";
27
+ import { AuthorizationCodeGrant } from "./grants/authorization-code.js";
28
+ import { DeviceAuthorizationGrant } from "./grants/device-authorization.js";
29
+ import { ResourceServer, } from "./resource-server.js";
30
+ import { defaultSubjectOf, signJwt, verifyJwt, } from "./signing-keys.js";
31
+ import { validateCodeChallenge } from "../utils/pkce.js";
32
+ const ENDPOINTS = {
33
+ authorization: {
34
+ option: "authorizationEndpoint",
35
+ path: "/authorize",
36
+ metadata: "authorization_endpoint",
37
+ methods: ["GET"],
38
+ oidc: false,
39
+ },
40
+ token: {
41
+ option: "tokenEndpoint",
42
+ path: "/token",
43
+ metadata: "token_endpoint",
44
+ methods: ["POST"],
45
+ oidc: false,
46
+ },
47
+ revocation: {
48
+ option: "revocationEndpoint",
49
+ path: "/revoke",
50
+ metadata: "revocation_endpoint",
51
+ methods: ["POST"],
52
+ oidc: false,
53
+ },
54
+ introspection: {
55
+ option: "introspectionEndpoint",
56
+ path: "/introspect",
57
+ metadata: "introspection_endpoint",
58
+ methods: ["POST"],
59
+ oidc: false,
60
+ },
61
+ deviceAuthorization: {
62
+ option: "deviceAuthorizationEndpoint",
63
+ path: "/device_authorization",
64
+ metadata: "device_authorization_endpoint",
65
+ methods: ["POST"],
66
+ oidc: false,
67
+ },
68
+ jwks: {
69
+ option: "jwksEndpoint",
70
+ path: "/jwks",
71
+ metadata: "jwks_uri",
72
+ methods: ["GET"],
73
+ oidc: true,
74
+ },
75
+ userinfo: {
76
+ option: "userinfoEndpoint",
77
+ path: "/userinfo",
78
+ metadata: "userinfo_endpoint",
79
+ methods: ["GET", "POST"],
80
+ oidc: true,
81
+ },
82
+ endSession: {
83
+ option: "endSessionEndpoint",
84
+ path: "/end_session",
85
+ metadata: "end_session_endpoint",
86
+ methods: ["GET", "POST"],
87
+ oidc: true,
88
+ },
89
+ metadata: {
90
+ path: "/.well-known/oauth-authorization-server",
91
+ methods: ["GET"],
92
+ },
93
+ oidcMetadata: {
94
+ path: "/.well-known/openid-configuration",
95
+ methods: ["GET"],
96
+ },
97
+ };
98
+ const _endpointKeysInSync = true;
99
+ const ENDPOINT_ENTRIES = Object.entries(ENDPOINTS);
100
+ const ADVERTISED_ENTRIES = ENDPOINT_ENTRIES.filter((entry) => "metadata" in entry[1]);
101
+ const DEVICE_AUTHORIZATION_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
102
+ const AUTHORIZATION_CODE_GRANT_TYPE = "authorization_code";
103
+ /**
104
+ * The path each endpoint is served from when the request context supplies only
105
+ * an `issuer` (so the endpoint URL is `${issuer}${path}`). Mount handlers at
106
+ * these paths — as the Hono adapter's `routes()` does — to match what the
107
+ * server advertises in its RFC 8414 metadata.
108
+ *
109
+ * Covers the whole served surface: the seven endpoints the server advertises,
110
+ * plus the two discovery documents (`metadata` for RFC 8414,
111
+ * `oidcMetadata` for OIDC Discovery) that are served but never advertised.
112
+ * Pair it with {@link ENDPOINT_METHODS} to enumerate path *and* verbs.
113
+ *
114
+ * @example Mount every endpoint yourself
115
+ * ```ts
116
+ * for (const key of Object.keys(ENDPOINT_PATHS) as (keyof typeof ENDPOINT_PATHS)[]) {
117
+ * app.on([...ENDPOINT_METHODS[key]], ENDPOINT_PATHS[key], handlerFor(key));
118
+ * }
119
+ * ```
120
+ */
121
+ export const ENDPOINT_PATHS = Object
122
+ .fromEntries(ENDPOINT_ENTRIES.map(([key, { path }]) => [key, path]));
123
+ /**
124
+ * The HTTP methods each endpoint in {@link ENDPOINT_PATHS} answers — `POST` for
125
+ * the token-style endpoints, `GET` for authorize/JWKS/discovery, both for
126
+ * UserInfo. Read it instead of re-deriving verbs per endpoint, so a hand-rolled
127
+ * mount answers exactly what the built-in `routes()` does.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * app.on([...ENDPOINT_METHODS.userinfo], ENDPOINT_PATHS.userinfo, handler);
132
+ * ```
133
+ */
134
+ export const ENDPOINT_METHODS = Object.fromEntries(ENDPOINT_ENTRIES.map(([key, { methods }]) => [key, methods]));
135
+ function isAuthorizationCodeGrant(grant) {
136
+ return grant instanceof AuthorizationCodeGrant;
137
+ }
138
+ function isDeviceAuthorizationGrant(grant) {
139
+ return grant instanceof DeviceAuthorizationGrant;
140
+ }
141
+ /**
142
+ * Authorization Server for issuing OAuth2 tokens.
143
+ *
144
+ * Handles OAuth2 endpoints including:
145
+ * - Token endpoint (POST /token)
146
+ * - Authorization endpoint (GET /authorize)
147
+ * - Revocation endpoint (POST /revoke) - RFC 7009
148
+ * - Introspection endpoint (POST /introspect) - RFC 7662
149
+ * - Device authorization endpoint (POST /device_authorization) - RFC 8628
150
+ * - Metadata endpoint (GET /.well-known/oauth-authorization-server) - RFC 8414
151
+ *
152
+ * Framework-agnostic: each `handle*Request` method takes a web `Request` and
153
+ * returns a `Response`, so an adapter routes its endpoints to them. Extends
154
+ * {@linkcode ResourceServer}, so the same instance also validates bearer
155
+ * tokens. A single instance serves every tenant/origin via the `resolve`
156
+ * callback in {@linkcode AuthorizationServerOptions}.
157
+ *
158
+ * The handlers throw {@linkcode HttpError} subclasses on failure (e.g.
159
+ * {@linkcode InvalidRequestError}, {@linkcode InvalidClientError},
160
+ * {@linkcode UnsupportedGrantTypeError}) and convert them to RFC 6749 §5.2
161
+ * error responses unless `throwOnError` is set.
162
+ *
163
+ * @template Client The client entity type.
164
+ * @template User The resource-owner entity type.
165
+ * @template S The scope type used across grants and metadata.
166
+ *
167
+ * @see https://datatracker.ietf.org/doc/html/rfc6749
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * const server = new AuthorizationServer({
172
+ * resolve: () => ({ services, issuer: "https://auth.example.com" }),
173
+ * grants: { authorization_code: authCodeGrant, refresh_token: refreshGrant },
174
+ * });
175
+ *
176
+ * app.post("/token", (c) => server.handleTokenRequest(c.req.raw));
177
+ * ```
178
+ */
179
+ export class AuthorizationServer extends ResourceServer {
180
+ /** Grants the server supports, keyed by `grant_type`. */
181
+ grants;
182
+ /** Scopes advertised in RFC 8414 metadata, if configured. */
183
+ scopesSupported;
184
+ /** OIDC signing keys; the OIDC surface is active when set. */
185
+ signingKeys;
186
+ #endSession;
187
+ #isPublicSuffix;
188
+ #subjectOf;
189
+ #userClaims;
190
+ #introspectionClaims;
191
+ #canIntrospectToken;
192
+ /** Resolves the per-request {@link AuthorizationServerContext}. */
193
+ #resolve;
194
+ /**
195
+ * Creates an authorization server from the given
196
+ * {@linkcode AuthorizationServerOptions}.
197
+ *
198
+ * @throws {Error} If any grant's map key does not equal its `grantType`.
199
+ * @throws {Error} If `requireOidc` is set without `signingKeys`.
200
+ */
201
+ constructor(options) {
202
+ super({
203
+ resolve: options.resolve,
204
+ Scope: options.Scope,
205
+ realm: options.realm,
206
+ errorFormat: options.errorFormat,
207
+ throwOnError: options.throwOnError,
208
+ });
209
+ this.#resolve = options.resolve;
210
+ this.grants = { ...options.grants };
211
+ for (const [grantType, grant] of Object.entries(this.grants)) {
212
+ if (grant.grantType !== grantType) {
213
+ throw new Error(`grant registered under key "${grantType}" has grantType ` +
214
+ `"${grant.grantType}"; the map key must equal grant.grantType`);
215
+ }
216
+ }
217
+ this.scopesSupported = options.scopesSupported;
218
+ this.signingKeys = options.signingKeys;
219
+ this.#endSession = options.endSession;
220
+ if (options.requireOidc && !this.signingKeys) {
221
+ throw new Error("requireOidc is set but no signingKeys provider was configured, so " +
222
+ "OIDC issuance is off: this instance would mint no id_token and " +
223
+ "answer 404 from /.well-known/openid-configuration, /jwks, and " +
224
+ "/userinfo. Load the signing key (importSigningKeyJwk) and pass it " +
225
+ "as the signingKeys option, or unset requireOidc if this server is " +
226
+ "plain OAuth2.");
227
+ }
228
+ this.#isPublicSuffix = options.isPublicSuffix;
229
+ this.#subjectOf = options.subjectOf ?? defaultSubjectOf;
230
+ this.#userClaims = options.userClaims;
231
+ this.#introspectionClaims = options.introspectionClaims;
232
+ this.#canIntrospectToken = options.canIntrospectToken;
233
+ }
234
+ /**
235
+ * Whether this instance will actually serve the OIDC surface: `true` once
236
+ * `signingKeys` is set, which is what gates `id_token` issuance and the
237
+ * `/jwks`, `/userinfo`, and `openid-configuration` endpoints (they answer
238
+ * 404 while it is `false`).
239
+ *
240
+ * Read it in a readiness probe to keep an instance that booted without its
241
+ * signing key out of the load balancer instead of letting it 404 relying
242
+ * parties. It reports the provider this server configured — not that the
243
+ * provider can produce a key, which surfaces on first use — and it tracks
244
+ * the `signingKeys` field, so it is meaningful from construction onward and
245
+ * follows any later reassignment.
246
+ */
247
+ get oidcEnabled() {
248
+ return this.signingKeys !== undefined;
249
+ }
250
+ /** Resolves the per-request {@link AuthorizationServerContext}. */
251
+ async authorizationContext(request) {
252
+ return await this.#resolve(request);
253
+ }
254
+ /** Resolves the endpoint URLs for a request (issuer-derived unless set). */
255
+ #resolvedEndpoints(context) {
256
+ const { issuer } = context;
257
+ const endpoints = {};
258
+ for (const [key, { option, path }] of ADVERTISED_ENTRIES) {
259
+ endpoints[key] = context[option] ??
260
+ (issuer ? `${issuer}${path}` : undefined);
261
+ }
262
+ return endpoints;
263
+ }
264
+ /** The resolved endpoint URLs for a request. Used by {@link localAuthServerFetch}. */
265
+ async resolveEndpoints(request) {
266
+ return this.#resolvedEndpoints(await this.authorizationContext(request));
267
+ }
268
+ /**
269
+ * Converts a token to a bearer token response body.
270
+ */
271
+ bearerToken(token, services) {
272
+ const bearerToken = {
273
+ token_type: "Bearer",
274
+ access_token: token.accessToken,
275
+ };
276
+ const { tokenService } = services;
277
+ if (token.accessTokenExpiresAt) {
278
+ bearerToken.expires_in = Math.round((token.accessTokenExpiresAt.getTime() - Date.now()) / 1000);
279
+ }
280
+ else if (tokenService) {
281
+ bearerToken.expires_in = tokenService.accessTokenLifetime;
282
+ }
283
+ if ("refreshToken" in token && token.refreshToken) {
284
+ bearerToken.refresh_token = token.refreshToken;
285
+ }
286
+ if (token.scope) {
287
+ bearerToken.scope = token.scope.toString();
288
+ }
289
+ return bearerToken;
290
+ }
291
+ /**
292
+ * Creates a token response. When the issuing `grant` is passed, an id_token
293
+ * is only minted for grants that represent an end-user authentication
294
+ * (`grant.issuesIdToken !== false`).
295
+ */
296
+ async createTokenResponse(token, context, grant) {
297
+ const body = this.bearerToken(token, context.services);
298
+ if (grant?.issuesIdToken !== false) {
299
+ const idToken = await this.mintIdToken(token, context);
300
+ if (idToken)
301
+ body.id_token = idToken;
302
+ }
303
+ return new Response(JSON.stringify(body), {
304
+ status: 200,
305
+ headers: {
306
+ "Content-Type": "application/json;charset=UTF-8",
307
+ "Cache-Control": "no-store",
308
+ "Pragma": "no-cache",
309
+ },
310
+ });
311
+ }
312
+ /**
313
+ * Applies `Cache-Control: no-store` and `Pragma: no-cache` headers to an
314
+ * error per RFC 6749 Section 5.2. The headers land on both the rethrown
315
+ * error (when {@link throwOnError} is true) and the generated response.
316
+ */
317
+ addCacheHeaders(error) {
318
+ error.headers.set("Cache-Control", "no-store");
319
+ error.headers.set("Pragma", "no-cache");
320
+ }
321
+ /**
322
+ * Error-prepare callback for the endpoints that authenticate the client
323
+ * (token, revocation, introspection, device authorization). Attaches the
324
+ * RFC 6749 Section 5.2 cache headers, and — when client authentication was
325
+ * attempted via the `Authorization` header and failed with a 401 — the
326
+ * matching `WWW-Authenticate: Basic` challenge that RFC 6749 Section 5.2
327
+ * requires.
328
+ */
329
+ prepareClientAuthError(request) {
330
+ return (error) => {
331
+ this.addCacheHeaders(error);
332
+ if (error.status === 401 && request.headers.get("authorization")) {
333
+ error.headers.set("WWW-Authenticate", `Basic realm="${this.realm}"`);
334
+ }
335
+ };
336
+ }
337
+ /**
338
+ * Rejects a client that is not registered for a grant type
339
+ * (RFC 6749 Section 5.2 `unauthorized_client`).
340
+ */
341
+ #assertGrantAuthorized(client, grantType) {
342
+ if (!client.grants?.includes(grantType)) {
343
+ throw new UnauthorizedClientError(`client is not authorized to use the ${grantType} grant type`);
344
+ }
345
+ }
346
+ /** The grant a token request's `grant_type` dispatches to. */
347
+ #grantFromBody(body) {
348
+ const grantType = body.get("grant_type");
349
+ if (typeof grantType !== "string") {
350
+ throw new InvalidRequestError("grant_type parameter required");
351
+ }
352
+ const grant = this.grants[grantType];
353
+ if (!grant) {
354
+ throw new UnsupportedGrantTypeError("invalid grant_type");
355
+ }
356
+ return grant;
357
+ }
358
+ async #beginClientAuthenticatedRequest(request, options = {}) {
359
+ if (request.method !== "POST") {
360
+ throw new InvalidRequestError("method must be POST");
361
+ }
362
+ const contentType = request.headers.get("content-type");
363
+ if (!contentType?.includes("application/x-www-form-urlencoded")) {
364
+ throw new InvalidRequestError("content-type header must be application/x-www-form-urlencoded");
365
+ }
366
+ const context = await this.authorizationContext(request);
367
+ const body = await request.clone().formData().catch(() => {
368
+ throw new InvalidRequestError("body must be application/x-www-form-urlencoded");
369
+ });
370
+ const grant = options.grantFromBody?.(body);
371
+ const client = grant
372
+ ? await grant.getAuthenticatedClient(request, body)
373
+ : await this.authenticateClient(request, body, context.services);
374
+ const grantType = grant?.grantType ?? options.requiredGrantType;
375
+ if (grantType)
376
+ this.#assertGrantAuthorized(client, grantType);
377
+ return { context, body, client, grant };
378
+ }
379
+ /**
380
+ * Handles a token request (POST /token).
381
+ * Supports multiple grant types based on the configured grants.
382
+ *
383
+ * A malformed request — wrong method, a content-type other than
384
+ * `application/x-www-form-urlencoded`, or an unsupported grant — is a 400
385
+ * `invalid_request`.
386
+ */
387
+ async handleTokenRequest(request) {
388
+ try {
389
+ const { context, body, client, grant } = await this
390
+ .#beginClientAuthenticatedRequest(request, {
391
+ grantFromBody: (body) => this.#grantFromBody(body),
392
+ });
393
+ const token = await grant.token(request, client, body);
394
+ return await this.createTokenResponse(token, context, grant);
395
+ }
396
+ catch (error) {
397
+ return this.handleError(error, this.prepareClientAuthError(request));
398
+ }
399
+ }
400
+ /**
401
+ * Mint an OIDC id_token for a user-bound token whose scope includes
402
+ * `openid`, or `undefined` when the OIDC surface is off (no signing keys),
403
+ * the token has no user, the scope lacks `openid`, or no issuer resolved.
404
+ */
405
+ async mintIdToken(token, context) {
406
+ if (!this.signingKeys || !token.user || !context.issuer)
407
+ return undefined;
408
+ if (!token.scope?.has("openid"))
409
+ return undefined;
410
+ const key = await this.signingKeys.getSigningKey();
411
+ const now = Math.floor(Date.now() / 1000);
412
+ const claims = {
413
+ ...(await this.#oidcClaims(token.user, token.scope, token.authenticationContext)),
414
+ iss: context.issuer,
415
+ aud: token.client.id,
416
+ iat: now,
417
+ exp: now + 3600,
418
+ };
419
+ if (token.nonce)
420
+ claims.nonce = token.nonce;
421
+ return await signJwt(key, claims);
422
+ }
423
+ /** OIDC claims released for a user: `userClaims` under the winning `sub`. */
424
+ async #oidcClaims(user, scope, authenticationContext) {
425
+ return {
426
+ ...(await this.#userClaims?.(user, scope, snapshotAuthenticationContext(authenticationContext)) ?? {}),
427
+ sub: this.#subjectOf(user),
428
+ };
429
+ }
430
+ /**
431
+ * Handles an OIDC discovery request (GET /.well-known/openid-configuration):
432
+ * the RFC 8414 metadata document including the OIDC fields. 404 when OIDC
433
+ * issuance is off (no signing keys), so relying parties fail fast instead of
434
+ * reading a document missing its REQUIRED OIDC members.
435
+ */
436
+ async handleOidcMetadataRequest(request) {
437
+ if (!this.signingKeys) {
438
+ return this.handleError(new OAuth2Error(404, "OIDC issuance is not enabled", {
439
+ extensions: { error: "not_found" },
440
+ }));
441
+ }
442
+ return await this.handleMetadataRequest(request);
443
+ }
444
+ /**
445
+ * Handles a JWKS request (GET /jwks): the public signing keys id_tokens and
446
+ * JWT access tokens verify against. 404 when OIDC issuance is off.
447
+ */
448
+ async handleJwksRequest(_request) {
449
+ if (!this.signingKeys) {
450
+ return this.handleError(new OAuth2Error(404, "OIDC issuance is not enabled", {
451
+ extensions: { error: "not_found" },
452
+ }));
453
+ }
454
+ try {
455
+ const jwks = await this.signingKeys.getPublicJwks();
456
+ return new Response(JSON.stringify(jwks), {
457
+ status: 200,
458
+ headers: {
459
+ "Content-Type": "application/json;charset=UTF-8",
460
+ "Cache-Control": "public, max-age=300",
461
+ },
462
+ });
463
+ }
464
+ catch (error) {
465
+ return this.handleError(error);
466
+ }
467
+ }
468
+ /**
469
+ * Handles an OIDC UserInfo request (GET/POST /userinfo): validates the
470
+ * bearer token (which must carry the `openid` scope and a user) and returns
471
+ * `sub` plus any configured `userClaims`.
472
+ *
473
+ * Failures follow RFC 6750 Section 3.1, which OIDC Core Section 5.3.3 defers
474
+ * to: a missing, malformed, or expired token is a 401 (`invalid_token`,
475
+ * omitted from the challenge when no credentials were presented at all),
476
+ * while a **valid** token that simply lacks the `openid` scope is a 403
477
+ * `insufficient_scope` whose challenge names `scope="openid"`. The
478
+ * distinction matters to relying parties — a 401 tells an RP its credential
479
+ * is bad, so answering one for a scope problem sends it to refresh a token
480
+ * that will fail again the same way.
481
+ */
482
+ async handleUserInfoRequest(request) {
483
+ try {
484
+ const { user, scope, token } = await this.authenticate(request);
485
+ this.assertScope(scope, "openid");
486
+ if (!user) {
487
+ throw new InvalidTokenError("access token has no resource owner");
488
+ }
489
+ const claims = await this.#oidcClaims(user, scope, token.authenticationContext);
490
+ return new Response(JSON.stringify(claims), {
491
+ status: 200,
492
+ headers: {
493
+ "Content-Type": "application/json;charset=UTF-8",
494
+ "Cache-Control": "no-store",
495
+ },
496
+ });
497
+ }
498
+ catch (error) {
499
+ return this.handleAuthError(error);
500
+ }
501
+ }
502
+ /**
503
+ * Handles an OIDC RP-Initiated Logout request (GET/POST /end_session).
504
+ *
505
+ * The server does not know what a session **is** — that is the app's — so
506
+ * `endSession` does the ending and this method does the protocol around it:
507
+ * identifying the relying party, authorizing the return trip, and shaping
508
+ * the response. `endSession` is always called, even when nothing else about
509
+ * the request checks out, because a logout that refuses to log anyone out is
510
+ * the worst possible failure mode for this endpoint.
511
+ *
512
+ * **Authorizing `post_logout_redirect_uri` is the whole security surface.**
513
+ * It is matched by **exact string** against the resolved client's
514
+ * {@link ClientInterface.postLogoutRedirectUris} — no patterns, no prefixes,
515
+ * unlike the authorization endpoint's redirect matching. A logout redirect
516
+ * carries no code to steal, but an unvalidated one is an open redirect on the
517
+ * IDP's own domain, which is a phishing primitive: the victim really did just
518
+ * come from your login page. When no client resolves, or the URI is not
519
+ * registered, the browser is **not** sent there — it goes to `fallback` if
520
+ * the app supplied one, or gets a bare 204.
521
+ *
522
+ * `id_token_hint` is verified against the signing keys but its expiry is
523
+ * ignored: by the time anyone logs out the id_token naming their session has
524
+ * usually expired, and it is a hint, not authority. `client_id` is accepted
525
+ * as the fallback identifier the spec allows. `state` rides along only to a
526
+ * URI that passed authorization.
527
+ *
528
+ * Wire the app's session teardown through the `endSession` **constructor
529
+ * option**; this method answers 404 without it, and the discovery document
530
+ * omits `end_session_endpoint` to match.
531
+ *
532
+ * @example
533
+ * ```ts
534
+ * const server = new AuthorizationServer({
535
+ * resolve,
536
+ * grants,
537
+ * signingKeys,
538
+ * endSession: async ({ subject }) => {
539
+ * if (subject) await sessions.deleteByUserId(subject);
540
+ * return { headers: { "Set-Cookie": clearSessionCookie() }, fallback: "/" };
541
+ * },
542
+ * });
543
+ * app.all("/end_session", (c) => server.handleEndSessionRequest(c.req.raw));
544
+ * ```
545
+ */
546
+ async handleEndSessionRequest(request) {
547
+ const endSession = this.#endSession;
548
+ if (!endSession)
549
+ return new Response(null, { status: 404 });
550
+ try {
551
+ const params = await this.#endSessionParameters(request);
552
+ const client = await this.#resolveEndSessionClient(request, params);
553
+ const result = await endSession({
554
+ request,
555
+ client,
556
+ subject: params.subject,
557
+ logoutHint: params.logoutHint,
558
+ }) ?? {};
559
+ const headers = new Headers(result.headers);
560
+ const authorized = this.#postLogoutRedirect(client, params);
561
+ const target = authorized ?? result.fallback;
562
+ if (!target)
563
+ return new Response(null, { status: 204, headers });
564
+ headers.set("Location", target);
565
+ return new Response(null, { status: 302, headers });
566
+ }
567
+ catch (error) {
568
+ return this.handleError(error);
569
+ }
570
+ }
571
+ /**
572
+ * The logout parameters, from the query string on GET and the form body on
573
+ * POST — RP-Initiated Logout §2 permits both. A verified `id_token_hint`
574
+ * contributes the subject and the audience; an unverifiable one contributes
575
+ * nothing rather than refusing the logout.
576
+ */
577
+ async #endSessionParameters(request) {
578
+ const url = new URL(request.url);
579
+ const params = request.method === "POST"
580
+ ? new URLSearchParams(await request.text())
581
+ : url.searchParams;
582
+ const idTokenHint = params.get("id_token_hint");
583
+ const hint = idTokenHint ? await this.#readIdTokenHint(idTokenHint) : null;
584
+ return {
585
+ clientId: params.get("client_id") ?? hint?.audience ?? null,
586
+ subject: hint?.subject ?? null,
587
+ postLogoutRedirectUri: params.get("post_logout_redirect_uri") ?? null,
588
+ state: params.get("state") ?? null,
589
+ logoutHint: params.get("logout_hint") ?? null,
590
+ };
591
+ }
592
+ /** The subject and audience of a signature-valid `id_token_hint`, if any. */
593
+ async #readIdTokenHint(idTokenHint) {
594
+ const key = await this.signingKeys?.getSigningKey();
595
+ if (!key)
596
+ return null;
597
+ const payload = await verifyJwt(idTokenHint, key.publicJwk, {
598
+ ignoreExpiration: true,
599
+ });
600
+ if (!payload)
601
+ return null;
602
+ const aud = payload.aud;
603
+ const audience = typeof aud === "string"
604
+ ? aud
605
+ : Array.isArray(aud) && typeof aud[0] === "string"
606
+ ? aud[0]
607
+ : null;
608
+ return {
609
+ subject: typeof payload.sub === "string" ? payload.sub : null,
610
+ audience,
611
+ };
612
+ }
613
+ /** The client the logout names, or null when it named none we can find. */
614
+ async #resolveEndSessionClient(request, params) {
615
+ if (!params.clientId)
616
+ return null;
617
+ const { services } = await this.authorizationContext(request);
618
+ try {
619
+ return await services.clientService.get(params.clientId) ?? null;
620
+ }
621
+ catch {
622
+ return null;
623
+ }
624
+ }
625
+ /**
626
+ * The `post_logout_redirect_uri` to honor, with `state` appended — or
627
+ * undefined when the request named none, named no resolvable client, or
628
+ * named a URI that client did not register.
629
+ */
630
+ #postLogoutRedirect(client, params) {
631
+ const requested = params.postLogoutRedirectUri;
632
+ if (!requested || !client)
633
+ return undefined;
634
+ const registered = client.postLogoutRedirectUris ?? [];
635
+ if (!registered.includes(requested))
636
+ return undefined;
637
+ if (!params.state)
638
+ return requested;
639
+ const url = new URL(requested);
640
+ url.searchParams.set("state", params.state);
641
+ return url.toString();
642
+ }
643
+ /**
644
+ * Parses authorization request parameters from the query string.
645
+ *
646
+ * The authorization endpoint is GET-only (RFC 6749 Section 3.1 requires GET
647
+ * and only permits POST), so parameters are read from `url.searchParams`.
648
+ */
649
+ parseAuthorizeParameters(request) {
650
+ const url = new URL(request.url);
651
+ const params = url.searchParams;
652
+ return {
653
+ responseType: params.get("response_type") ?? undefined,
654
+ clientId: params.get("client_id") ?? undefined,
655
+ redirectUri: params.get("redirect_uri") ?? undefined,
656
+ state: params.get("state") ?? undefined,
657
+ scope: params.get("scope") ?? undefined,
658
+ challenge: params.get("code_challenge") ?? undefined,
659
+ challengeMethod: params.get("code_challenge_method") ?? undefined,
660
+ nonce: params.get("nonce") ?? undefined,
661
+ acrValues: params.get("acr_values") ?? undefined,
662
+ maxAge: params.get("max_age") ?? undefined,
663
+ prompt: params.get("prompt") ?? undefined,
664
+ };
665
+ }
666
+ /**
667
+ * Handles an authorization request (GET /authorize).
668
+ * Used for the authorization code flow.
669
+ *
670
+ * Once the request has named a redirect URI the client registered, failures
671
+ * are reported to that URI as an RFC 6749 Section 4.1.2.1 error redirect;
672
+ * before that they are error responses from this endpoint, since there is no
673
+ * verified place to send the user back to.
674
+ *
675
+ * Requires a grant extending {@linkcode AuthorizationCodeGrant} registered
676
+ * under `authorization_code`; without one the endpoint answers 500
677
+ * `server_error` (or rethrows, with `throwOnError`).
678
+ *
679
+ * @param request The incoming request
680
+ * @param authenticateUser Function to authenticate the user
681
+ * @param handleConsent Optional function to handle user consent
682
+ * @returns A Response (redirect to client or error page)
683
+ */
684
+ async handleAuthorizeRequest(request, authenticateUser, handleConsent) {
685
+ const target = { url: null };
686
+ try {
687
+ return await this.#authorize(request, target, authenticateUser, handleConsent);
688
+ }
689
+ catch (error) {
690
+ return this.#authorizeErrorResponse(error, target.url);
691
+ }
692
+ }
693
+ /**
694
+ * The authorize flow proper, recording the redirect target on `target` as
695
+ * soon as one is verified so {@link handleAuthorizeRequest} can report later
696
+ * failures to the client.
697
+ */
698
+ async #authorize(request, target, authenticateUser, handleConsent) {
699
+ const params = this.parseAuthorizeParameters(request);
700
+ const grant = this.#authorizationCodeGrant();
701
+ const { services } = await this.authorizationContext(request);
702
+ const client = await this.#resolveAuthorizeClient(grant, params.clientId, services);
703
+ const redirectUrl = this.#resolveRedirectTarget(client, params.redirectUri);
704
+ target.url = redirectUrl;
705
+ if (!params.state) {
706
+ throw new InvalidRequestError("state required");
707
+ }
708
+ redirectUrl.searchParams.set("state", params.state);
709
+ this.#validateResponseType(params.responseType);
710
+ const requestedScope = grant.parseScope(params.scope);
711
+ this.#validateChallenge(grant, params);
712
+ const authResult = await authenticateUser(request);
713
+ if (authResult instanceof Response)
714
+ return authResult;
715
+ if (!authResult) {
716
+ throw new AccessDeniedError("authentication required");
717
+ }
718
+ const { user, authorizedScope } = authResult;
719
+ const authenticationContext = snapshotAuthenticationContext(authResult.authenticationContext);
720
+ if (grant.requirePKCE && !params.challenge) {
721
+ throw new InvalidRequestError("code_challenge required (PKCE is required for this grant)");
722
+ }
723
+ const consented = await this.#resolveScopeAndConsent({
724
+ grant,
725
+ client,
726
+ user,
727
+ requestedScope,
728
+ authorizedScope,
729
+ services,
730
+ handleConsent,
731
+ });
732
+ if (consented instanceof Response)
733
+ return consented;
734
+ const authorizationCode = await grant.generateAuthorizationCode({
735
+ client,
736
+ user,
737
+ scope: consented.scope,
738
+ redirectUri: params.redirectUri ?? null,
739
+ challenge: params.challenge ?? null,
740
+ challengeMethod: params.challengeMethod ?? null,
741
+ nonce: params.nonce ?? null,
742
+ authenticationContext,
743
+ }, request);
744
+ redirectUrl.searchParams.set("code", authorizationCode.code);
745
+ return Response.redirect(redirectUrl.toString(), 302);
746
+ }
747
+ /**
748
+ * Reports an authorize failure: an RFC 6749 Section 4.1.2.1 error redirect
749
+ * once a verified `redirectUrl` exists, otherwise a plain error response.
750
+ */
751
+ #authorizeErrorResponse(error, redirectUrl) {
752
+ if (!redirectUrl || !(error instanceof Error) || !isOAuth2Error(error)) {
753
+ return this.handleError(error);
754
+ }
755
+ const { searchParams } = redirectUrl;
756
+ searchParams.set("error", error.extensions.error ?? "server_error");
757
+ // Use exposedMessage, never raw error.message, so 5xx server_error detail isn't leaked into the redirect.
758
+ const description = error.extensions.error_description ??
759
+ error.exposedMessage;
760
+ if (description) {
761
+ searchParams.set("error_description", description);
762
+ }
763
+ const errorUri = error.extensions.error_uri ?? error.type;
764
+ if (errorUri) {
765
+ searchParams.set("error_uri", errorUri);
766
+ }
767
+ return Response.redirect(redirectUrl.toString(), 302);
768
+ }
769
+ #authorizationCodeGrant() {
770
+ const grant = this.grants[AUTHORIZATION_CODE_GRANT_TYPE];
771
+ if (!grant) {
772
+ throw new ServerError("missing authorization code grant");
773
+ }
774
+ if (!isAuthorizationCodeGrant(grant)) {
775
+ throw new ServerError(`the grant registered as "${AUTHORIZATION_CODE_GRANT_TYPE}" ` +
776
+ `(${grant.constructor.name}) cannot serve the authorization ` +
777
+ `endpoint: it must extend AuthorizationCodeGrant`);
778
+ }
779
+ return grant;
780
+ }
781
+ #deviceAuthorizationGrant() {
782
+ const grant = this.grants[DEVICE_AUTHORIZATION_GRANT_TYPE];
783
+ if (!grant) {
784
+ throw new ServerError("device authorization grant not configured");
785
+ }
786
+ if (!isDeviceAuthorizationGrant(grant)) {
787
+ throw new ServerError(`the grant registered as "${DEVICE_AUTHORIZATION_GRANT_TYPE}" ` +
788
+ `(${grant.constructor.name}) cannot serve the device authorization ` +
789
+ `endpoint: it must extend DeviceAuthorizationGrant`);
790
+ }
791
+ return grant;
792
+ }
793
+ /**
794
+ * The client the authorize request names, once it is registered for the
795
+ * authorization code grant.
796
+ */
797
+ async #resolveAuthorizeClient(grant, clientId, services) {
798
+ if (!clientId) {
799
+ throw new InvalidRequestError("client_id parameter required");
800
+ }
801
+ const client = await grant.getClient(clientId, services.clientService);
802
+ this.#assertGrantAuthorized(client, AUTHORIZATION_CODE_GRANT_TYPE);
803
+ return client;
804
+ }
805
+ /**
806
+ * The URL the browser is sent back to: the requested `redirect_uri` once it
807
+ * matches a registration, or the client's first literal registration when the
808
+ * request omitted one.
809
+ */
810
+ #resolveRedirectTarget(client, redirectUri) {
811
+ if (!client.redirectUris?.length) {
812
+ throw new UnauthorizedClientError("no authorized redirect_uri");
813
+ }
814
+ if (redirectUri &&
815
+ !matchRedirectUri(client.redirectUris, redirectUri, this.#isPublicSuffix)) {
816
+ throw new UnauthorizedClientError("redirect_uri not authorized");
817
+ }
818
+ const defaultRedirectUri = client.redirectUris.find((uri) => !isRedirectUriPattern(uri));
819
+ if (!redirectUri && !defaultRedirectUri) {
820
+ throw new InvalidRequestError("redirect_uri required when every registered redirect_uri is a pattern");
821
+ }
822
+ return new URL(redirectUri ?? defaultRedirectUri);
823
+ }
824
+ /** Rejects any `response_type` other than the `code` this endpoint issues. */
825
+ #validateResponseType(responseType) {
826
+ if (!responseType) {
827
+ throw new InvalidRequestError("response_type required");
828
+ }
829
+ // RFC 6749 §4.1.2.1: unsupported response_type is unsupported_response_type, not invalid_request.
830
+ if (responseType !== "code") {
831
+ throw new UnsupportedResponseTypeError("response_type not supported");
832
+ }
833
+ }
834
+ /**
835
+ * Rejects PKCE parameters the grant cannot honor (RFC 7636 Section 4.3), and
836
+ * a `code_challenge` malformed for a method whose shape Section 4.2 fixes.
837
+ */
838
+ #validateChallenge(grant, params) {
839
+ const { challenge, challengeMethod } = params;
840
+ if (challengeMethod && !challenge) {
841
+ throw new InvalidRequestError("code_challenge required when code_challenge_method is set");
842
+ }
843
+ if (challenge && !grant.validateChallengeMethod(challengeMethod)) {
844
+ throw new InvalidRequestError("unsupported code_challenge_method");
845
+ }
846
+ if (challenge && !validateCodeChallenge(challenge, challengeMethod)) {
847
+ throw new InvalidRequestError("code_challenge must be 43-128 characters using [A-Z] / [a-z] / [0-9] / - / . / _ / ~");
848
+ }
849
+ }
850
+ /**
851
+ * The scope the authorization code will carry: the requested scope narrowed
852
+ * by the token service, then by consent when the user has not already
853
+ * pre-authorized it. A `Response` short-circuits the flow (a consent page).
854
+ */
855
+ async #resolveScopeAndConsent(options) {
856
+ const { grant, client, user, requestedScope, authorizedScope, services, handleConsent, } = options;
857
+ const acceptedScope = await grant.acceptedScope(client, user, requestedScope, services.tokenService);
858
+ let scope = acceptedScope ?? undefined;
859
+ const needsConsent = scope !== undefined &&
860
+ !(authorizedScope && authorizedScope.has(scope));
861
+ if (needsConsent && handleConsent) {
862
+ const consent = await handleConsent(client, scope, user);
863
+ if (consent instanceof Response)
864
+ return consent;
865
+ if (!consent.approved) {
866
+ throw new AccessDeniedError("user denied consent");
867
+ }
868
+ if (consent.scope)
869
+ scope = consent.scope;
870
+ }
871
+ return { scope };
872
+ }
873
+ /**
874
+ * Authenticates a client from the credentials the request presents: Basic
875
+ * authentication (RFC 6749 Section 2.3.1) or the `client_id`/`client_secret`
876
+ * of `body`, the form body the endpoint already parsed. The grants
877
+ * authenticate through the same implementation, so a request that fails here
878
+ * fails identically at the token endpoint.
879
+ *
880
+ * @throws {InvalidClientError} If client authentication fails
881
+ */
882
+ async authenticateClient(request, body, services) {
883
+ return await authenticateClientCredentials(extractClientCredentials(request, body), services.clientService);
884
+ }
885
+ /**
886
+ * Handles a token revocation request (POST /revoke).
887
+ *
888
+ * Per RFC 7009, requires client authentication and accepts:
889
+ * - token: The token to revoke (required)
890
+ * - token_type_hint: Hint about token type (optional)
891
+ *
892
+ * **A client can only revoke its own tokens.** RFC 7009 Section 2.1 requires
893
+ * the server to verify that the presented token was issued to the
894
+ * authenticated client; without that check any client that has observed a
895
+ * token value — a resource server it called, a shared browser, a log — could
896
+ * end another application's live sessions at will. A token belonging to a
897
+ * different client is left alone.
898
+ *
899
+ * Always returns 200 OK, even if the token was invalid, already revoked, or
900
+ * issued to another client: RFC 7009 Section 2.2 requires that answer so the
901
+ * endpoint cannot be used as an oracle for whether a token value is live or
902
+ * whose it is. That covers the token only: a malformed request — wrong
903
+ * method, a content-type other than `application/x-www-form-urlencoded`, or
904
+ * no `token` parameter — is a 400 `invalid_request`, as on the token
905
+ * endpoint.
906
+ *
907
+ * `token_type_hint` orders the lookup rather than restricting it, so a client
908
+ * that mislabels its token still gets it revoked.
909
+ *
910
+ * @see https://datatracker.ietf.org/doc/html/rfc7009
911
+ */
912
+ async handleRevocationRequest(request) {
913
+ try {
914
+ const { context, body, client } = await this
915
+ .#beginClientAuthenticatedRequest(request);
916
+ const token = body.get("token");
917
+ if (typeof token !== "string") {
918
+ throw new InvalidRequestError("token parameter required");
919
+ }
920
+ const tokenTypeHint = body.get("token_type_hint");
921
+ const hint = typeof tokenTypeHint === "string" ? tokenTypeHint : null;
922
+ const { tokenService } = context.services;
923
+ const resolved = await this.#resolveToken(tokenService, token, hint);
924
+ if (resolved &&
925
+ resolved.token.client.id.toString() === client.id.toString()) {
926
+ await tokenService.revoke(token, resolved.tokenType);
927
+ }
928
+ return new Response(null, { status: 200 });
929
+ }
930
+ catch (error) {
931
+ return this.handleError(error, this.prepareClientAuthError(request));
932
+ }
933
+ }
934
+ async #resolveToken(tokenService, value, hint) {
935
+ const refreshFirst = hint === "refresh_token";
936
+ if (refreshFirst) {
937
+ const refreshToken = await tokenService.getRefreshToken(value);
938
+ if (refreshToken) {
939
+ return { tokenType: "refresh_token", token: refreshToken };
940
+ }
941
+ }
942
+ const token = await tokenService.getToken(value);
943
+ if (token)
944
+ return { tokenType: "access_token", token };
945
+ if (refreshFirst)
946
+ return undefined;
947
+ const refreshToken = await tokenService.getRefreshToken(value);
948
+ return refreshToken
949
+ ? { tokenType: "refresh_token", token: refreshToken }
950
+ : undefined;
951
+ }
952
+ #introspectedExpiry(resolved) {
953
+ return resolved.tokenType === "refresh_token"
954
+ ? resolved.token.refreshTokenExpiresAt
955
+ : resolved.token.accessTokenExpiresAt;
956
+ }
957
+ /**
958
+ * Handles a token introspection request (POST /introspect).
959
+ *
960
+ * Per RFC 7662, requires client authentication and returns information
961
+ * about the token including whether it is active.
962
+ * {@linkcode AuthorizationServerOptions.canIntrospectToken} controls which
963
+ * tokens an authenticated caller may inspect. A denial returns only
964
+ * `{ active: false }`; without a policy, all admitted clients may inspect
965
+ * any token, including separately authorized resource servers.
966
+ *
967
+ * Both access tokens and refresh tokens introspect. `token_type_hint` orders
968
+ * the lookup rather than restricting it (RFC 7662 Section 2.1 requires the
969
+ * other kind to be tried when the hinted lookup misses), so a mislabelled
970
+ * token still resolves. An active access token carries
971
+ * `token_type: "Bearer"`; a refresh token carries no `token_type`, since
972
+ * that field describes how an access token is presented, and its `exp` is
973
+ * the refresh token's own expiry.
974
+ *
975
+ * A token with no user — the client acting as its own resource owner, as
976
+ * the client credentials grant issues — carries no `sub` and no `username`;
977
+ * `client_id` identifies the machine. `sub` is optional in RFC 7662, and
978
+ * leaving it absent keeps `sub` an unambiguous "there is a resource owner"
979
+ * signal rather than overloading `client_id` into the subject namespace
980
+ * (RFC 9700 §4.15.1).
981
+ *
982
+ * A wrong method, or a content type other than
983
+ * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
984
+ * token endpoint.
985
+ *
986
+ * @see https://datatracker.ietf.org/doc/html/rfc7662
987
+ */
988
+ async handleIntrospectionRequest(request) {
989
+ try {
990
+ const { context, body, client } = await this
991
+ .#beginClientAuthenticatedRequest(request);
992
+ const tokenValue = body.get("token");
993
+ if (typeof tokenValue !== "string") {
994
+ throw new InvalidRequestError("token parameter required");
995
+ }
996
+ const tokenTypeHint = body.get("token_type_hint");
997
+ const hint = typeof tokenTypeHint === "string" ? tokenTypeHint : null;
998
+ const { tokenService } = context.services;
999
+ let response;
1000
+ const resolved = await this.#resolveToken(tokenService, tokenValue, hint);
1001
+ const expiresAt = resolved && this.#introspectedExpiry(resolved);
1002
+ if (!resolved || (expiresAt && expiresAt < new Date()) ||
1003
+ (this.#canIntrospectToken &&
1004
+ await this.#canIntrospectToken(client, resolved.token, resolved.tokenType) !== true)) {
1005
+ response = { active: false };
1006
+ }
1007
+ else {
1008
+ const { token, tokenType } = resolved;
1009
+ response = {
1010
+ active: true,
1011
+ client_id: token.client.id,
1012
+ };
1013
+ if (tokenType === "access_token") {
1014
+ response.token_type = "Bearer";
1015
+ }
1016
+ if (token.scope) {
1017
+ response.scope = token.scope.toString();
1018
+ }
1019
+ if (expiresAt) {
1020
+ response.exp = Math.floor(expiresAt.getTime() / 1000);
1021
+ }
1022
+ if (context.issuer) {
1023
+ response.iss = context.issuer;
1024
+ }
1025
+ const user = token.user;
1026
+ if (user?.id) {
1027
+ response.sub = user.id;
1028
+ }
1029
+ if (user?.username) {
1030
+ response.username = user.username;
1031
+ }
1032
+ if (this.#introspectionClaims) {
1033
+ response = {
1034
+ ...await this.#introspectionClaims(token),
1035
+ ...response,
1036
+ };
1037
+ }
1038
+ }
1039
+ return new Response(JSON.stringify(response), {
1040
+ status: 200,
1041
+ headers: {
1042
+ "Content-Type": "application/json;charset=UTF-8",
1043
+ "Cache-Control": "no-store",
1044
+ "Pragma": "no-cache",
1045
+ },
1046
+ });
1047
+ }
1048
+ catch (error) {
1049
+ return this.handleError(error, this.prepareClientAuthError(request));
1050
+ }
1051
+ }
1052
+ /**
1053
+ * Gets OAuth2 server metadata (RFC 8414).
1054
+ *
1055
+ * Returns metadata about this authorization server including supported
1056
+ * grant types, endpoints, and capabilities.
1057
+ *
1058
+ * `code_challenge_methods_supported` is derived from the registered
1059
+ * authorization code grant's own `challengeMethods`, so a server that
1060
+ * registers an extra method (or drops `S256`) advertises what it will
1061
+ * actually accept. It is omitted entirely when no authorization code grant
1062
+ * is registered, since PKCE has nothing to apply to.
1063
+ *
1064
+ * `token_endpoint_auth_methods_supported` always includes `none`, the
1065
+ * RFC 7591 method for a client with no secret. The token endpoint accepts it
1066
+ * on every deployment and for every grant — a request carrying only
1067
+ * `client_id` reaches `ClientServiceInterface.getAuthenticated` with no
1068
+ * secret, which that interface's contract requires to resolve a public
1069
+ * client (an authorization-code exchange carrying a `code_verifier` looks
1070
+ * the client up with `get` instead when `requireClientAuthentication` is
1071
+ * explicitly `false`) — and there is no option that turns it off, so it is not
1072
+ * conditional on anything a caller configures. Which clients may use it is
1073
+ * decided per client, by whether the client has a secret; discovery
1074
+ * describes the endpoint, not the client population.
1075
+ *
1076
+ * @throws {ServerError} If the resolved context has no `issuer` — RFC 8414
1077
+ * makes it REQUIRED and it is the field clients validate against.
1078
+ * @see https://datatracker.ietf.org/doc/html/rfc8414#section-2
1079
+ * @see https://datatracker.ietf.org/doc/html/rfc7591#section-2
1080
+ */
1081
+ getMetadata(context) {
1082
+ const grants = Object.keys(this.grants);
1083
+ const hasAuthCodeGrant = grants.includes("authorization_code");
1084
+ if (!context.issuer) {
1085
+ throw new ServerError("issuer must be configured to serve authorization server metadata " +
1086
+ "(RFC 8414)");
1087
+ }
1088
+ const endpoints = this.#resolvedEndpoints(context);
1089
+ const metadata = {
1090
+ issuer: context.issuer,
1091
+ };
1092
+ this.#advertiseEndpoints(metadata, endpoints, false);
1093
+ metadata.grant_types_supported = grants;
1094
+ metadata.token_endpoint_auth_methods_supported = [
1095
+ "client_secret_basic",
1096
+ "client_secret_post",
1097
+ "none",
1098
+ ];
1099
+ const challengeMethods = this.#supportedChallengeMethods();
1100
+ if (challengeMethods.length) {
1101
+ metadata.code_challenge_methods_supported = challengeMethods;
1102
+ }
1103
+ metadata.response_types_supported = hasAuthCodeGrant ? ["code"] : [];
1104
+ if (this.scopesSupported) {
1105
+ metadata.scopes_supported = this.scopesSupported;
1106
+ }
1107
+ if (this.signingKeys) {
1108
+ this.#advertiseEndpoints(metadata, endpoints, true);
1109
+ metadata.id_token_signing_alg_values_supported = ["ES256"];
1110
+ metadata.subject_types_supported = ["public"];
1111
+ }
1112
+ return metadata;
1113
+ }
1114
+ #supportedChallengeMethods() {
1115
+ const grant = this.grants[AUTHORIZATION_CODE_GRANT_TYPE];
1116
+ if (!grant || !isAuthorizationCodeGrant(grant))
1117
+ return [];
1118
+ const { challengeMethods } = grant;
1119
+ return Object.keys(challengeMethods).filter((method) => typeof challengeMethods[method] === "function");
1120
+ }
1121
+ /** Publishes the resolved URLs of the OIDC (or plain OAuth2) endpoints. */
1122
+ #advertiseEndpoints(metadata, endpoints, oidc) {
1123
+ for (const [key, endpoint] of ADVERTISED_ENTRIES) {
1124
+ if (endpoint.oidc !== oidc)
1125
+ continue;
1126
+ if (key === "endSession" && !this.#endSession)
1127
+ continue;
1128
+ const url = endpoints[key];
1129
+ if (url)
1130
+ metadata[endpoint.metadata] = url;
1131
+ }
1132
+ }
1133
+ /**
1134
+ * Handles a metadata request (GET /.well-known/oauth-authorization-server).
1135
+ *
1136
+ * Returns the authorization server metadata as JSON per RFC 8414.
1137
+ *
1138
+ * @see https://datatracker.ietf.org/doc/html/rfc8414
1139
+ */
1140
+ async handleMetadataRequest(request) {
1141
+ try {
1142
+ const context = await this.authorizationContext(request);
1143
+ const metadata = this.getMetadata(context);
1144
+ return new Response(JSON.stringify(metadata), {
1145
+ status: 200,
1146
+ headers: {
1147
+ "Content-Type": "application/json;charset=UTF-8",
1148
+ },
1149
+ });
1150
+ }
1151
+ catch (error) {
1152
+ return this.handleError(error);
1153
+ }
1154
+ }
1155
+ /**
1156
+ * Handles a device authorization request (POST /device_authorization).
1157
+ *
1158
+ * Initiates the device authorization flow by generating a device_code
1159
+ * and user_code that the user can use to authorize the request on
1160
+ * a secondary device.
1161
+ *
1162
+ * Requires a grant extending {@linkcode DeviceAuthorizationGrant} registered
1163
+ * under `urn:ietf:params:oauth:grant-type:device_code`, and a
1164
+ * `verificationUri` on the resolved context; without either the endpoint
1165
+ * answers 500 `server_error`. A wrong method, or a content type other than
1166
+ * `application/x-www-form-urlencoded`, is a 400 `invalid_request`, as on the
1167
+ * token endpoint.
1168
+ *
1169
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
1170
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.2
1171
+ */
1172
+ async handleDeviceAuthorizationRequest(request) {
1173
+ try {
1174
+ const { context, body, client } = await this
1175
+ .#beginClientAuthenticatedRequest(request, {
1176
+ requiredGrantType: DEVICE_AUTHORIZATION_GRANT_TYPE,
1177
+ });
1178
+ const grant = this.#deviceAuthorizationGrant();
1179
+ const scopeText = body.get("scope");
1180
+ const scope = typeof scopeText === "string"
1181
+ ? grant.parseScope(scopeText)
1182
+ : undefined;
1183
+ // RFC 8628 §3.2 requires a verification_uri the user can visit.
1184
+ if (!context.verificationUri) {
1185
+ throw new ServerError("verificationUri must be configured for the device authorization " +
1186
+ "endpoint (RFC 8628)");
1187
+ }
1188
+ const authorization = await grant.initiateDeviceAuthorization(client, request, scope);
1189
+ const response = {
1190
+ device_code: authorization.deviceCode,
1191
+ user_code: authorization.userCode,
1192
+ verification_uri: context.verificationUri,
1193
+ expires_in: Math.floor((authorization.expiresAt.getTime() - Date.now()) / 1000),
1194
+ interval: authorization.interval,
1195
+ };
1196
+ return new Response(JSON.stringify(response), {
1197
+ status: 200,
1198
+ headers: {
1199
+ "Content-Type": "application/json;charset=UTF-8",
1200
+ "Cache-Control": "no-store",
1201
+ "Pragma": "no-cache",
1202
+ },
1203
+ });
1204
+ }
1205
+ catch (error) {
1206
+ return this.handleError(error, this.prepareClientAuthError(request));
1207
+ }
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Returns a `fetch`-compatible function that dispatches requests directly
1212
+ * to the {@link AuthorizationServer}'s handlers in-process, bypassing the
1213
+ * network.
1214
+ *
1215
+ * Use this to point a `DirectClient` at a co-located authorization
1216
+ * server — the request flow becomes a plain method call, so token refresh
1217
+ * (and every other client → server interaction) stays in-process.
1218
+ *
1219
+ * Routing is by pathname against the endpoints the server resolves for the
1220
+ * request (so it follows per-request issuer/origin):
1221
+ * - `POST {tokenEndpoint}` → `handleTokenRequest`
1222
+ * - `POST {revocationEndpoint}` → `handleRevocationRequest`
1223
+ * - `POST {introspectionEndpoint}` → `handleIntrospectionRequest`
1224
+ * - `POST {deviceAuthorizationEndpoint}` → `handleDeviceAuthorizationRequest`
1225
+ * - `GET /.well-known/oauth-authorization-server` → `handleMetadataRequest`
1226
+ *
1227
+ * The authorization endpoint is deliberately **not** routed: `/authorize`
1228
+ * needs an `authenticateUser` (and optional `handleConsent`) callback this
1229
+ * transport has no access to, and in real flows it's a browser navigation,
1230
+ * never a call through the client's `fetch`. A request to it throws an
1231
+ * actionable error pointing at `handleAuthorizeRequest`. Any other path also
1232
+ * throws — the helper intentionally does not proxy arbitrary paths; if a call
1233
+ * lands here unexpectedly, that's a wiring bug worth surfacing rather than
1234
+ * silently forwarding.
1235
+ */
1236
+ export function localAuthServerFetch(authServer) {
1237
+ return async (input, init) => {
1238
+ const request = input instanceof Request
1239
+ ? (init ? new Request(input, init) : input)
1240
+ : new Request(String(input), init);
1241
+ const url = new URL(request.url);
1242
+ const path = url.pathname;
1243
+ const method = request.method.toUpperCase();
1244
+ const endpoints = await authServer.resolveEndpoints(request);
1245
+ const pathOf = (endpoint) => endpoint ? new URL(endpoint).pathname : undefined;
1246
+ const handlers = {
1247
+ authorization: null,
1248
+ token: (request) => authServer.handleTokenRequest(request),
1249
+ revocation: (request) => authServer.handleRevocationRequest(request),
1250
+ introspection: (request) => authServer.handleIntrospectionRequest(request),
1251
+ deviceAuthorization: (request) => authServer.handleDeviceAuthorizationRequest(request),
1252
+ jwks: (request) => authServer.handleJwksRequest(request),
1253
+ userinfo: (request) => authServer.handleUserInfoRequest(request),
1254
+ endSession: null,
1255
+ };
1256
+ for (const [key, { methods }] of ADVERTISED_ENTRIES) {
1257
+ const handler = handlers[key];
1258
+ if (!handler || !methods.includes(method))
1259
+ continue;
1260
+ if (path === pathOf(endpoints[key]))
1261
+ return await handler(request);
1262
+ }
1263
+ if (method === "GET" && path === ENDPOINT_PATHS.metadata) {
1264
+ return await authServer.handleMetadataRequest(request);
1265
+ }
1266
+ if (method === "GET" && path === ENDPOINT_PATHS.oidcMetadata) {
1267
+ return await authServer.handleOidcMetadataRequest(request);
1268
+ }
1269
+ const endSessionPath = pathOf(endpoints.endSession);
1270
+ if (endSessionPath && path === endSessionPath) {
1271
+ throw new Error(`localAuthServerFetch cannot dispatch the end-session endpoint ` +
1272
+ `(${method} ${path}). RP-initiated logout requires an endSession ` +
1273
+ `callback this transport has no access to — only the app knows how ` +
1274
+ `to end its own session — and in real flows it is a browser ` +
1275
+ `navigation, not a client fetch. Drive it directly via ` +
1276
+ `server.handleEndSessionRequest(request, endSession).`);
1277
+ }
1278
+ const authorizePath = pathOf(endpoints.authorization);
1279
+ if (authorizePath && path === authorizePath) {
1280
+ throw new Error(`localAuthServerFetch cannot dispatch the authorization endpoint ` +
1281
+ `(${method} ${path}). /authorize requires an authenticateUser ` +
1282
+ `(and optional handleConsent) callback this transport has no ` +
1283
+ `access to, and in real flows it is a browser navigation, not a ` +
1284
+ `client fetch. Drive it directly via ` +
1285
+ `server.handleAuthorizeRequest(request, authenticateUser) or the ` +
1286
+ `createMemoryAuthorizationServer() harness's authorize() helper.`);
1287
+ }
1288
+ throw new Error(`localAuthServerFetch: no handler for ${method} ${path}. ` +
1289
+ `Configure the authorization server with explicit endpoint URLs ` +
1290
+ `and point your client at the matching paths.`);
1291
+ };
1292
+ }
1293
+ export { AbstractGrant } from "./grants/grant.js";
1294
+ export { AuthorizationCodeGrant } from "./grants/authorization-code.js";
1295
+ export { ClientCredentialsGrant } from "./grants/client-credentials.js";
1296
+ export { createJwtAccessTokenGenerator, exportSigningKeyJwk, generateSigningKey, importSigningKeyJwk, RotatingSigningKeyProvider, signJwt, StaticSigningKeyProvider, verifyJwt, } from "./signing-keys.js";
1297
+ export { RefreshTokenGrant } from "./grants/refresh-token.js";
1298
+ export { PasswordGrant } from "./grants/password.js";
1299
+ export { DeviceAuthorizationGrant } from "./grants/device-authorization.js";
1300
+ export { AbstractTokenService, } from "./services/token.js";
1301
+ export { AbstractAuthorizationCodeService, } from "./services/authorization-code.js";
1302
+ export { challengeMethods, CODE_VERIFIER_MAX_LENGTH, CODE_VERIFIER_MIN_LENGTH, CODE_VERIFIER_PATTERN, generateCodeChallenge, generateCodeVerifier, generateState, getChallengeMethod, validateCodeChallenge, validateCodeVerifier, verifyCodeChallenge, } from "../utils/pkce.js";
1303
+ export { encodeBasicAuth, parseBasicAuth, tryParseBasicAuth, } from "../utils/basic-auth.js";
1304
+ export { checkRedirectUriPattern, isRedirectUriPattern, matchRedirectUri, } from "./redirect-uri.js";