@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,66 @@
1
+ /**
2
+ * Request logging that removes query values, including OAuth callback credentials.
3
+ *
4
+ * @module hono/log
5
+ */
6
+ import "../../_dnt.polyfills.js";
7
+ /**
8
+ * Produces a request target with every query value replaced by `[redacted]`.
9
+ * Keeps the pathname and percent-encoded parameter names, including repeats,
10
+ * and drops the origin and fragment. Use it before recording a request URL.
11
+ * Paths and parameter names must themselves contain no secrets.
12
+ *
13
+ * @param url An absolute request URL.
14
+ * @returns The pathname followed by redacted query parameters, if any.
15
+ * @throws TypeError if `url` is not a valid absolute URL.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * import { redactedRequestTarget } from "@udibo/oauth2/hono/log";
20
+ *
21
+ * const target = redactedRequestTarget(
22
+ * "https://app.example.com/auth/callback?code=secret&state=nonce",
23
+ * );
24
+ * // "/auth/callback?code=[redacted]&state=[redacted]"
25
+ * ```
26
+ */
27
+ export function redactedRequestTarget(url) {
28
+ const { pathname, searchParams } = new URL(url);
29
+ const names = [...searchParams.keys()];
30
+ if (names.length === 0)
31
+ return pathname;
32
+ const query = names
33
+ .map((name) => `${encodeURIComponent(name)}=[redacted]`)
34
+ .join("&");
35
+ return `${pathname}?${query}`;
36
+ }
37
+ /**
38
+ * Logs Hono requests and responses without any query values. Mount before
39
+ * authentication routes so OAuth callback codes and state stay out of logs.
40
+ * Logs method, redacted target, response status, and elapsed milliseconds;
41
+ * does not log headers or bodies. Paths and parameter names remain visible.
42
+ *
43
+ * @param print Receives each log line; defaults to `console.log`, resolved per line.
44
+ * @returns Middleware emitting a request line and, after downstream completion,
45
+ * a response line.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * import { Hono } from "hono";
50
+ * import { requestLogger } from "@udibo/oauth2/hono/log";
51
+ *
52
+ * const app = new Hono();
53
+ * app.use(requestLogger());
54
+ * app.get("/", (c) => c.text("Hello"));
55
+ * ```
56
+ */
57
+ export function requestLogger(print) {
58
+ const emit = print ?? ((line) => console.log(line));
59
+ return async (c, next) => {
60
+ const target = redactedRequestTarget(c.req.url);
61
+ emit(`<-- ${c.req.method} ${target}`);
62
+ const start = Date.now();
63
+ await next();
64
+ emit(`--> ${c.req.method} ${target} ${c.res.status} ${Date.now() - start}ms`);
65
+ };
66
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Hono adapter for the core {@link ResourceServer}.
3
+ *
4
+ * Provides middleware and accessors that let a Hono app authenticate
5
+ * requests against an OAuth2 authorization server. The core server
6
+ * implementation is framework-agnostic; this adapter is the only place that
7
+ * knows about `Context` and middleware wiring.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Hono } from "hono";
12
+ * import {
13
+ * HonoResourceServer,
14
+ * type HonoResourceServerVariables,
15
+ * } from "@udibo/oauth2/hono/resource-server";
16
+ *
17
+ * const resourceServer = new HonoResourceServer({
18
+ * resolve: () => ({ services: { tokenService } }),
19
+ * });
20
+ * const app = new Hono<{
21
+ * Variables: HonoResourceServerVariables<Client, User>;
22
+ * }>();
23
+ *
24
+ * app.use("/api/*", resourceServer.protect());
25
+ * app.get("/api/me", (c) => c.json(resourceServer.getContext(c).user));
26
+ * ```
27
+ *
28
+ * @module
29
+ */
30
+ import "../../_dnt.polyfills.js";
31
+ import type { Context, MiddlewareHandler } from "hono";
32
+ import type { ClientInterface } from "../../models/client.js";
33
+ import type { AbstractScope, BasicScope } from "../../models/scope.js";
34
+ import { type AuthenticatedContext, ResourceServer } from "../../server/resource-server.js";
35
+ import type { RequireConditions } from "../../models/authorization.js";
36
+ export { type HonoResourceServerVariables, OAUTH2_CONTEXT_KEY, } from "./_common.js";
37
+ /**
38
+ * Hono-specific wrapper around {@link ResourceServer}. Construction options
39
+ * are identical to the core server; the subclass only adds Hono middleware
40
+ * helpers.
41
+ */
42
+ export declare class HonoResourceServer<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> extends ResourceServer<Client, User, S> {
43
+ /**
44
+ * Returns a Hono middleware that validates the bearer token.
45
+ *
46
+ * On success, sets the authenticated context at `c.get("@udibo/oauth2")`
47
+ * (typed via {@link HonoResourceServerVariables}) and calls `next()`. On
48
+ * failure, returns an RFC 6749 error response with a `WWW-Authenticate`
49
+ * header per RFC 6750 Section 3.
50
+ */
51
+ protect(requiredScope?: S | string): MiddlewareHandler;
52
+ /**
53
+ * Returns a Hono middleware that asserts the **already-authenticated**
54
+ * request carries `requiredScope`, **without** re-validating the token.
55
+ *
56
+ * Mount it downstream of {@link protect} (or a BFF `protect`, which sets the
57
+ * same context) when one authenticated mount exposes routes needing
58
+ * different scopes — e.g. `read` on GETs, `write` on mutations — that a
59
+ * single `protect(scope)` can't express. It reads the context `protect`
60
+ * already populated and checks the scope in memory, so unlike a second
61
+ * `protect(scope)` it performs no token lookup. On insufficient scope it
62
+ * returns the same RFC 6750 `insufficient_scope` response (403 with a
63
+ * `WWW-Authenticate` header) as `protect`.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * app.use("/api/*", resourceServer.protect()); // authenticate once
68
+ * app.get("/api/items", resourceServer.requireScope("read"), list);
69
+ * app.post("/api/items", resourceServer.requireScope("write"), create);
70
+ * ```
71
+ */
72
+ requireScope(requiredScope: S | string): MiddlewareHandler;
73
+ /**
74
+ * Middleware asserting the already-authenticated request satisfies every
75
+ * condition — AND semantics across keys, arrays mean all-of (see
76
+ * {@linkcode RequireConditions}). Scope failures answer the RFC 6750
77
+ * `insufficient_scope` challenge exactly as {@linkcode requireScope} does;
78
+ * permission, role, and organization failures answer a plain 403. Mount a
79
+ * {@linkcode protect} first — this middleware does not validate the token.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * app.use("/api/*", resourceServer.protect());
84
+ * app.post(
85
+ * "/api/posts",
86
+ * resourceServer.require({ scope: "posts:write", permission: "posts:write" }),
87
+ * create,
88
+ * );
89
+ * ```
90
+ */
91
+ require(conditions: RequireConditions): MiddlewareHandler;
92
+ /**
93
+ * Authenticates a Hono {@link Context} or a raw {@link Request} manually,
94
+ * for handlers that want to authenticate inline instead of using
95
+ * {@link protect} as middleware. Throws on failure — pair with
96
+ * {@link ResourceServer.handleAuthError} to convert to a response.
97
+ */
98
+ authenticate(c: Context | Request, requiredScope?: S | string): Promise<AuthenticatedContext<Client, User, S>>;
99
+ /** Typed accessor for the authenticated OAuth2 context. */
100
+ getContext(c: Context): AuthenticatedContext<Client, User, S>;
101
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Hono adapter for the core {@link ResourceServer}.
3
+ *
4
+ * Provides middleware and accessors that let a Hono app authenticate
5
+ * requests against an OAuth2 authorization server. The core server
6
+ * implementation is framework-agnostic; this adapter is the only place that
7
+ * knows about `Context` and middleware wiring.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Hono } from "hono";
12
+ * import {
13
+ * HonoResourceServer,
14
+ * type HonoResourceServerVariables,
15
+ * } from "@udibo/oauth2/hono/resource-server";
16
+ *
17
+ * const resourceServer = new HonoResourceServer({
18
+ * resolve: () => ({ services: { tokenService } }),
19
+ * });
20
+ * const app = new Hono<{
21
+ * Variables: HonoResourceServerVariables<Client, User>;
22
+ * }>();
23
+ *
24
+ * app.use("/api/*", resourceServer.protect());
25
+ * app.get("/api/me", (c) => c.json(resourceServer.getContext(c).user));
26
+ * ```
27
+ *
28
+ * @module
29
+ */
30
+ import "../../_dnt.polyfills.js";
31
+ import { ResourceServer, } from "../../server/resource-server.js";
32
+ import { authenticateHonoRequest, createProtectMiddleware, createRequireMiddleware, createRequireScopeMiddleware, readContext, } from "./_common.js";
33
+ export { OAUTH2_CONTEXT_KEY, } from "./_common.js";
34
+ /**
35
+ * Hono-specific wrapper around {@link ResourceServer}. Construction options
36
+ * are identical to the core server; the subclass only adds Hono middleware
37
+ * helpers.
38
+ */
39
+ export class HonoResourceServer extends ResourceServer {
40
+ /**
41
+ * Returns a Hono middleware that validates the bearer token.
42
+ *
43
+ * On success, sets the authenticated context at `c.get("@udibo/oauth2")`
44
+ * (typed via {@link HonoResourceServerVariables}) and calls `next()`. On
45
+ * failure, returns an RFC 6749 error response with a `WWW-Authenticate`
46
+ * header per RFC 6750 Section 3.
47
+ */
48
+ protect(requiredScope) {
49
+ return createProtectMiddleware(this, requiredScope);
50
+ }
51
+ /**
52
+ * Returns a Hono middleware that asserts the **already-authenticated**
53
+ * request carries `requiredScope`, **without** re-validating the token.
54
+ *
55
+ * Mount it downstream of {@link protect} (or a BFF `protect`, which sets the
56
+ * same context) when one authenticated mount exposes routes needing
57
+ * different scopes — e.g. `read` on GETs, `write` on mutations — that a
58
+ * single `protect(scope)` can't express. It reads the context `protect`
59
+ * already populated and checks the scope in memory, so unlike a second
60
+ * `protect(scope)` it performs no token lookup. On insufficient scope it
61
+ * returns the same RFC 6750 `insufficient_scope` response (403 with a
62
+ * `WWW-Authenticate` header) as `protect`.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * app.use("/api/*", resourceServer.protect()); // authenticate once
67
+ * app.get("/api/items", resourceServer.requireScope("read"), list);
68
+ * app.post("/api/items", resourceServer.requireScope("write"), create);
69
+ * ```
70
+ */
71
+ requireScope(requiredScope) {
72
+ return createRequireScopeMiddleware(this, "HonoResourceServer", requiredScope);
73
+ }
74
+ /**
75
+ * Middleware asserting the already-authenticated request satisfies every
76
+ * condition — AND semantics across keys, arrays mean all-of (see
77
+ * {@linkcode RequireConditions}). Scope failures answer the RFC 6750
78
+ * `insufficient_scope` challenge exactly as {@linkcode requireScope} does;
79
+ * permission, role, and organization failures answer a plain 403. Mount a
80
+ * {@linkcode protect} first — this middleware does not validate the token.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * app.use("/api/*", resourceServer.protect());
85
+ * app.post(
86
+ * "/api/posts",
87
+ * resourceServer.require({ scope: "posts:write", permission: "posts:write" }),
88
+ * create,
89
+ * );
90
+ * ```
91
+ */
92
+ require(conditions) {
93
+ return createRequireMiddleware(this, "HonoResourceServer", conditions);
94
+ }
95
+ /**
96
+ * Authenticates a Hono {@link Context} or a raw {@link Request} manually,
97
+ * for handlers that want to authenticate inline instead of using
98
+ * {@link protect} as middleware. Throws on failure — pair with
99
+ * {@link ResourceServer.handleAuthError} to convert to a response.
100
+ */
101
+ authenticate(c, requiredScope) {
102
+ return authenticateHonoRequest(this, c, requiredScope);
103
+ }
104
+ /** Typed accessor for the authenticated OAuth2 context. */
105
+ getContext(c) {
106
+ return readContext(c, "HonoResourceServer");
107
+ }
108
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The response ladder every {@link DirectClient} network call climbs: bounded
3
+ * read, deadline, redirect refusal, JSON validation, RFC 6749 §5.2 error
4
+ * mapping.
5
+ *
6
+ * Not part of the public API. The authorization server's bytes become either a
7
+ * typed {@link OAuth2Error} or a validated JSON object here and nowhere else,
8
+ * so the guards sit next to the tests that pin them.
9
+ *
10
+ * @module
11
+ */
12
+ import { type OAuth2Error } from "../errors.js";
13
+ /** Response bytes accepted from an endpoint before the call is refused. */
14
+ export declare const MAX_RESPONSE_BYTES: number;
15
+ /** Wall-clock budget for one call, covering the connect and the body read. */
16
+ export declare const REQUEST_TIMEOUT_MS = 10000;
17
+ /** How a call describes itself in an error message, e.g. `"token endpoint"`. */
18
+ export type Endpoint = string;
19
+ /** How {@link sendGuarded} treats a `3xx`. */
20
+ export type RedirectPolicy = "refuse" | "follow";
21
+ /**
22
+ * Sends one request with a deadline.
23
+ *
24
+ * With the default `"refuse"` policy a `3xx` throws rather than being
25
+ * followed: these requests carry client credentials, an authorization code, a
26
+ * refresh token, or a bearer token, and replaying any of them at whatever host
27
+ * the response names would hand the secret to that host. Discovery passes
28
+ * `"follow"` — it sends no credential, and the mix-up it could otherwise
29
+ * enable is closed by validating the `issuer` the document reports.
30
+ *
31
+ * @param fetchImpl The client's resolved `fetch`.
32
+ * @param url Where to send it.
33
+ * @param init Request options; `redirect` and `signal` are supplied here. A
34
+ * caller-supplied `signal` is honoured *in addition to* the deadline.
35
+ * @param what The endpoint's name, for error messages.
36
+ * @param options `redirect` policy and `timeoutMs` deadline.
37
+ * @returns The response, which may still be a non-OK status.
38
+ * @throws {ServerError} when the request cannot be made, times out, or (under
39
+ * `"refuse"`) answers with a redirect.
40
+ */
41
+ export declare function sendGuarded(fetchImpl: typeof fetch, url: string, init: RequestInit, what: Endpoint, options?: {
42
+ redirect?: RedirectPolicy;
43
+ timeoutMs?: number;
44
+ }): Promise<Response>;
45
+ /**
46
+ * Reads a response as a JSON object, refusing anything else.
47
+ *
48
+ * A non-OK status becomes the typed {@link OAuth2Error} the body names, or a
49
+ * {@link ServerError} carrying the status when it names nothing.
50
+ *
51
+ * @param res The response from {@link sendGuarded}.
52
+ * @param what The endpoint's name, for error messages.
53
+ * @returns The parsed JSON object.
54
+ * @throws {OAuth2Error} on a non-OK status, an oversized body, a body that is
55
+ * not valid JSON, or JSON that is not an object.
56
+ */
57
+ export declare function receiveJson(res: Response, what: Endpoint): Promise<Record<string, unknown>>;
58
+ /**
59
+ * Turns a non-OK response into the typed error its body names. Use for
60
+ * endpoints whose success case carries no JSON (RFC 7009 revocation).
61
+ *
62
+ * @param res A non-OK response from {@link sendGuarded}.
63
+ * @param what The endpoint's name, for error messages.
64
+ * @returns The error to throw.
65
+ */
66
+ export declare function errorFromResponse(res: Response, what: Endpoint): Promise<OAuth2Error>;
67
+ /**
68
+ * Enforces RFC 6749 §5.2 on a token response that arrived with a `2xx`.
69
+ *
70
+ * A server may report a failure in the body of a `200`; and a body with no
71
+ * usable `access_token` is not a token response no matter what it says. Both
72
+ * must throw here, because the alternative is persisting a bundle whose
73
+ * `accessToken` is `undefined` and then sending every subsequent request with
74
+ * no `Authorization` header at all.
75
+ *
76
+ * @param body The parsed token-endpoint response.
77
+ * @param what The endpoint's name, for error messages.
78
+ * @throws {OAuth2Error} when the body reports an error or carries no non-empty
79
+ * string `access_token`.
80
+ */
81
+ export declare function assertTokenResponse(body: Record<string, unknown>, what: Endpoint): void;
82
+ /**
83
+ * Requires a named non-empty string field on a `2xx` response body, and
84
+ * rejects a body that reports an OAuth2 error alongside its `200`.
85
+ *
86
+ * @param body The parsed response.
87
+ * @param field The field the caller cannot proceed without.
88
+ * @param what The endpoint's name, for error messages.
89
+ * @throws {OAuth2Error} when the body reports an error or lacks the field.
90
+ */
91
+ export declare function assertField(body: Record<string, unknown>, field: string, what: Endpoint): void;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * The response ladder every {@link DirectClient} network call climbs: bounded
3
+ * read, deadline, redirect refusal, JSON validation, RFC 6749 §5.2 error
4
+ * mapping.
5
+ *
6
+ * Not part of the public API. The authorization server's bytes become either a
7
+ * typed {@link OAuth2Error} or a validated JSON object here and nowhere else,
8
+ * so the guards sit next to the tests that pin them.
9
+ *
10
+ * @module
11
+ */
12
+ import { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InsufficientScopeError, InvalidClientError, InvalidGrantError, InvalidRequestError, InvalidScopeError, InvalidTokenError, OAuth2Error as OAuth2ErrorClass, ServerError, SlowDownError, TemporarilyUnavailableError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError, } from "../errors.js";
13
+ import { sanitizeProviderText } from "../utils/text.js";
14
+ /** Response bytes accepted from an endpoint before the call is refused. */
15
+ export const MAX_RESPONSE_BYTES = 64 * 1024;
16
+ /** Wall-clock budget for one call, covering the connect and the body read. */
17
+ export const REQUEST_TIMEOUT_MS = 10_000;
18
+ /**
19
+ * Maps an OAuth2 error code to the specific error class so callers can
20
+ * `catch (e) { if (e instanceof InvalidGrantError) … }` instead of
21
+ * inspecting `e.extensions.error` at every call site.
22
+ */
23
+ const ERROR_CLASS_BY_CODE = {
24
+ access_denied: AccessDeniedError,
25
+ authorization_pending: AuthorizationPendingError,
26
+ expired_token: ExpiredTokenError,
27
+ insufficient_scope: InsufficientScopeError,
28
+ invalid_client: InvalidClientError,
29
+ invalid_grant: InvalidGrantError,
30
+ invalid_request: InvalidRequestError,
31
+ invalid_scope: InvalidScopeError,
32
+ invalid_token: InvalidTokenError,
33
+ server_error: ServerError,
34
+ slow_down: SlowDownError,
35
+ temporarily_unavailable: TemporarilyUnavailableError,
36
+ unauthorized_client: UnauthorizedClientError,
37
+ unsupported_grant_type: UnsupportedGrantTypeError,
38
+ unsupported_response_type: UnsupportedResponseTypeError,
39
+ unsupported_token_type: UnsupportedTokenTypeError,
40
+ };
41
+ /**
42
+ * Sends one request with a deadline.
43
+ *
44
+ * With the default `"refuse"` policy a `3xx` throws rather than being
45
+ * followed: these requests carry client credentials, an authorization code, a
46
+ * refresh token, or a bearer token, and replaying any of them at whatever host
47
+ * the response names would hand the secret to that host. Discovery passes
48
+ * `"follow"` — it sends no credential, and the mix-up it could otherwise
49
+ * enable is closed by validating the `issuer` the document reports.
50
+ *
51
+ * @param fetchImpl The client's resolved `fetch`.
52
+ * @param url Where to send it.
53
+ * @param init Request options; `redirect` and `signal` are supplied here. A
54
+ * caller-supplied `signal` is honoured *in addition to* the deadline.
55
+ * @param what The endpoint's name, for error messages.
56
+ * @param options `redirect` policy and `timeoutMs` deadline.
57
+ * @returns The response, which may still be a non-OK status.
58
+ * @throws {ServerError} when the request cannot be made, times out, or (under
59
+ * `"refuse"`) answers with a redirect.
60
+ */
61
+ export async function sendGuarded(fetchImpl, url, init, what, options = {}) {
62
+ const redirect = options.redirect ?? "refuse";
63
+ const deadline = AbortSignal.timeout(options.timeoutMs ?? REQUEST_TIMEOUT_MS);
64
+ let res;
65
+ try {
66
+ res = await fetchImpl(url, {
67
+ ...init,
68
+ redirect: redirect === "refuse" ? "manual" : "follow",
69
+ signal: init.signal ? AbortSignal.any([init.signal, deadline]) : deadline,
70
+ });
71
+ }
72
+ catch (error) {
73
+ throw new ServerError(`could not reach the ${what} (${describeError(error)})`, { cause: error });
74
+ }
75
+ if (redirect === "refuse" && res.status >= 300 && res.status < 400) {
76
+ await res.body?.cancel();
77
+ throw new ServerError(`the ${what} answered with a redirect (HTTP ${res.status}), which this ` +
78
+ `client refuses to follow — replaying the request to another host ` +
79
+ `would leak the credentials it carries`);
80
+ }
81
+ return res;
82
+ }
83
+ /**
84
+ * Reads a response as a JSON object, refusing anything else.
85
+ *
86
+ * A non-OK status becomes the typed {@link OAuth2Error} the body names, or a
87
+ * {@link ServerError} carrying the status when it names nothing.
88
+ *
89
+ * @param res The response from {@link sendGuarded}.
90
+ * @param what The endpoint's name, for error messages.
91
+ * @returns The parsed JSON object.
92
+ * @throws {OAuth2Error} on a non-OK status, an oversized body, a body that is
93
+ * not valid JSON, or JSON that is not an object.
94
+ */
95
+ export async function receiveJson(res, what) {
96
+ const body = await readBounded(res);
97
+ if (!res.ok)
98
+ throw errorFromBody(res, safeParse(body.text), what, body.text);
99
+ if (body.truncated) {
100
+ throw new ServerError(`the ${what} response exceeded ${MAX_RESPONSE_BYTES} bytes — refusing ` +
101
+ `to parse a response this large`);
102
+ }
103
+ let parsed;
104
+ try {
105
+ parsed = JSON.parse(body.text);
106
+ }
107
+ catch (error) {
108
+ throw new ServerError(`the ${what} returned ${describeMediaType(res)} that was not valid ` +
109
+ `JSON (${describeError(error)})`, { cause: error });
110
+ }
111
+ if (!isJsonObject(parsed)) {
112
+ throw new ServerError(`the ${what} returned ${describeJsonValue(parsed)} where a JSON ` +
113
+ `object was required`);
114
+ }
115
+ return parsed;
116
+ }
117
+ /**
118
+ * Turns a non-OK response into the typed error its body names. Use for
119
+ * endpoints whose success case carries no JSON (RFC 7009 revocation).
120
+ *
121
+ * @param res A non-OK response from {@link sendGuarded}.
122
+ * @param what The endpoint's name, for error messages.
123
+ * @returns The error to throw.
124
+ */
125
+ export async function errorFromResponse(res, what) {
126
+ const body = await readBounded(res);
127
+ return errorFromBody(res, safeParse(body.text), what, body.text);
128
+ }
129
+ /**
130
+ * Enforces RFC 6749 §5.2 on a token response that arrived with a `2xx`.
131
+ *
132
+ * A server may report a failure in the body of a `200`; and a body with no
133
+ * usable `access_token` is not a token response no matter what it says. Both
134
+ * must throw here, because the alternative is persisting a bundle whose
135
+ * `accessToken` is `undefined` and then sending every subsequent request with
136
+ * no `Authorization` header at all.
137
+ *
138
+ * @param body The parsed token-endpoint response.
139
+ * @param what The endpoint's name, for error messages.
140
+ * @throws {OAuth2Error} when the body reports an error or carries no non-empty
141
+ * string `access_token`.
142
+ */
143
+ export function assertTokenResponse(body, what) {
144
+ const reported = reportedError(body);
145
+ if (reported)
146
+ throw buildError(reported);
147
+ const accessToken = body.access_token;
148
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
149
+ throw new ServerError(`the ${what} returned a success response with no "access_token"`);
150
+ }
151
+ }
152
+ /**
153
+ * Requires a named non-empty string field on a `2xx` response body, and
154
+ * rejects a body that reports an OAuth2 error alongside its `200`.
155
+ *
156
+ * @param body The parsed response.
157
+ * @param field The field the caller cannot proceed without.
158
+ * @param what The endpoint's name, for error messages.
159
+ * @throws {OAuth2Error} when the body reports an error or lacks the field.
160
+ */
161
+ export function assertField(body, field, what) {
162
+ const reported = reportedError(body);
163
+ if (reported)
164
+ throw buildError(reported);
165
+ const value = body[field];
166
+ if (typeof value !== "string" || value.length === 0) {
167
+ throw new ServerError(`the ${what} returned a success response with no "${field}"`);
168
+ }
169
+ }
170
+ /**
171
+ * Reads up to {@link MAX_RESPONSE_BYTES} and reports whether more was coming.
172
+ *
173
+ * Never throws for being too large: on a **failed** response the bytes are the
174
+ * only place the OAuth2 error code lives, and refusing to look would turn a
175
+ * `invalid_grant` that happens to arrive oversized into a generic transport
176
+ * error — which reads as "retry later" instead of "this grant is dead", so the
177
+ * client would keep presenting a revoked refresh token forever. Truncation is
178
+ * reported instead, and the success path refuses it.
179
+ */
180
+ async function readBounded(res) {
181
+ if (!res.body)
182
+ return { text: "", truncated: false };
183
+ const reader = res.body.getReader();
184
+ const chunks = [];
185
+ let size = 0;
186
+ let truncated = false;
187
+ try {
188
+ while (true) {
189
+ const { done, value } = await reader.read();
190
+ if (done)
191
+ break;
192
+ if (size + value.byteLength > MAX_RESPONSE_BYTES) {
193
+ const room = MAX_RESPONSE_BYTES - size;
194
+ if (room > 0) {
195
+ chunks.push(value.subarray(0, room));
196
+ size += room;
197
+ }
198
+ truncated = true;
199
+ break;
200
+ }
201
+ chunks.push(value);
202
+ size += value.byteLength;
203
+ }
204
+ }
205
+ catch {
206
+ return { text: decode(chunks, size), truncated: true };
207
+ }
208
+ finally {
209
+ await reader.cancel().catch(() => { });
210
+ }
211
+ return { text: decode(chunks, size), truncated };
212
+ }
213
+ function decode(chunks, size) {
214
+ const buffer = new Uint8Array(size);
215
+ let offset = 0;
216
+ for (const chunk of chunks) {
217
+ buffer.set(chunk, offset);
218
+ offset += chunk.byteLength;
219
+ }
220
+ return new TextDecoder().decode(buffer);
221
+ }
222
+ function reportedError(value) {
223
+ if (!isJsonObject(value))
224
+ return null;
225
+ const error = value.error;
226
+ if (typeof error !== "string" || error.length === 0)
227
+ return null;
228
+ return {
229
+ error: sanitizeProviderText(error),
230
+ description: typeof value.error_description === "string"
231
+ ? sanitizeProviderText(value.error_description)
232
+ : undefined,
233
+ uri: typeof value.error_uri === "string"
234
+ ? sanitizeProviderText(value.error_uri)
235
+ : undefined,
236
+ };
237
+ }
238
+ /**
239
+ * `status` is passed through only when it is a real error status: a server
240
+ * that reports `error` inside a `200` still needs a throwable, and `HttpError`
241
+ * refuses to carry a 2xx.
242
+ */
243
+ function buildError(reported, status) {
244
+ const ErrorClass = ERROR_CLASS_BY_CODE[reported.error] ?? OAuth2ErrorClass;
245
+ return new ErrorClass(reported.description ?? reported.error, {
246
+ status: status !== undefined && status >= 400 ? status : undefined,
247
+ extensions: {
248
+ error: reported.error,
249
+ error_description: reported.description,
250
+ error_uri: reported.uri,
251
+ },
252
+ });
253
+ }
254
+ /**
255
+ * Recovers the `error` code from a body that did not parse.
256
+ *
257
+ * The realistic reason a failed token response is unparseable here is that the
258
+ * cap cut it mid-string — a server padding `error_description`, or a proxy
259
+ * appending a page of HTML. The code itself is short and near the front, and
260
+ * losing it costs the caller the difference between "this grant is dead" and
261
+ * "something went wrong", so it is worth one bounded scan. Restricted to the
262
+ * lowercase-underscore shape RFC 6749 §5.2 defines, so nothing else matches.
263
+ */
264
+ function scanErrorCode(text) {
265
+ const match = /"error"\s*:\s*"([a-z_]{1,64})"/.exec(text);
266
+ return match ? { error: match[1] } : null;
267
+ }
268
+ function errorFromBody(res, parsed, what, raw = "") {
269
+ const reported = reportedError(parsed) ?? scanErrorCode(raw);
270
+ if (reported)
271
+ return buildError(reported, res.status);
272
+ return new ServerError(`${what}: HTTP ${res.status}`, res.status >= 400 ? { status: res.status } : undefined);
273
+ }
274
+ function safeParse(text) {
275
+ try {
276
+ return JSON.parse(text);
277
+ }
278
+ catch {
279
+ return undefined;
280
+ }
281
+ }
282
+ function isJsonObject(value) {
283
+ return typeof value === "object" && value !== null && !Array.isArray(value);
284
+ }
285
+ function describeJsonValue(value) {
286
+ if (value === null)
287
+ return "JSON null";
288
+ if (Array.isArray(value))
289
+ return "a JSON array";
290
+ return `a JSON ${typeof value}`;
291
+ }
292
+ function describeMediaType(res) {
293
+ const contentType = sanitizeProviderText(res.headers.get("content-type")?.split(";")[0] ?? "");
294
+ return contentType ? `a "${contentType}" response` : "a response";
295
+ }
296
+ function describeError(error) {
297
+ if (error instanceof Error) {
298
+ if (error.name === "TimeoutError")
299
+ return "timed out";
300
+ return sanitizeProviderText(error.message);
301
+ }
302
+ return sanitizeProviderText(String(error));
303
+ }