@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,703 @@
1
+ /**
2
+ * In-memory implementations of every OAuth2 service interface.
3
+ *
4
+ * The `Memory*Service` family is the public, generic version of the fixtures
5
+ * the library uses for its own tests. They satisfy the
6
+ * `UserServiceInterface`, `ClientServiceInterface`, `TokenServiceInterface`,
7
+ * `AuthorizationCodeServiceInterface`, and `DeviceAuthorizationServiceInterface`
8
+ * contracts using `Map`-backed storage that lives only for the process /
9
+ * test lifetime.
10
+ *
11
+ * They are deliberately **not** for production: tokens and codes vanish on
12
+ * restart, there is no concurrency control, and nothing is ever reclaimed — an
13
+ * expired record stays in its `Map` until something revokes it, and the
14
+ * revoked-refresh-token tombstones that reuse detection reads are kept for the
15
+ * process lifetime, so memory grows with every token issued. Port the shape to
16
+ * a database and it needs the expiry sweep or row TTL these fixtures skip. Use
17
+ * them to:
18
+ *
19
+ * - Spin up a stand-in authorization server in tests of consumer apps.
20
+ * - Prototype an OAuth2 integration before the database schema lands.
21
+ * - Drive examples that need realistic data without DB infrastructure.
22
+ *
23
+ * Pair with `createMemoryAuthorizationServer` for the one-shot factory
24
+ * variant, or compose directly when you need finer control.
25
+ *
26
+ * @module
27
+ */
28
+ import { snapshotAuthenticationContext } from "../utils/authentication-context.js";
29
+ import { PasswordIdentityService, } from "../identity/password.js";
30
+ import { sha256Hash } from "../server/utils/hash.js";
31
+ const FIXTURE_PBKDF2_ITERATIONS = 1_000;
32
+ /**
33
+ * In-memory `UserServiceInterface` implementation.
34
+ *
35
+ * Generic over the user type — supply your real app's `User` interface
36
+ * (extending {@link MemoryUserShape}) so fixtures match production data.
37
+ */
38
+ export class MemoryUserService {
39
+ #usersById = new Map();
40
+ #usersByUsername = new Map();
41
+ #credentialsByUserId = new Map();
42
+ #passwords;
43
+ /** Applies the password-hasher default; construct with no arguments for it. */
44
+ constructor(options = {}) {
45
+ this.#passwords = options.passwords ??
46
+ new PasswordIdentityService({ iterations: FIXTURE_PBKDF2_ITERATIONS });
47
+ }
48
+ /** Adds a user with the given password. Throws on duplicate id or username. */
49
+ async add(user, password) {
50
+ if (this.#usersById.has(user.id)) {
51
+ throw new Error(`User with id "${user.id}" already exists`);
52
+ }
53
+ if (this.#usersByUsername.has(user.username)) {
54
+ throw new Error(`User with username "${user.username}" already exists`);
55
+ }
56
+ this.#usersById.set(user.id, user);
57
+ this.#usersByUsername.set(user.username, user);
58
+ this.#credentialsByUserId.set(user.id, await this.#passwords.hash(password));
59
+ }
60
+ /** Looks up a user by id, or resolves `undefined` when none is stored. */
61
+ get(id) {
62
+ return Promise.resolve(this.#usersById.get(id));
63
+ }
64
+ /**
65
+ * Resolves the user when the username exists and the password verifies
66
+ * against its stored salt/hash, otherwise `undefined`. Backs the
67
+ * resource-owner password-credentials grant.
68
+ */
69
+ async getAuthenticated(username, password) {
70
+ const user = this.#usersByUsername.get(username);
71
+ if (!user)
72
+ return undefined;
73
+ const credential = this.#credentialsByUserId.get(user.id);
74
+ if (!credential)
75
+ return undefined;
76
+ return await this.#passwords.verify(password, credential)
77
+ ? user
78
+ : undefined;
79
+ }
80
+ /** Looks up a user by username without a password check, or `undefined`. */
81
+ findByUsername(username) {
82
+ return Promise.resolve(this.#usersByUsername.get(username));
83
+ }
84
+ /**
85
+ * The user's stored password credential, or `undefined`. Shape-compatible
86
+ * with the identity layer's `IdentityUserStore.getCredential`, so a
87
+ * memory-backed store can drive the own-auth flows (sign-in, password
88
+ * reset) in examples and tests.
89
+ */
90
+ getCredential(userId) {
91
+ return Promise.resolve(this.#credentialsByUserId.get(userId));
92
+ }
93
+ /**
94
+ * Replace the user's stored password credential (e.g. after a password
95
+ * reset). Matches `IdentityUserStore.setCredential`.
96
+ * @throws Error when no user with `userId` exists.
97
+ */
98
+ setCredential(userId, credential) {
99
+ if (!this.#usersById.has(userId)) {
100
+ throw new Error(`User with id "${userId}" does not exist`);
101
+ }
102
+ this.#credentialsByUserId.set(userId, credential);
103
+ return Promise.resolve();
104
+ }
105
+ }
106
+ /**
107
+ * In-memory `ClientServiceInterface` implementation.
108
+ *
109
+ * A client is treated as confidential when {@link add} is called with a
110
+ * secret, and public when it is not — there is no separate flag to
111
+ * remember. `getAuthenticated` requires the secret for confidential
112
+ * clients and accepts a missing secret for public clients.
113
+ */
114
+ export class MemoryClientService {
115
+ #clientsById = new Map();
116
+ #secretHashByClientId = new Map();
117
+ #ownerUserIdByClientId = new Map();
118
+ #userService;
119
+ /**
120
+ * Creates an empty client store backed by the given user service.
121
+ *
122
+ * @param userService Resolves the owner returned by {@linkcode getUser}
123
+ * for the client-credentials grant.
124
+ */
125
+ constructor(userService) {
126
+ this.#userService = userService;
127
+ }
128
+ /**
129
+ * Registers a client. Pass `secret` to make it confidential; omit for a
130
+ * public client. `ownerUserId` is the user returned by {@link getUser},
131
+ * used by the client-credentials grant.
132
+ * @throws Error when a client with the same id is already registered.
133
+ */
134
+ async add(client, secret, ownerUserId) {
135
+ if (this.#clientsById.has(client.id)) {
136
+ throw new Error(`Client with id "${client.id}" already exists`);
137
+ }
138
+ this.#clientsById.set(client.id, client);
139
+ if (secret) {
140
+ this.#secretHashByClientId.set(client.id, await sha256Hash(secret));
141
+ }
142
+ if (ownerUserId) {
143
+ this.#ownerUserIdByClientId.set(client.id, ownerUserId);
144
+ }
145
+ }
146
+ /** Looks up a client by id, or resolves `undefined` when none is stored. */
147
+ get(id) {
148
+ return Promise.resolve(this.#clientsById.get(id));
149
+ }
150
+ /**
151
+ * Resolves the client when authentication succeeds, otherwise `undefined`.
152
+ * Confidential clients (registered with a secret) require a matching
153
+ * `secret`; public clients (registered without one) ignore it.
154
+ */
155
+ async getAuthenticated(id, secret) {
156
+ const client = this.#clientsById.get(id);
157
+ if (!client)
158
+ return undefined;
159
+ const storedHash = this.#secretHashByClientId.get(id);
160
+ if (storedHash) {
161
+ if (!secret)
162
+ return undefined;
163
+ if (await sha256Hash(secret) !== storedHash)
164
+ return undefined;
165
+ }
166
+ return client;
167
+ }
168
+ /**
169
+ * Resolves the user registered as the client's owner via the `ownerUserId`
170
+ * passed to {@linkcode add}, or `undefined` when none was set. Accepts the
171
+ * client or its id.
172
+ */
173
+ getUser(client) {
174
+ const id = typeof client === "string" ? client : client.id;
175
+ const userId = this.#ownerUserIdByClientId.get(id);
176
+ if (!userId)
177
+ return Promise.resolve(undefined);
178
+ return this.#userService.get(userId);
179
+ }
180
+ }
181
+ /**
182
+ * In-memory `TokenServiceInterface` implementation. Tokens are hashed on
183
+ * write and looked up by hash, mirroring how a real database-backed token
184
+ * service would store them.
185
+ *
186
+ * Per-client lifetime overrides are supported via three optional fields the
187
+ * client may declare: `accessTokenLifetime`, `refreshTokenLifetime`, and
188
+ * `refreshTokenMaxLifetime`. Each takes precedence over the service-wide
189
+ * default when present.
190
+ */
191
+ export class MemoryTokenService {
192
+ /** Service-wide access-token lifetime in seconds when a client sets none. */
193
+ accessTokenLifetime;
194
+ /** Service-wide refresh-token lifetime in seconds when a client sets none. */
195
+ refreshTokenLifetime;
196
+ /**
197
+ * Service-wide absolute refresh-token family lifetime in seconds when a
198
+ * client sets none. Undefined leaves families uncapped.
199
+ */
200
+ refreshTokenMaxLifetime;
201
+ #clientService;
202
+ #userService;
203
+ #tokensByAccessHash = new Map();
204
+ #tokensByRefreshHash = new Map();
205
+ #tokensByCodeHash = new Map();
206
+ #revokedByRefreshHash = new Map();
207
+ /**
208
+ * Creates an empty token store.
209
+ *
210
+ * @param options Backing client/user services and optional default token
211
+ * lifetimes.
212
+ */
213
+ constructor(options) {
214
+ this.#clientService = options.clientService;
215
+ this.#userService = options.userService;
216
+ this.accessTokenLifetime = options.accessTokenLifetime ?? 60 * 60;
217
+ this.refreshTokenLifetime = options.refreshTokenLifetime ??
218
+ 14 * 24 * 60 * 60;
219
+ this.refreshTokenMaxLifetime = options.refreshTokenMaxLifetime;
220
+ }
221
+ /**
222
+ * Accepts the requested scope verbatim; the stub performs no scope
223
+ * narrowing. Return `false` from a subclass to reject a request.
224
+ */
225
+ acceptedScope(_client, _user, scope) {
226
+ return Promise.resolve(scope);
227
+ }
228
+ /** Mints a random opaque access token. */
229
+ generateAccessToken(_client, _user, _scope) {
230
+ return Promise.resolve(crypto.randomUUID());
231
+ }
232
+ /** Mints a random opaque refresh token. */
233
+ generateRefreshToken(_client, _user, _scope) {
234
+ return Promise.resolve(crypto.randomUUID());
235
+ }
236
+ /**
237
+ * Returns now plus the client's `accessTokenLifetime` override, falling back
238
+ * to {@linkcode accessTokenLifetime}.
239
+ */
240
+ accessTokenExpiresAt(client, _user, _scope) {
241
+ const lifetime = client.accessTokenLifetime ??
242
+ this.accessTokenLifetime;
243
+ return Promise.resolve(new Date(Date.now() + lifetime * 1000));
244
+ }
245
+ /**
246
+ * Returns now plus the client's `refreshTokenLifetime` override, falling
247
+ * back to {@linkcode refreshTokenLifetime}.
248
+ */
249
+ refreshTokenExpiresAt(client, _user, _scope) {
250
+ const lifetime = client.refreshTokenLifetime ??
251
+ this.refreshTokenLifetime;
252
+ return Promise.resolve(new Date(Date.now() + lifetime * 1000));
253
+ }
254
+ /**
255
+ * Returns the family anchor plus the client's `refreshTokenMaxLifetime`
256
+ * override, falling back to {@linkcode refreshTokenMaxLifetime}, or
257
+ * `undefined` when neither caps the family.
258
+ */
259
+ refreshTokenFamilyExpiresAt(client, _user, familyCreatedAt, _scope) {
260
+ const maxLifetime = client
261
+ .refreshTokenMaxLifetime ??
262
+ this.refreshTokenMaxLifetime;
263
+ return Promise.resolve(maxLifetime == null
264
+ ? undefined
265
+ : new Date(familyCreatedAt.getTime() + maxLifetime * 1000));
266
+ }
267
+ async #hydrate(stored, accessToken, refreshToken) {
268
+ const client = await this.#clientService.get(stored.clientId);
269
+ if (!client)
270
+ return undefined;
271
+ const user = stored.userId
272
+ ? await this.#userService.get(stored.userId)
273
+ : undefined;
274
+ if (stored.userId && !user)
275
+ return undefined;
276
+ const token = {
277
+ accessToken,
278
+ accessTokenExpiresAt: stored.accessTokenExpiresAt,
279
+ client,
280
+ user,
281
+ scope: stored.scope,
282
+ authenticationContext: snapshotAuthenticationContext(stored.authenticationContext),
283
+ };
284
+ if (stored.refreshTokenHash) {
285
+ return {
286
+ ...token,
287
+ refreshToken: refreshToken ?? "",
288
+ refreshTokenExpiresAt: stored.refreshTokenExpiresAt,
289
+ familyId: stored.familyId,
290
+ familyCreatedAt: stored.familyCreatedAt,
291
+ };
292
+ }
293
+ return token;
294
+ }
295
+ /**
296
+ * Resolves the token whose access token hashes to the given value, with its
297
+ * client and user rehydrated, or `undefined` when not found.
298
+ */
299
+ async getToken(accessToken) {
300
+ const stored = this.#tokensByAccessHash.get(await sha256Hash(accessToken));
301
+ if (!stored)
302
+ return undefined;
303
+ return this.#hydrate(stored, accessToken);
304
+ }
305
+ /**
306
+ * Resolves the refresh token whose value hashes to the given input, with its
307
+ * client and user rehydrated, or `undefined` when not found.
308
+ */
309
+ async getRefreshToken(refreshToken) {
310
+ const stored = this.#tokensByRefreshHash.get(await sha256Hash(refreshToken));
311
+ if (!stored)
312
+ return undefined;
313
+ return this.#hydrate(stored, "", refreshToken);
314
+ }
315
+ async save(token) {
316
+ const accessTokenHash = await sha256Hash(token.accessToken);
317
+ const stored = {
318
+ accessTokenHash,
319
+ accessTokenExpiresAt: token.accessTokenExpiresAt,
320
+ clientId: token.client.id,
321
+ userId: token.user?.id,
322
+ scope: token.scope,
323
+ authenticationContext: snapshotAuthenticationContext(token.authenticationContext),
324
+ };
325
+ if (token.code) {
326
+ stored.codeHash = await sha256Hash(token.code);
327
+ let bucket = this.#tokensByCodeHash.get(stored.codeHash);
328
+ if (!bucket) {
329
+ bucket = new Set();
330
+ this.#tokensByCodeHash.set(stored.codeHash, bucket);
331
+ }
332
+ bucket.add(accessTokenHash);
333
+ }
334
+ if ("refreshToken" in token) {
335
+ const rt = token;
336
+ stored.refreshTokenHash = await sha256Hash(rt.refreshToken);
337
+ stored.refreshTokenExpiresAt = rt.refreshTokenExpiresAt;
338
+ stored.familyId = rt.familyId;
339
+ stored.familyCreatedAt = rt.familyCreatedAt;
340
+ this.#tokensByRefreshHash.set(stored.refreshTokenHash, stored);
341
+ }
342
+ this.#tokensByAccessHash.set(accessTokenHash, stored);
343
+ return token;
344
+ }
345
+ async revoke(token, hint) {
346
+ if (typeof token !== "string") {
347
+ if ("refreshToken" in token && token.refreshToken) {
348
+ hint = "refresh_token";
349
+ token = token.refreshToken;
350
+ }
351
+ else {
352
+ token = token.accessToken;
353
+ }
354
+ }
355
+ const hashed = await sha256Hash(token);
356
+ const stored = hint === "refresh_token"
357
+ ? this.#tokensByRefreshHash.get(hashed)
358
+ : this.#tokensByAccessHash.get(hashed);
359
+ if (!stored)
360
+ return false;
361
+ this.#tokensByAccessHash.delete(stored.accessTokenHash);
362
+ if (stored.refreshTokenHash) {
363
+ this.#tombstone(stored.refreshTokenHash, stored);
364
+ this.#tokensByRefreshHash.delete(stored.refreshTokenHash);
365
+ }
366
+ return true;
367
+ }
368
+ #tombstone(refreshTokenHash, stored) {
369
+ this.#revokedByRefreshHash.set(refreshTokenHash, stored);
370
+ }
371
+ /**
372
+ * Looks up a revoked/rotated-out refresh token for the refresh grant's
373
+ * reuse detection: revoked refresh records are kept as tombstones instead
374
+ * of deleted.
375
+ */
376
+ async getRevokedRefreshToken(refreshToken) {
377
+ const stored = this.#revokedByRefreshHash.get(await sha256Hash(refreshToken));
378
+ if (!stored)
379
+ return undefined;
380
+ return this.#hydrate(stored, "", refreshToken);
381
+ }
382
+ /**
383
+ * Revokes every live token in a rotation family (reuse detection response).
384
+ * Resolves `true` when at least one live token was removed.
385
+ */
386
+ revokeFamily(familyId) {
387
+ let revoked = false;
388
+ for (const [hash, stored] of this.#tokensByRefreshHash) {
389
+ if (stored.familyId === familyId) {
390
+ this.#tombstone(hash, stored);
391
+ this.#tokensByRefreshHash.delete(hash);
392
+ this.#tokensByAccessHash.delete(stored.accessTokenHash);
393
+ revoked = true;
394
+ }
395
+ }
396
+ for (const [hash, stored] of this.#tokensByAccessHash) {
397
+ if (stored.familyId === familyId) {
398
+ this.#tokensByAccessHash.delete(hash);
399
+ revoked = true;
400
+ }
401
+ }
402
+ return Promise.resolve(revoked);
403
+ }
404
+ /**
405
+ * Removes every token issued from the given authorization code, used when a
406
+ * code is replayed. Resolves `true` when at least one token was removed.
407
+ */
408
+ async revokeCode(code) {
409
+ const codeHash = await sha256Hash(code);
410
+ const tokenHashes = this.#tokensByCodeHash.get(codeHash);
411
+ if (!tokenHashes || tokenHashes.size === 0)
412
+ return false;
413
+ for (const accessTokenHash of tokenHashes) {
414
+ const stored = this.#tokensByAccessHash.get(accessTokenHash);
415
+ if (stored) {
416
+ this.#tokensByAccessHash.delete(accessTokenHash);
417
+ if (stored.refreshTokenHash) {
418
+ this.#tokensByRefreshHash.delete(stored.refreshTokenHash);
419
+ }
420
+ }
421
+ }
422
+ this.#tokensByCodeHash.delete(codeHash);
423
+ return true;
424
+ }
425
+ }
426
+ /** In-memory `AuthorizationCodeServiceInterface` implementation. */
427
+ export class MemoryAuthorizationCodeService {
428
+ /** Authorization-code lifetime in seconds applied by {@linkcode expiresAt}. */
429
+ lifetime;
430
+ #clientService;
431
+ #userService;
432
+ #codes = new Map();
433
+ /**
434
+ * Creates an empty authorization-code store.
435
+ *
436
+ * @param options Backing client/user services and optional code lifetime.
437
+ */
438
+ constructor(options) {
439
+ this.#clientService = options.clientService;
440
+ this.#userService = options.userService;
441
+ this.lifetime = options.lifetime ?? 600;
442
+ }
443
+ /** Mints a random opaque authorization code. */
444
+ generateCode(_client, _user, _scope) {
445
+ return Promise.resolve(crypto.randomUUID());
446
+ }
447
+ /** Returns now plus {@linkcode lifetime}. */
448
+ expiresAt(_client, _user, _scope) {
449
+ return Promise.resolve(new Date(Date.now() + this.lifetime * 1000));
450
+ }
451
+ /**
452
+ * Resolves the code whose value hashes to the given input, with its client
453
+ * and user rehydrated, or `undefined` when not found or either is missing.
454
+ */
455
+ async get(code) {
456
+ const stored = this.#codes.get(await sha256Hash(code));
457
+ if (!stored)
458
+ return undefined;
459
+ const client = await this.#clientService.get(stored.clientId);
460
+ const user = await this.#userService.get(stored.userId);
461
+ if (!client || !user)
462
+ return undefined;
463
+ return {
464
+ code,
465
+ expiresAt: stored.expiresAt,
466
+ client,
467
+ user,
468
+ scope: stored.scope,
469
+ redirectUri: stored.redirectUri,
470
+ challenge: stored.challenge,
471
+ challengeMethod: stored.challengeMethod,
472
+ nonce: stored.nonce,
473
+ authenticationContext: snapshotAuthenticationContext(stored.authenticationContext),
474
+ };
475
+ }
476
+ /**
477
+ * Persists an authorization code indexed by its hash, including the PKCE
478
+ * challenge and redirect URI. Returns the input unchanged.
479
+ */
480
+ async save(authorizationCode) {
481
+ const hashed = await sha256Hash(authorizationCode.code);
482
+ this.#codes.set(hashed, {
483
+ codeHash: hashed,
484
+ expiresAt: authorizationCode.expiresAt,
485
+ clientId: authorizationCode.client.id,
486
+ userId: authorizationCode.user.id,
487
+ scope: authorizationCode.scope,
488
+ redirectUri: authorizationCode.redirectUri,
489
+ challenge: authorizationCode.challenge,
490
+ challengeMethod: authorizationCode.challengeMethod,
491
+ nonce: authorizationCode.nonce,
492
+ authenticationContext: snapshotAuthenticationContext(authorizationCode.authenticationContext),
493
+ });
494
+ return authorizationCode;
495
+ }
496
+ /**
497
+ * Removes a code, accepting the record or its raw string. Resolves `true`
498
+ * when a code was removed, `false` otherwise.
499
+ */
500
+ async revoke(authorizationCode) {
501
+ const code = typeof authorizationCode === "string"
502
+ ? authorizationCode
503
+ : authorizationCode.code;
504
+ return this.#codes.delete(await sha256Hash(code));
505
+ }
506
+ }
507
+ /** In-memory `DeviceAuthorizationServiceInterface` implementation. */
508
+ export class MemoryDeviceAuthorizationService {
509
+ /** Device-authorization lifetime in seconds applied by {@linkcode expiresAt}. */
510
+ lifetime;
511
+ /** Minimum seconds clients must wait between token-endpoint polls. */
512
+ interval;
513
+ #clientService;
514
+ #userService;
515
+ #byDeviceCode = new Map();
516
+ #byUserCode = new Map();
517
+ /**
518
+ * Creates an empty device-authorization store.
519
+ *
520
+ * @param options Backing client/user services and optional lifetime and
521
+ * polling interval.
522
+ */
523
+ constructor(options) {
524
+ this.#clientService = options.clientService;
525
+ this.#userService = options.userService;
526
+ this.lifetime = options.lifetime ?? 1800;
527
+ this.interval = options.interval ?? 5;
528
+ }
529
+ /** Mints a random opaque device code (polled by the device). */
530
+ generateDeviceCode(_client, _scope) {
531
+ return Promise.resolve(crypto.randomUUID());
532
+ }
533
+ /**
534
+ * Mints a short, human-enterable user code in `XXXX-XXXX` form drawn from an
535
+ * unambiguous alphabet (no vowels or look-alike digits).
536
+ */
537
+ generateUserCode(_client, _scope) {
538
+ const chars = "BCDFGHJKLMNPQRSTVWXYZ23456789";
539
+ let code = "";
540
+ const array = new Uint8Array(8);
541
+ crypto.getRandomValues(array);
542
+ for (let i = 0; i < 8; i++) {
543
+ code += chars[array[i] % chars.length];
544
+ if (i === 3)
545
+ code += "-";
546
+ }
547
+ return Promise.resolve(code);
548
+ }
549
+ /** Returns now plus {@linkcode lifetime}. */
550
+ expiresAt(_client, _scope) {
551
+ return Promise.resolve(new Date(Date.now() + this.lifetime * 1000));
552
+ }
553
+ async #hydrate(stored, deviceCode, userCode) {
554
+ const client = await this.#clientService.get(stored.clientId);
555
+ if (!client)
556
+ return undefined;
557
+ const user = stored.userId
558
+ ? await this.#userService.get(stored.userId)
559
+ : undefined;
560
+ if (stored.userId && !user)
561
+ return undefined;
562
+ return {
563
+ deviceCode,
564
+ userCode,
565
+ expiresAt: stored.expiresAt,
566
+ client,
567
+ scope: stored.scope,
568
+ interval: stored.interval,
569
+ authorized: stored.authorized,
570
+ user,
571
+ denied: stored.denied,
572
+ lastPolled: stored.lastPolled,
573
+ };
574
+ }
575
+ /**
576
+ * Persists a device authorization indexed by both its hashed device code and
577
+ * hashed user code. Returns the input unchanged.
578
+ */
579
+ async save(authorization) {
580
+ const deviceCodeHash = await sha256Hash(authorization.deviceCode);
581
+ const userCodeHash = await sha256Hash(authorization.userCode);
582
+ const stored = {
583
+ deviceCodeHash,
584
+ userCodeHash,
585
+ expiresAt: authorization.expiresAt,
586
+ clientId: authorization.client.id,
587
+ scope: authorization.scope,
588
+ interval: authorization.interval,
589
+ authorized: authorization.authorized,
590
+ userId: authorization.user?.id,
591
+ denied: authorization.denied,
592
+ lastPolled: authorization.lastPolled,
593
+ };
594
+ this.#byDeviceCode.set(deviceCodeHash, stored);
595
+ this.#byUserCode.set(userCodeHash, stored);
596
+ return authorization;
597
+ }
598
+ /**
599
+ * Resolves the authorization for a device code (the value the device polls
600
+ * with), client and user rehydrated, or `undefined` when not found.
601
+ */
602
+ async getByDeviceCode(deviceCode) {
603
+ const stored = this.#byDeviceCode.get(await sha256Hash(deviceCode));
604
+ if (!stored)
605
+ return undefined;
606
+ return this.#hydrate(stored, deviceCode, "");
607
+ }
608
+ /**
609
+ * Resolves the authorization for a user code (the value the user enters in a
610
+ * browser), client and user rehydrated, or `undefined` when not found.
611
+ */
612
+ async getByUserCode(userCode) {
613
+ const stored = this.#byUserCode.get(await sha256Hash(userCode));
614
+ if (!stored)
615
+ return undefined;
616
+ return this.#hydrate(stored, "", userCode);
617
+ }
618
+ async #findStored(authorization) {
619
+ if (authorization.deviceCode) {
620
+ const stored = this.#byDeviceCode.get(await sha256Hash(authorization.deviceCode));
621
+ if (stored)
622
+ return stored;
623
+ }
624
+ if (authorization.userCode) {
625
+ const stored = this.#byUserCode.get(await sha256Hash(authorization.userCode));
626
+ if (stored)
627
+ return stored;
628
+ }
629
+ return undefined;
630
+ }
631
+ /**
632
+ * Marks the authorization granted by `user`, optionally narrowing `scope`,
633
+ * so a subsequent device-code poll can exchange it for a token. Returns the
634
+ * updated record. Throws when the authorization is not stored.
635
+ */
636
+ async approve(authorization, user, scope) {
637
+ const stored = await this.#findStored(authorization);
638
+ if (!stored) {
639
+ throw new Error("approve: device authorization not found");
640
+ }
641
+ stored.authorized = true;
642
+ stored.userId = user.id;
643
+ if (scope !== undefined)
644
+ stored.scope = scope;
645
+ return {
646
+ ...authorization,
647
+ authorized: true,
648
+ user,
649
+ scope: stored.scope,
650
+ };
651
+ }
652
+ /**
653
+ * Marks the authorization denied so a subsequent poll is rejected. Returns
654
+ * the updated record. Throws when the authorization is not stored.
655
+ */
656
+ async deny(authorization) {
657
+ const stored = await this.#findStored(authorization);
658
+ if (!stored) {
659
+ throw new Error("deny: device authorization not found");
660
+ }
661
+ stored.denied = true;
662
+ return { ...authorization, denied: true };
663
+ }
664
+ /**
665
+ * Records that the device just polled (and stores its current interval) so
666
+ * `slow_down` rate limiting can be enforced. Returns the updated record.
667
+ * Throws when the authorization is not stored.
668
+ */
669
+ async updateLastPolled(authorization) {
670
+ const stored = await this.#findStored(authorization);
671
+ if (!stored) {
672
+ throw new Error("updateLastPolled: device authorization not found");
673
+ }
674
+ stored.lastPolled = new Date();
675
+ stored.interval = authorization.interval;
676
+ return { ...authorization, lastPolled: stored.lastPolled };
677
+ }
678
+ /**
679
+ * Removes a device authorization, accepting a raw device code or any record
680
+ * this store returns — including the partially hydrated ones from
681
+ * {@linkcode getByDeviceCode} and {@linkcode getByUserCode}, which are
682
+ * resolved back to the stored entry so both its device-code and user-code
683
+ * index entries are dropped. Resolves `true` only for the call that removed
684
+ * a live authorization, `false` otherwise.
685
+ */
686
+ async revoke(authorization) {
687
+ if (typeof authorization === "string") {
688
+ const hashed = await sha256Hash(authorization);
689
+ const stored = this.#byDeviceCode.get(hashed);
690
+ if (!stored)
691
+ return false;
692
+ this.#byDeviceCode.delete(hashed);
693
+ this.#byUserCode.delete(stored.userCodeHash);
694
+ return true;
695
+ }
696
+ const stored = await this.#findStored(authorization);
697
+ if (!stored)
698
+ return false;
699
+ const deleted = this.#byDeviceCode.delete(stored.deviceCodeHash);
700
+ this.#byUserCode.delete(stored.userCodeHash);
701
+ return deleted;
702
+ }
703
+ }