@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,103 @@
1
+ /**
2
+ * Device Authorization Grant implementation (RFC 8628).
3
+ *
4
+ * This grant type allows devices with limited input capabilities
5
+ * to obtain user authorization by having the user authorize
6
+ * the request on a secondary device.
7
+ *
8
+ * @see https://datatracker.ietf.org/doc/html/rfc8628
9
+ * @module
10
+ */
11
+ import type { ClientInterface } from "../../models/client.js";
12
+ import type { Token } from "../../models/token.js";
13
+ import type { AbstractScope, BasicScope } from "../../models/scope.js";
14
+ import type { DeviceAuthorization } from "../../models/device-authorization.js";
15
+ import type { DeviceAuthorizationServiceInterface } from "../services/device-authorization.js";
16
+ import { AbstractGrant, type GrantOptions, type GrantServices } from "./grant.js";
17
+ /**
18
+ * Services required by the device authorization grant.
19
+ */
20
+ export interface DeviceAuthorizationGrantServices<Client extends ClientInterface, User, S extends AbstractScope> extends GrantServices<Client, User, S> {
21
+ /** Generates, stores, and polls device/user codes for the device flow. */
22
+ deviceAuthorizationService: DeviceAuthorizationServiceInterface<Client, User, S>;
23
+ }
24
+ /**
25
+ * Options for configuring the device authorization grant.
26
+ *
27
+ * @see https://datatracker.ietf.org/doc/html/rfc8628
28
+ */
29
+ export interface DeviceAuthorizationGrantOptions<Client extends ClientInterface, User, S extends AbstractScope> extends GrantOptions<Client, User, S, DeviceAuthorizationGrantServices<Client, User, S>> {
30
+ /** Allow optional refresh token. Defaults to true. */
31
+ allowRefreshToken?: boolean;
32
+ }
33
+ /**
34
+ * The Device Authorization Grant type (RFC 8628).
35
+ *
36
+ * This grant is used for devices that lack a browser or have limited
37
+ * input capabilities (TVs, game consoles, CLI tools, etc.).
38
+ *
39
+ * Flow:
40
+ * 1. Device requests authorization, receives device_code and user_code
41
+ * 2. User visits verification_uri on another device and enters user_code
42
+ * 3. User authorizes or denies the request
43
+ * 4. Device polls token endpoint with device_code until authorized/denied/expired
44
+ *
45
+ * @see https://datatracker.ietf.org/doc/html/rfc8628
46
+ */
47
+ export declare class DeviceAuthorizationGrant<Client extends ClientInterface, User, S extends AbstractScope = BasicScope> extends AbstractGrant<Client, User, S, DeviceAuthorizationGrantServices<Client, User, S>> {
48
+ #private;
49
+ /**
50
+ * The grant type identifier.
51
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
52
+ */
53
+ readonly grantType = "urn:ietf:params:oauth:grant-type:device_code";
54
+ /** Creates the grant from its {@linkcode DeviceAuthorizationGrantOptions}. */
55
+ constructor(options: DeviceAuthorizationGrantOptions<Client, User, S>);
56
+ /**
57
+ * Initiates a device authorization request.
58
+ *
59
+ * Called by the device authorization endpoint to create a new
60
+ * pending authorization that the user can approve. Resolves its own
61
+ * `deviceAuthorizationService` from the request.
62
+ *
63
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
64
+ */
65
+ initiateDeviceAuthorization(client: Client, request: Request, scope?: S | null): Promise<DeviceAuthorization<Client, User, S>>;
66
+ /**
67
+ * Handles a token request for the device authorization grant.
68
+ *
69
+ * The client polls this endpoint with the device_code until:
70
+ * - The user authorizes the request (returns tokens)
71
+ * - The user denies the request (returns access_denied)
72
+ * - The device_code expires (returns expired_token)
73
+ * - The client polls too frequently (returns slow_down)
74
+ *
75
+ * Ownership is authenticated before the authorization is acted on: a client
76
+ * polling a device code issued to a different client is refused
77
+ * `invalid_grant` whether that code is live or expired, and the record is
78
+ * left untouched, so a poll the caller is not entitled to make can neither
79
+ * destroy the authorization nor distinguish its state.
80
+ *
81
+ * The device code is claimed by revoking it once the user has approved, so
82
+ * concurrent polls of one approved device code cannot both receive a token.
83
+ * Recording the poll time is bookkeeping for `slow_down` only: it is skipped
84
+ * on every outcome that revokes the authorization. When the store **raises**
85
+ * for the vanished record — as it must, if a lost race is to be
86
+ * distinguishable — the poll is refused with `invalid_grant` rather than
87
+ * surfacing the store's own error. A store whose poll write instead resolves
88
+ * for a record that is gone leaves that poll answering
89
+ * `authorization_pending`, and the next one gets `invalid_grant` from the
90
+ * existence check; the code is still single-use either way, because only the
91
+ * caller whose `revoke` resolved `true` is issued a token.
92
+ *
93
+ * @throws {InvalidRequestError} If `device_code` is missing.
94
+ * @throws {InvalidGrantError} If the device code is unknown, was issued to another client, or was already claimed by another poll.
95
+ * @throws {ExpiredTokenError} If the device code has expired.
96
+ * @throws {SlowDownError} If the client polls faster than the interval allows.
97
+ * @throws {AccessDeniedError} If the user denied the request.
98
+ * @throws {AuthorizationPendingError} If the user has not yet approved the request.
99
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
100
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
101
+ */
102
+ token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, S>>;
103
+ }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Device Authorization Grant implementation (RFC 8628).
3
+ *
4
+ * This grant type allows devices with limited input capabilities
5
+ * to obtain user authorization by having the user authorize
6
+ * the request on a secondary device.
7
+ *
8
+ * @see https://datatracker.ietf.org/doc/html/rfc8628
9
+ * @module
10
+ */
11
+ import { AccessDeniedError, AuthorizationPendingError, ExpiredTokenError, InvalidGrantError, InvalidRequestError, SlowDownError, } from "../../errors.js";
12
+ import { AbstractGrant, } from "./grant.js";
13
+ /**
14
+ * The Device Authorization Grant type (RFC 8628).
15
+ *
16
+ * This grant is used for devices that lack a browser or have limited
17
+ * input capabilities (TVs, game consoles, CLI tools, etc.).
18
+ *
19
+ * Flow:
20
+ * 1. Device requests authorization, receives device_code and user_code
21
+ * 2. User visits verification_uri on another device and enters user_code
22
+ * 3. User authorizes or denies the request
23
+ * 4. Device polls token endpoint with device_code until authorized/denied/expired
24
+ *
25
+ * @see https://datatracker.ietf.org/doc/html/rfc8628
26
+ */
27
+ export class DeviceAuthorizationGrant extends AbstractGrant {
28
+ /**
29
+ * The grant type identifier.
30
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
31
+ */
32
+ grantType = "urn:ietf:params:oauth:grant-type:device_code";
33
+ /** Creates the grant from its {@linkcode DeviceAuthorizationGrantOptions}. */
34
+ constructor(options) {
35
+ super({
36
+ ...options,
37
+ allowRefreshToken: options.allowRefreshToken ?? true,
38
+ });
39
+ }
40
+ /**
41
+ * Initiates a device authorization request.
42
+ *
43
+ * Called by the device authorization endpoint to create a new
44
+ * pending authorization that the user can approve. Resolves its own
45
+ * `deviceAuthorizationService` from the request.
46
+ *
47
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.1
48
+ */
49
+ async initiateDeviceAuthorization(client, request, scope) {
50
+ const { deviceAuthorizationService } = await this.resolveServices(request);
51
+ const deviceAuthorization = {
52
+ deviceCode: await deviceAuthorizationService.generateDeviceCode(client, scope),
53
+ userCode: await deviceAuthorizationService.generateUserCode(client, scope),
54
+ expiresAt: await deviceAuthorizationService.expiresAt(client, scope),
55
+ client,
56
+ scope,
57
+ interval: deviceAuthorizationService.interval,
58
+ };
59
+ return await deviceAuthorizationService.save(deviceAuthorization);
60
+ }
61
+ /**
62
+ * Handles a token request for the device authorization grant.
63
+ *
64
+ * The client polls this endpoint with the device_code until:
65
+ * - The user authorizes the request (returns tokens)
66
+ * - The user denies the request (returns access_denied)
67
+ * - The device_code expires (returns expired_token)
68
+ * - The client polls too frequently (returns slow_down)
69
+ *
70
+ * Ownership is authenticated before the authorization is acted on: a client
71
+ * polling a device code issued to a different client is refused
72
+ * `invalid_grant` whether that code is live or expired, and the record is
73
+ * left untouched, so a poll the caller is not entitled to make can neither
74
+ * destroy the authorization nor distinguish its state.
75
+ *
76
+ * The device code is claimed by revoking it once the user has approved, so
77
+ * concurrent polls of one approved device code cannot both receive a token.
78
+ * Recording the poll time is bookkeeping for `slow_down` only: it is skipped
79
+ * on every outcome that revokes the authorization. When the store **raises**
80
+ * for the vanished record — as it must, if a lost race is to be
81
+ * distinguishable — the poll is refused with `invalid_grant` rather than
82
+ * surfacing the store's own error. A store whose poll write instead resolves
83
+ * for a record that is gone leaves that poll answering
84
+ * `authorization_pending`, and the next one gets `invalid_grant` from the
85
+ * existence check; the code is still single-use either way, because only the
86
+ * caller whose `revoke` resolved `true` is issued a token.
87
+ *
88
+ * @throws {InvalidRequestError} If `device_code` is missing.
89
+ * @throws {InvalidGrantError} If the device code is unknown, was issued to another client, or was already claimed by another poll.
90
+ * @throws {ExpiredTokenError} If the device code has expired.
91
+ * @throws {SlowDownError} If the client polls faster than the interval allows.
92
+ * @throws {AccessDeniedError} If the user denied the request.
93
+ * @throws {AuthorizationPendingError} If the user has not yet approved the request.
94
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.4
95
+ * @see https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
96
+ */
97
+ async token(request, client, body) {
98
+ const { deviceAuthorizationService, tokenService } = await this
99
+ .resolveServices(request);
100
+ const deviceCode = body.get("device_code");
101
+ if (typeof deviceCode !== "string") {
102
+ throw new InvalidRequestError("device_code parameter required");
103
+ }
104
+ const authorization = await deviceAuthorizationService.getByDeviceCode(deviceCode);
105
+ if (!authorization) {
106
+ throw new InvalidGrantError("invalid device_code");
107
+ }
108
+ if (authorization.client.id !== client.id) {
109
+ throw new InvalidGrantError("device_code was issued to another client");
110
+ }
111
+ if (authorization.expiresAt < new Date()) {
112
+ await deviceAuthorizationService.revoke(authorization);
113
+ throw new ExpiredTokenError("device_code has expired");
114
+ }
115
+ if (authorization.lastPolled) {
116
+ const elapsed = Date.now() - authorization.lastPolled.getTime();
117
+ const minInterval = authorization.interval * 1000;
118
+ if (elapsed < minInterval) {
119
+ authorization.interval += 5;
120
+ await this.#recordPoll(deviceAuthorizationService, authorization, deviceCode);
121
+ throw new SlowDownError("polling too frequently");
122
+ }
123
+ }
124
+ if (authorization.denied) {
125
+ await deviceAuthorizationService.revoke(authorization);
126
+ throw new AccessDeniedError("authorization request was denied");
127
+ }
128
+ if (!authorization.authorized || !authorization.user) {
129
+ await this.#recordPoll(deviceAuthorizationService, authorization, deviceCode);
130
+ throw new AuthorizationPendingError("authorization request is pending user approval");
131
+ }
132
+ const { user, scope } = authorization;
133
+ if (!await deviceAuthorizationService.revoke(authorization)) {
134
+ throw new InvalidGrantError("device_code already used");
135
+ }
136
+ const token = await this.generateToken(client, user, scope, tokenService);
137
+ return await tokenService.save(token);
138
+ }
139
+ async #recordPoll(deviceAuthorizationService, authorization, deviceCode) {
140
+ try {
141
+ await deviceAuthorizationService.updateLastPolled(authorization);
142
+ }
143
+ catch (error) {
144
+ let stillStored;
145
+ try {
146
+ stillStored = await deviceAuthorizationService.getByDeviceCode(deviceCode);
147
+ }
148
+ catch {
149
+ throw error;
150
+ }
151
+ if (stillStored)
152
+ throw error;
153
+ throw new InvalidGrantError("device_code already used");
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Base grant type implementation for OAuth2.
3
+ *
4
+ * Provides the abstract base class and the framework-facing grant contract for
5
+ * implementing OAuth2 grant types per RFC 6749.
6
+ *
7
+ * **Service resolution.** A grant holds no storage. It is constructed with a
8
+ * `resolve(request) => services` function that returns the full set of services
9
+ * that grant needs, and the base resolves them per request through
10
+ * `resolveServices`. The grant infers its `Client`/`User`/`Scope` types from
11
+ * that `resolve` return, so a grant can be built standalone (e.g. for tests) and
12
+ * the {@link AuthorizationServer} infers its own generics from the grants. The
13
+ * base provides stateless helpers (`acceptedScope`, `generateToken`) that take
14
+ * the resolved services as arguments.
15
+ *
16
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4
17
+ * @module
18
+ */
19
+ import type { ClientCredentials, ClientInterface } from "../../models/client.js";
20
+ import type { RefreshToken, Token } from "../../models/token.js";
21
+ import type { AuthenticationContext } from "../../models/authentication.js";
22
+ import type { AbstractScope, ScopeConstructor } from "../../models/scope.js";
23
+ import { BasicScope } from "../../models/scope.js";
24
+ import type { ClientServiceInterface } from "../services/client.js";
25
+ import type { TokenServiceInterface } from "../services/token.js";
26
+ /**
27
+ * The core services every grant needs: client lookup/authentication and token
28
+ * storage. Each grant defines its own services interface by extending this with
29
+ * the grant-specific services it resolves (e.g. `authorizationCodeService`).
30
+ */
31
+ export interface GrantServices<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> {
32
+ /** Looks up and authenticates clients presenting a token request. */
33
+ clientService: ClientServiceInterface<Client, User>;
34
+ /** Generates and persists access and refresh tokens. */
35
+ tokenService: TokenServiceInterface<Client, User, Scope>;
36
+ }
37
+ /**
38
+ * The framework-facing grant contract that {@link AuthorizationServer}
39
+ * dispatches through. It carries no grant-specific service types, so any grant —
40
+ * including a third-party grant with its own services — is assignable to it and
41
+ * lives in the server's grant map without `any`. Grants resolve their own
42
+ * services from the request, so these methods take no services argument.
43
+ */
44
+ export interface DispatchableGrant<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope> {
45
+ /** The OAuth2 `grant_type` value this grant handles (e.g. `authorization_code`). */
46
+ readonly grantType: string;
47
+ /**
48
+ * Whether this grant's tokens represent an end-user authentication, making
49
+ * them eligible for an id_token. Treated as `true` when absent; grants with
50
+ * no authentication event (client_credentials) set `false`.
51
+ */
52
+ readonly issuesIdToken?: boolean;
53
+ /**
54
+ * Authenticates the client presenting this grant's token request. `body` is
55
+ * the request's form body, parsed once by the token endpoint — a grant reads
56
+ * its parameters from it instead of consuming the request.
57
+ */
58
+ getAuthenticatedClient(request: Request, body: FormData): Promise<Client>;
59
+ /**
60
+ * Exchanges the request for a token. `body` is the request's form body,
61
+ * parsed once by the token endpoint.
62
+ */
63
+ token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, Scope>>;
64
+ }
65
+ /**
66
+ * Configuration shared by all grants: how to reach the services the grant
67
+ * needs, plus the scope and refresh-token settings. Each grant's own options
68
+ * type extends this with its grant-specific settings.
69
+ */
70
+ export interface GrantOptions<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope, Services extends GrantServices<Client, User, Scope> = GrantServices<Client, User, Scope>> {
71
+ /** Resolves the services this grant needs for a request. */
72
+ resolve: (request: Request) => Services | Promise<Services>;
73
+ /** Scope constructor used to parse scope strings. Defaults to {@linkcode BasicScope}. */
74
+ Scope?: ScopeConstructor<Scope>;
75
+ /** Allow optional refresh token. */
76
+ allowRefreshToken?: boolean;
77
+ }
78
+ /**
79
+ * Abstract base class for grant type implementations.
80
+ *
81
+ * Extend this to create custom grant types, parameterized by the services
82
+ * interface the grant resolves. A subclass implements {@link token}, reaches
83
+ * its services through {@link resolveServices}, and overrides
84
+ * {@link getAuthenticatedClient} only when it authenticates clients by
85
+ * something other than their registered credentials (as the authorization-code
86
+ * grant does for PKCE).
87
+ */
88
+ export declare abstract class AbstractGrant<Client extends ClientInterface, User, Scope extends AbstractScope = BasicScope, Services extends GrantServices<Client, User, Scope> = GrantServices<Client, User, Scope>> implements DispatchableGrant<Client, User, Scope> {
89
+ #private;
90
+ /** The OAuth2 `grant_type` value this grant handles (e.g. `authorization_code`). */
91
+ abstract readonly grantType: string;
92
+ /** Constructor used to parse scope strings into {@linkcode Scope} objects. */
93
+ Scope: ScopeConstructor<Scope>;
94
+ /** Allow optional refresh token. Defaults to false. */
95
+ allowRefreshToken: boolean;
96
+ /** Creates a grant from the shared {@linkcode GrantOptions}. */
97
+ constructor(options: GrantOptions<Client, User, Scope, Services>);
98
+ /** The services this grant needs for `request`, from its `resolve` option. */
99
+ protected resolveServices(request: Request): Promise<Services>;
100
+ /** Parses a scope string into a Scope object. */
101
+ parseScope(scopeText?: string | null): Scope | undefined;
102
+ /**
103
+ * Validates and returns the accepted scope for the client and user, per the
104
+ * `TokenServiceInterface.acceptedScope` contract: a returned scope replaces
105
+ * the requested one (narrowing), `null`/`undefined` accepts the requested
106
+ * scope as-is, and `false` rejects.
107
+ *
108
+ * `user` is `undefined` when the client is acting as its own resource owner
109
+ * (client credentials, RFC 6749 §4.4), so the token service decides scope
110
+ * from the client alone.
111
+ *
112
+ * Throws InvalidScopeError if the scope is rejected.
113
+ */
114
+ acceptedScope(client: Client, user: User | undefined, scope: Scope | null | undefined, tokenService: TokenServiceInterface<Client, User, Scope>): Promise<Scope | null | undefined>;
115
+ /**
116
+ * Extracts client credentials from a request: HTTP Basic first, then the
117
+ * `client_id`/`client_secret` of the already-parsed `body`. Override to add
118
+ * grant-specific credential rules (as the authorization-code grant does for
119
+ * PKCE).
120
+ *
121
+ * @throws {InvalidClientError} If the request presents no usable credentials.
122
+ */
123
+ protected getClientCredentials(request: Request, body: FormData): ClientCredentials;
124
+ /**
125
+ * Clamps a token's expiries to the absolute expiry of its rotation family,
126
+ * so nothing derived from a family outlives it — an access token issued a
127
+ * moment before the ceiling included. Mutates `token`; a token with no
128
+ * expiry of its own takes the family's.
129
+ */
130
+ protected capAtFamilyExpiry(token: Token<Client, User, Scope> | RefreshToken<Client, User, Scope>, familyExpiresAt: Date): void;
131
+ /**
132
+ * Generates a token for a client and user from a resolved `tokenService`.
133
+ * Includes a refresh token if allowed, which starts a new rotation family:
134
+ * `familyId` plus the `familyCreatedAt` anchor the refresh grant caps
135
+ * rotations against. When the token service caps families
136
+ * ({@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt}), both
137
+ * expiries are clamped to the new family's ceiling.
138
+ *
139
+ * Pass `user: undefined` for a token the client holds as its own resource
140
+ * owner (client credentials, RFC 6749 §4.4). Such a token is never issued a
141
+ * refresh token whatever `allowRefreshToken` says — there is no resource
142
+ * owner whose authorization a rotation could carry forward.
143
+ * `authenticationContext` is verified event evidence, snapshotted before
144
+ * generation and carried to storage. Omit it when unknown; it is discarded
145
+ * for tokens with no user.
146
+ */
147
+ generateToken(client: Client, user: User | undefined, scope: Scope | null | undefined, tokenService: TokenServiceInterface<Client, User, Scope>, authenticationContext?: AuthenticationContext): Promise<Token<Client, User, Scope>>;
148
+ /**
149
+ * Authenticates the client presenting this grant's token request against the
150
+ * grant's own `clientService`, from the credentials the request presents.
151
+ *
152
+ * Override to apply grant-specific authentication rules — the
153
+ * authorization-code grant accepts a PKCE `code_verifier` in place of a
154
+ * client secret for public clients.
155
+ *
156
+ * @throws {InvalidClientError} If authentication fails.
157
+ */
158
+ getAuthenticatedClient(request: Request, body: FormData): Promise<Client>;
159
+ /**
160
+ * Handles a token request. `body` is the request's form body, parsed once by
161
+ * the token endpoint; the grant resolves the services it needs from the
162
+ * request internally.
163
+ */
164
+ abstract token(request: Request, client: Client, body: FormData): Promise<Token<Client, User, Scope>>;
165
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Base grant type implementation for OAuth2.
3
+ *
4
+ * Provides the abstract base class and the framework-facing grant contract for
5
+ * implementing OAuth2 grant types per RFC 6749.
6
+ *
7
+ * **Service resolution.** A grant holds no storage. It is constructed with a
8
+ * `resolve(request) => services` function that returns the full set of services
9
+ * that grant needs, and the base resolves them per request through
10
+ * `resolveServices`. The grant infers its `Client`/`User`/`Scope` types from
11
+ * that `resolve` return, so a grant can be built standalone (e.g. for tests) and
12
+ * the {@link AuthorizationServer} infers its own generics from the grants. The
13
+ * base provides stateless helpers (`acceptedScope`, `generateToken`) that take
14
+ * the resolved services as arguments.
15
+ *
16
+ * @see https://datatracker.ietf.org/doc/html/rfc6749#section-4
17
+ * @module
18
+ */
19
+ import { snapshotAuthenticationContext } from "../../utils/authentication-context.js";
20
+ import { BasicScope } from "../../models/scope.js";
21
+ import { InvalidScopeError } from "../../errors.js";
22
+ import { authenticateClientCredentials, extractClientCredentials, } from "../client-authentication.js";
23
+ /**
24
+ * Abstract base class for grant type implementations.
25
+ *
26
+ * Extend this to create custom grant types, parameterized by the services
27
+ * interface the grant resolves. A subclass implements {@link token}, reaches
28
+ * its services through {@link resolveServices}, and overrides
29
+ * {@link getAuthenticatedClient} only when it authenticates clients by
30
+ * something other than their registered credentials (as the authorization-code
31
+ * grant does for PKCE).
32
+ */
33
+ export class AbstractGrant {
34
+ /** Constructor used to parse scope strings into {@linkcode Scope} objects. */
35
+ Scope;
36
+ /** Allow optional refresh token. Defaults to false. */
37
+ allowRefreshToken;
38
+ #resolve;
39
+ /** Creates a grant from the shared {@linkcode GrantOptions}. */
40
+ constructor(options) {
41
+ this.allowRefreshToken = options.allowRefreshToken ?? false;
42
+ this.Scope = options.Scope ??
43
+ BasicScope;
44
+ this.#resolve = options.resolve;
45
+ }
46
+ /** The services this grant needs for `request`, from its `resolve` option. */
47
+ resolveServices(request) {
48
+ return Promise.resolve(this.#resolve(request));
49
+ }
50
+ /** Parses a scope string into a Scope object. */
51
+ parseScope(scopeText) {
52
+ return scopeText ? new this.Scope(scopeText) : undefined;
53
+ }
54
+ /**
55
+ * Validates and returns the accepted scope for the client and user, per the
56
+ * `TokenServiceInterface.acceptedScope` contract: a returned scope replaces
57
+ * the requested one (narrowing), `null`/`undefined` accepts the requested
58
+ * scope as-is, and `false` rejects.
59
+ *
60
+ * `user` is `undefined` when the client is acting as its own resource owner
61
+ * (client credentials, RFC 6749 §4.4), so the token service decides scope
62
+ * from the client alone.
63
+ *
64
+ * Throws InvalidScopeError if the scope is rejected.
65
+ */
66
+ async acceptedScope(client, user, scope, tokenService) {
67
+ const acceptedScope = await tokenService.acceptedScope(client, user, scope);
68
+ if (acceptedScope === false) {
69
+ throw new InvalidScopeError(scope ? "invalid scope" : "scope required");
70
+ }
71
+ return acceptedScope ?? scope;
72
+ }
73
+ /**
74
+ * Extracts client credentials from a request: HTTP Basic first, then the
75
+ * `client_id`/`client_secret` of the already-parsed `body`. Override to add
76
+ * grant-specific credential rules (as the authorization-code grant does for
77
+ * PKCE).
78
+ *
79
+ * @throws {InvalidClientError} If the request presents no usable credentials.
80
+ */
81
+ getClientCredentials(request, body) {
82
+ return extractClientCredentials(request, body);
83
+ }
84
+ /**
85
+ * Clamps a token's expiries to the absolute expiry of its rotation family,
86
+ * so nothing derived from a family outlives it — an access token issued a
87
+ * moment before the ceiling included. Mutates `token`; a token with no
88
+ * expiry of its own takes the family's.
89
+ */
90
+ capAtFamilyExpiry(token, familyExpiresAt) {
91
+ if (!token.accessTokenExpiresAt ||
92
+ familyExpiresAt < token.accessTokenExpiresAt) {
93
+ token.accessTokenExpiresAt = familyExpiresAt;
94
+ }
95
+ if ("refreshToken" in token) {
96
+ const refreshToken = token;
97
+ if (!refreshToken.refreshTokenExpiresAt ||
98
+ familyExpiresAt < refreshToken.refreshTokenExpiresAt) {
99
+ refreshToken.refreshTokenExpiresAt = familyExpiresAt;
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * Generates a token for a client and user from a resolved `tokenService`.
105
+ * Includes a refresh token if allowed, which starts a new rotation family:
106
+ * `familyId` plus the `familyCreatedAt` anchor the refresh grant caps
107
+ * rotations against. When the token service caps families
108
+ * ({@linkcode TokenServiceInterface.refreshTokenFamilyExpiresAt}), both
109
+ * expiries are clamped to the new family's ceiling.
110
+ *
111
+ * Pass `user: undefined` for a token the client holds as its own resource
112
+ * owner (client credentials, RFC 6749 §4.4). Such a token is never issued a
113
+ * refresh token whatever `allowRefreshToken` says — there is no resource
114
+ * owner whose authorization a rotation could carry forward.
115
+ * `authenticationContext` is verified event evidence, snapshotted before
116
+ * generation and carried to storage. Omit it when unknown; it is discarded
117
+ * for tokens with no user.
118
+ */
119
+ async generateToken(client, user, scope, tokenService, authenticationContext) {
120
+ const event = user === undefined
121
+ ? undefined
122
+ : snapshotAuthenticationContext(authenticationContext);
123
+ const token = {
124
+ accessToken: await tokenService.generateAccessToken(client, user, scope, event),
125
+ client,
126
+ };
127
+ if (event)
128
+ token.authenticationContext = event;
129
+ if (user !== undefined)
130
+ token.user = user;
131
+ if (scope)
132
+ token.scope = scope;
133
+ const accessTokenExpiresAt = await tokenService.accessTokenExpiresAt(client, user, scope);
134
+ if (accessTokenExpiresAt)
135
+ token.accessTokenExpiresAt = accessTokenExpiresAt;
136
+ if (this.allowRefreshToken && user !== undefined) {
137
+ const refreshToken = await tokenService.generateRefreshToken(client, user, scope);
138
+ if (refreshToken) {
139
+ const familyCreatedAt = new Date();
140
+ const result = {
141
+ ...token,
142
+ refreshToken,
143
+ familyId: crypto.randomUUID(),
144
+ familyCreatedAt,
145
+ };
146
+ const refreshTokenExpiresAt = await tokenService.refreshTokenExpiresAt(client, user, scope);
147
+ if (refreshTokenExpiresAt) {
148
+ result.refreshTokenExpiresAt = refreshTokenExpiresAt;
149
+ }
150
+ const familyExpiresAt = await tokenService
151
+ .refreshTokenFamilyExpiresAt?.(client, user, familyCreatedAt, scope);
152
+ if (familyExpiresAt)
153
+ this.capAtFamilyExpiry(result, familyExpiresAt);
154
+ return result;
155
+ }
156
+ }
157
+ return token;
158
+ }
159
+ /**
160
+ * Authenticates the client presenting this grant's token request against the
161
+ * grant's own `clientService`, from the credentials the request presents.
162
+ *
163
+ * Override to apply grant-specific authentication rules — the
164
+ * authorization-code grant accepts a PKCE `code_verifier` in place of a
165
+ * client secret for public clients.
166
+ *
167
+ * @throws {InvalidClientError} If authentication fails.
168
+ */
169
+ async getAuthenticatedClient(request, body) {
170
+ const { clientService } = await this.resolveServices(request);
171
+ return await authenticateClientCredentials(this.getClientCredentials(request, body), clientService);
172
+ }
173
+ }