@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,280 @@
1
+ /**
2
+ * Browser client for an app fronted by a Backend-for-Frontend.
3
+ *
4
+ * The BFF holds the tokens; this client only ever talks to the BFF's
5
+ * `/auth/*` routes with the session cookie attached, so no token reaches the
6
+ * browser. Pair it with the `@udibo/oauth2/hono/bff` adapter, or any BFF
7
+ * serving the same four routes.
8
+ *
9
+ * @module
10
+ */
11
+ import { loginContinuation as computeLoginContinuation } from "../utils/url.js";
12
+ import { receiveJson, sendGuarded } from "./_http.js";
13
+ import { mergedHeaders, OAuth2ClientBase, SIGNED_OUT, } from "./base.js";
14
+ /**
15
+ * OAuth2 client for a browser app whose sessions live in a
16
+ * Backend-for-Frontend.
17
+ *
18
+ * Every method here is a same-origin call to the BFF with `credentials:
19
+ * "include"`, never a call to the authorization server. There is no token
20
+ * storage, no PKCE, and no access token to read — if you need one, the app
21
+ * wants a {@link DirectClient} instead.
22
+ *
23
+ * @example Same-origin BFF
24
+ * ```ts
25
+ * import { BffClient } from "@udibo/oauth2/client";
26
+ *
27
+ * const client = new BffClient();
28
+ *
29
+ * const session = await client.getSession();
30
+ * if (!session.isAuthenticated) {
31
+ * const { url } = await client.login({ returnTo: location.pathname });
32
+ * location.assign(url);
33
+ * }
34
+ *
35
+ * const res = await client.fetch("/api/items");
36
+ * ```
37
+ *
38
+ * @example BFF mounted elsewhere, with a custom CSRF header
39
+ * ```ts
40
+ * import { BffClient } from "@udibo/oauth2/client";
41
+ *
42
+ * const client = new BffClient({
43
+ * endpoints: { baseUrl: "https://api.example.com", login: "/session/start" },
44
+ * csrfHeader: { name: "x-requested-with", value: "spa" },
45
+ * });
46
+ * ```
47
+ */
48
+ export class BffClient extends OAuth2ClientBase {
49
+ #endpoints;
50
+ #authorizePath;
51
+ #csrfHeader;
52
+ /**
53
+ * Builds a client against the BFF's `/auth/*` routes.
54
+ *
55
+ * @param options Endpoint overrides, the CSRF header to send, and the
56
+ * `fetch` to use. Every field is optional; the defaults match `HonoBff`
57
+ * mounted at `/auth` on the same origin.
58
+ */
59
+ constructor(options = {}) {
60
+ super(options);
61
+ const endpoints = options.endpoints ?? {};
62
+ this.#endpoints = {
63
+ baseUrl: endpoints.baseUrl ?? "",
64
+ login: endpoints.login ?? "/auth/login",
65
+ logout: endpoints.logout ?? "/auth/logout",
66
+ session: endpoints.session ?? "/auth/session",
67
+ };
68
+ this.#authorizePath = options.authorizePath;
69
+ this.#csrfHeader = options.csrfHeader === false ? {} : {
70
+ [options.csrfHeader?.name ?? "x-csrf"]: options.csrfHeader?.value ?? "1",
71
+ };
72
+ }
73
+ #url(path) {
74
+ const origin = typeof location !== "undefined"
75
+ ? location.origin
76
+ : "http://localhost";
77
+ return new URL(`${this.#endpoints.baseUrl}${path}`, origin).toString();
78
+ }
79
+ /**
80
+ * Builds the BFF login URL. Navigate the browser there; the BFF runs the
81
+ * authorization-code flow and drops the session cookie before redirecting
82
+ * back to `returnTo`.
83
+ *
84
+ * @param options `returnTo` is passed through as the `return_to` query
85
+ * parameter. The BFF is responsible for validating it against open
86
+ * redirects.
87
+ * @returns The URL to navigate to.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { BffClient } from "@udibo/oauth2/client";
92
+ *
93
+ * const client = new BffClient();
94
+ * const { url } = await client.login({ returnTo: "/dashboard" });
95
+ * location.assign(url);
96
+ * ```
97
+ */
98
+ login(options = {}) {
99
+ const url = new URL(this.#url(this.#endpoints.login));
100
+ if (options.returnTo)
101
+ url.searchParams.set("return_to", options.returnTo);
102
+ return Promise.resolve({ url: url.toString() });
103
+ }
104
+ /**
105
+ * Builds the BFF logout URL and emits `logged_out` so the UI can render
106
+ * signed-out immediately. Navigate the browser to the returned URL; the BFF
107
+ * destroys the session and clears the cookie.
108
+ *
109
+ * @param options `returnTo` is passed through as the `return_to` query
110
+ * parameter.
111
+ * @returns The URL to navigate to.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * import { BffClient } from "@udibo/oauth2/client";
116
+ *
117
+ * const client = new BffClient();
118
+ * const { url } = await client.logout({ returnTo: "/" });
119
+ * location.assign(url!);
120
+ * ```
121
+ */
122
+ logout(options = {}) {
123
+ const url = new URL(this.#url(this.#endpoints.logout));
124
+ if (options.returnTo)
125
+ url.searchParams.set("return_to", options.returnTo);
126
+ this.emit({ type: "logged_out", reason: "user" });
127
+ return Promise.resolve({ url: url.toString() });
128
+ }
129
+ /**
130
+ * Probes `GET /auth/session` and returns the full {@link SessionState} —
131
+ * `isAuthenticated`, `user`, plus the `sessionExpiresIn` and `logoutUrl` a
132
+ * SPA uses to schedule a re-probe and render a sign-out link.
133
+ *
134
+ * **Never rejects**, per {@link OAuth2ClientBase.getSession}: an
135
+ * unreachable BFF, a non-OK status, and a body that is not a JSON object
136
+ * (an HTML error page from a misrouted path is the common one) all read as
137
+ * signed out. A failure that was not simply "nobody is signed in" — anything
138
+ * other than a `401` — also emits an `error` event so a UI can tell the two
139
+ * apart. The read is bounded and deadlined like every other call this
140
+ * package makes.
141
+ *
142
+ * Only `isAuthenticated: true` carries `user` / `sessionExpiresIn` /
143
+ * `logoutUrl` through; an anonymous payload always reports them as `null`,
144
+ * whatever the BFF put in the body.
145
+ *
146
+ * @returns The session as the BFF reports it. Tokens are never included.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * import { BffClient } from "@udibo/oauth2/client";
151
+ *
152
+ * const { isAuthenticated, logoutUrl } = await new BffClient().getSession();
153
+ * ```
154
+ */
155
+ async getSession() {
156
+ return await this.#probe(false);
157
+ }
158
+ async #probe(quiet) {
159
+ let body;
160
+ try {
161
+ const res = await sendGuarded((input, init) => this.fetchImpl(input, init), this.#url(this.#endpoints.session), {
162
+ credentials: "include",
163
+ headers: { Accept: "application/json", ...this.#csrfHeader },
164
+ }, "session endpoint");
165
+ if (res.status === 401) {
166
+ await res.body?.cancel();
167
+ return SIGNED_OUT;
168
+ }
169
+ body = await receiveJson(res, "session endpoint");
170
+ }
171
+ catch (error) {
172
+ if (!quiet)
173
+ this.emit({ type: "error", error });
174
+ return SIGNED_OUT;
175
+ }
176
+ const session = body;
177
+ if (!session.isAuthenticated)
178
+ return SIGNED_OUT;
179
+ return {
180
+ isAuthenticated: true,
181
+ user: session.user ?? null,
182
+ sessionExpiresIn: session.sessionExpiresIn ?? null,
183
+ logoutUrl: session.logoutUrl ?? null,
184
+ };
185
+ }
186
+ /**
187
+ * The signed-in user's claims from the session probe, or `null`.
188
+ *
189
+ * @returns The `user` field of {@link getSession}.
190
+ */
191
+ async getUser() {
192
+ return (await this.getSession()).user;
193
+ }
194
+ /**
195
+ * Re-probes `GET /auth/session`.
196
+ *
197
+ * The BFF owns credential renewal — its `attachToken` middleware refreshes
198
+ * the access token server-side when a proxied call needs one — so this
199
+ * reports the current state rather than driving a refresh itself. Call it
200
+ * shortly before {@link SessionState.sessionExpiresIn} elapses to keep the
201
+ * UI honest about whether the session is still alive.
202
+ *
203
+ * Emits no `error` event, per {@link OAuth2ClientBase.renewSession}: this
204
+ * runs on a background timer, where a failed probe means "ask again shortly",
205
+ * not "show the user a problem".
206
+ *
207
+ * @returns The refreshed {@link SessionState}.
208
+ */
209
+ renewSession() {
210
+ return this.#probe(true);
211
+ }
212
+ /**
213
+ * Fetches with the session cookie and the CSRF header attached, so the BFF
214
+ * authenticates the call and its CSRF guard accepts it.
215
+ *
216
+ * A `401` emits `logged_out` with reason `session_expired` and is returned
217
+ * as-is — the BFF, not the browser, decides whether a refresh is possible.
218
+ *
219
+ * @param input Request target, as for `fetch`. A `Request` keeps the headers
220
+ * and body it was built with.
221
+ * @param init Request options. A caller-set CSRF header is left alone, and
222
+ * `init.headers` override same-named headers on a `Request` input.
223
+ * @returns The BFF's response, unmodified.
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * import { BffClient } from "@udibo/oauth2/client";
228
+ *
229
+ * const res = await new BffClient().fetch("/api/items");
230
+ * ```
231
+ */
232
+ async fetch(input, init = {}) {
233
+ const headers = mergedHeaders(input, init);
234
+ for (const [name, value] of Object.entries(this.#csrfHeader)) {
235
+ if (!headers.has(name))
236
+ headers.set(name, value);
237
+ }
238
+ const res = await this.fetchImpl(input, {
239
+ ...init,
240
+ credentials: "include",
241
+ headers,
242
+ });
243
+ if (res.status === 401) {
244
+ this.emit({ type: "logged_out", reason: "session_expired" });
245
+ }
246
+ return res;
247
+ }
248
+ /**
249
+ * Builds the URL that (re)starts sign-in toward `returnTo`, for an app whose
250
+ * own login surface (a sign-in form, create-account, forgot-password) hands
251
+ * back to the BFF flow after authenticating a user: resume an in-flight
252
+ * authorize URL, or start a fresh BFF login.
253
+ *
254
+ * Pure and synchronous — safe in render. The mirror of
255
+ * `HonoBff.loginContinuation`, so both sides of the app agree on where
256
+ * "sign in" goes.
257
+ *
258
+ * Recognizing an in-flight authorize URL requires `authorizePath` in the
259
+ * constructor options; without it every `returnTo` starts a fresh login.
260
+ *
261
+ * @param returnTo Where the user was headed. `null` / `undefined` fall back
262
+ * to `/`.
263
+ * @returns The URL to redirect to.
264
+ *
265
+ * @example
266
+ * ```ts
267
+ * import { BffClient } from "@udibo/oauth2/client";
268
+ *
269
+ * const client = new BffClient({ authorizePath: "/api/oauth2/authorize" });
270
+ * const href = client.loginContinuation("/api/oauth2/authorize?client_id=a");
271
+ * ```
272
+ */
273
+ loginContinuation(returnTo) {
274
+ return computeLoginContinuation(returnTo, {
275
+ authorizeEndpoint: this.#authorizePath,
276
+ loginPath: `${this.#endpoints.baseUrl}${this.#endpoints.login}`,
277
+ defaultReturnTo: "/",
278
+ });
279
+ }
280
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Browser-storage implementations of {@link RefreshTokenStorage} and
3
+ * {@link AuthRequestStorage}.
4
+ *
5
+ * Both ship from the `@udibo/oauth2/client` subpath alongside the
6
+ * `Memory*` defaults so non-React browser consumers (vanilla JS,
7
+ * Vue, Svelte, …) can use them too.
8
+ *
9
+ * Both implementations are **SSR-safe**: if `window` / `indexedDB` /
10
+ * `sessionStorage` is undefined at construction, the storage degrades
11
+ * to a no-op (`get` returns `null`, `set`/`clear` do nothing). The
12
+ * client constructs the real backing on first use in the browser.
13
+ *
14
+ * @module
15
+ */
16
+ import type { AuthRequestRecord, AuthRequestStorage, RefreshTokenStorage } from "./storage.js";
17
+ /** Options for {@link IndexedDBRefreshTokenStorage}. */
18
+ export interface IndexedDBRefreshTokenStorageOptions {
19
+ /**
20
+ * Discriminates this client's refresh token from every other OAuth2
21
+ * client's on the same origin, which all share one IndexedDB namespace.
22
+ * Pass the `client_id` handed to the client; when one client id is
23
+ * registered at more than one issuer, include the issuer too so the two
24
+ * registrations do not share a slot.
25
+ */
26
+ clientId: string;
27
+ /** IndexedDB database name. Defaults to `"@udibo/oauth2"`. */
28
+ databaseName?: string;
29
+ /** Object store name. Defaults to `"refresh-token"`. */
30
+ storeName?: string;
31
+ }
32
+ /**
33
+ * Refresh token storage backed by IndexedDB.
34
+ *
35
+ * IndexedDB is preferred over `localStorage` because the latter is
36
+ * synchronously readable from any script that runs in the document
37
+ * (including injected XSS payloads). IndexedDB's same-origin guarantees
38
+ * are similar but its async API and per-database isolation make
39
+ * exfiltration somewhat harder. Combined with refresh-token rotation +
40
+ * reuse detection on the authorization server, it's the recommended
41
+ * default for a browser `DirectClient`.
42
+ *
43
+ * No `localStorage`-backed variant is shipped: it is synchronously
44
+ * readable by any in-document script (including injected XSS), which
45
+ * defeats the protection above.
46
+ *
47
+ * Every instance needs a
48
+ * {@link IndexedDBRefreshTokenStorageOptions.clientId} — the token is stored
49
+ * under it, so two clients on one origin (two APIs, two issuers) keep
50
+ * separate refresh tokens instead of overwriting each other's.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * import {
55
+ * DirectClient,
56
+ * IndexedDBRefreshTokenStorage,
57
+ * } from "@udibo/oauth2/client";
58
+ *
59
+ * const client = new DirectClient({
60
+ * clientId: "web-app",
61
+ * issuer: "https://sso.example",
62
+ * refreshTokenStorage: new IndexedDBRefreshTokenStorage({
63
+ * clientId: "web-app",
64
+ * }),
65
+ * });
66
+ * ```
67
+ */
68
+ export declare class IndexedDBRefreshTokenStorage implements RefreshTokenStorage {
69
+ #private;
70
+ /**
71
+ * Names the slot this instance owns.
72
+ *
73
+ * @param options Must carry `clientId`; `databaseName` and `storeName`
74
+ * isolate further when one client id needs more than one slot.
75
+ * @throws {TypeError} when `clientId` is empty, which would put every
76
+ * client on the origin back in one slot.
77
+ */
78
+ constructor(options: IndexedDBRefreshTokenStorageOptions);
79
+ /**
80
+ * Returns the stored refresh token, or `null` when none is stored, when
81
+ * `indexedDB` is unavailable (SSR / non-browser), or when another tab is
82
+ * blocking the store's creation.
83
+ *
84
+ * @throws {DOMException} when IndexedDB rejects the read.
85
+ */
86
+ get(): Promise<string | null>;
87
+ /**
88
+ * Persists {@linkcode value} as the refresh token. A no-op when `indexedDB`
89
+ * is unavailable (SSR / non-browser).
90
+ *
91
+ * @throws {DOMException} when IndexedDB rejects the write.
92
+ */
93
+ set(value: string): Promise<void>;
94
+ /**
95
+ * Discards the stored refresh token. A no-op when `indexedDB` is unavailable
96
+ * (SSR / non-browser).
97
+ *
98
+ * @throws {DOMException} when IndexedDB rejects the delete.
99
+ */
100
+ clear(): Promise<void>;
101
+ }
102
+ /** Options for {@link SessionStorageAuthRequestStorage}. */
103
+ export interface SessionStorageAuthRequestStorageOptions {
104
+ /** sessionStorage key prefix. Defaults to `"oauth2:auth-req:"`. */
105
+ keyPrefix?: string;
106
+ /**
107
+ * Maximum age of an auth request before it's treated as expired
108
+ * (epoch-ms). Reads of older entries return `null` and the entry is
109
+ * removed. Defaults to 10 minutes — long enough for a slow
110
+ * authorize-redirect round-trip, short enough that stale state from a
111
+ * previous abandoned login doesn't accumulate.
112
+ */
113
+ ttlMs?: number;
114
+ }
115
+ /**
116
+ * Auth-request storage backed by `sessionStorage`.
117
+ *
118
+ * Survives the authorize-redirect round-trip (sessionStorage persists
119
+ * across navigations within the same tab) but doesn't outlive the tab,
120
+ * so a forgotten in-flight authorization doesn't leak across browser
121
+ * sessions.
122
+ *
123
+ * Stale entries past {@link SessionStorageAuthRequestStorageOptions.ttlMs}
124
+ * are pruned on read.
125
+ */
126
+ export declare class SessionStorageAuthRequestStorage implements AuthRequestStorage {
127
+ #private;
128
+ /** Configures the key prefix and TTL; both default per option. */
129
+ constructor(options?: SessionStorageAuthRequestStorageOptions);
130
+ /**
131
+ * Returns the record for {@linkcode state}, or `null` when it is absent,
132
+ * unparseable, expired past the configured TTL, or `sessionStorage` is
133
+ * unavailable (SSR / non-browser). Expired or unparseable entries are pruned.
134
+ */
135
+ get(state: string): AuthRequestRecord | null;
136
+ /**
137
+ * Stores {@linkcode value} under {@linkcode state}. A no-op when
138
+ * `sessionStorage` is unavailable (SSR / non-browser).
139
+ */
140
+ set(state: string, value: AuthRequestRecord): void;
141
+ /**
142
+ * Removes the record for {@linkcode state}. A no-op when `sessionStorage` is
143
+ * unavailable (SSR / non-browser).
144
+ */
145
+ delete(state: string): void;
146
+ /**
147
+ * Removes every record under the configured key prefix, leaving unrelated
148
+ * `sessionStorage` entries intact. A no-op when `sessionStorage` is
149
+ * unavailable (SSR / non-browser).
150
+ */
151
+ clear(): void;
152
+ }
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Browser-storage implementations of {@link RefreshTokenStorage} and
3
+ * {@link AuthRequestStorage}.
4
+ *
5
+ * Both ship from the `@udibo/oauth2/client` subpath alongside the
6
+ * `Memory*` defaults so non-React browser consumers (vanilla JS,
7
+ * Vue, Svelte, …) can use them too.
8
+ *
9
+ * Both implementations are **SSR-safe**: if `window` / `indexedDB` /
10
+ * `sessionStorage` is undefined at construction, the storage degrades
11
+ * to a no-op (`get` returns `null`, `set`/`clear` do nothing). The
12
+ * client constructs the real backing on first use in the browser.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * Refresh token storage backed by IndexedDB.
18
+ *
19
+ * IndexedDB is preferred over `localStorage` because the latter is
20
+ * synchronously readable from any script that runs in the document
21
+ * (including injected XSS payloads). IndexedDB's same-origin guarantees
22
+ * are similar but its async API and per-database isolation make
23
+ * exfiltration somewhat harder. Combined with refresh-token rotation +
24
+ * reuse detection on the authorization server, it's the recommended
25
+ * default for a browser `DirectClient`.
26
+ *
27
+ * No `localStorage`-backed variant is shipped: it is synchronously
28
+ * readable by any in-document script (including injected XSS), which
29
+ * defeats the protection above.
30
+ *
31
+ * Every instance needs a
32
+ * {@link IndexedDBRefreshTokenStorageOptions.clientId} — the token is stored
33
+ * under it, so two clients on one origin (two APIs, two issuers) keep
34
+ * separate refresh tokens instead of overwriting each other's.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * import {
39
+ * DirectClient,
40
+ * IndexedDBRefreshTokenStorage,
41
+ * } from "@udibo/oauth2/client";
42
+ *
43
+ * const client = new DirectClient({
44
+ * clientId: "web-app",
45
+ * issuer: "https://sso.example",
46
+ * refreshTokenStorage: new IndexedDBRefreshTokenStorage({
47
+ * clientId: "web-app",
48
+ * }),
49
+ * });
50
+ * ```
51
+ */
52
+ export class IndexedDBRefreshTokenStorage {
53
+ #databaseName;
54
+ #storeName;
55
+ #key;
56
+ /**
57
+ * Names the slot this instance owns.
58
+ *
59
+ * @param options Must carry `clientId`; `databaseName` and `storeName`
60
+ * isolate further when one client id needs more than one slot.
61
+ * @throws {TypeError} when `clientId` is empty, which would put every
62
+ * client on the origin back in one slot.
63
+ */
64
+ constructor(options) {
65
+ if (!options.clientId) {
66
+ throw new TypeError("`clientId` is required so clients on one origin do not share a " +
67
+ "refresh token slot");
68
+ }
69
+ this.#databaseName = options.databaseName ?? "@udibo/oauth2";
70
+ this.#storeName = options.storeName ?? "refresh-token";
71
+ this.#key = options.clientId;
72
+ }
73
+ /**
74
+ * Returns the stored refresh token, or `null` when none is stored, when
75
+ * `indexedDB` is unavailable (SSR / non-browser), or when another tab is
76
+ * blocking the store's creation.
77
+ *
78
+ * @throws {DOMException} when IndexedDB rejects the read.
79
+ */
80
+ async get() {
81
+ const db = await this.#openDb();
82
+ if (!db)
83
+ return null;
84
+ return await new Promise((resolve, reject) => {
85
+ const tx = db.transaction(this.#storeName, "readonly");
86
+ const req = tx.objectStore(this.#storeName).get(this.#key);
87
+ req.onsuccess = () => resolve(req.result ?? null);
88
+ req.onerror = () => reject(req.error);
89
+ tx.oncomplete = () => db.close();
90
+ });
91
+ }
92
+ /**
93
+ * Persists {@linkcode value} as the refresh token. A no-op when `indexedDB`
94
+ * is unavailable (SSR / non-browser).
95
+ *
96
+ * @throws {DOMException} when IndexedDB rejects the write.
97
+ */
98
+ async set(value) {
99
+ const db = await this.#openDb();
100
+ if (!db)
101
+ return;
102
+ await new Promise((resolve, reject) => {
103
+ const tx = db.transaction(this.#storeName, "readwrite");
104
+ const req = tx.objectStore(this.#storeName).put(value, this.#key);
105
+ req.onerror = () => reject(req.error);
106
+ tx.oncomplete = () => {
107
+ db.close();
108
+ resolve();
109
+ };
110
+ });
111
+ }
112
+ /**
113
+ * Discards the stored refresh token. A no-op when `indexedDB` is unavailable
114
+ * (SSR / non-browser).
115
+ *
116
+ * @throws {DOMException} when IndexedDB rejects the delete.
117
+ */
118
+ async clear() {
119
+ const db = await this.#openDb();
120
+ if (!db)
121
+ return;
122
+ await new Promise((resolve, reject) => {
123
+ const tx = db.transaction(this.#storeName, "readwrite");
124
+ const req = tx.objectStore(this.#storeName).delete(this.#key);
125
+ req.onerror = () => reject(req.error);
126
+ tx.oncomplete = () => {
127
+ db.close();
128
+ resolve();
129
+ };
130
+ });
131
+ }
132
+ async #openDb() {
133
+ if (typeof indexedDB === "undefined")
134
+ return null;
135
+ const db = await this.#open();
136
+ if (!db || db.objectStoreNames.contains(this.#storeName))
137
+ return db;
138
+ const nextVersion = db.version + 1;
139
+ db.close();
140
+ return await this.#open(nextVersion);
141
+ }
142
+ #open(version) {
143
+ return new Promise((resolve, reject) => {
144
+ const req = version === undefined
145
+ ? indexedDB.open(this.#databaseName)
146
+ : indexedDB.open(this.#databaseName, version);
147
+ req.onupgradeneeded = () => {
148
+ const db = req.result;
149
+ if (!db.objectStoreNames.contains(this.#storeName)) {
150
+ db.createObjectStore(this.#storeName);
151
+ }
152
+ };
153
+ req.onsuccess = () => resolve(req.result);
154
+ req.onerror = () => reject(req.error);
155
+ req.onblocked = () => resolve(null);
156
+ });
157
+ }
158
+ }
159
+ /**
160
+ * Auth-request storage backed by `sessionStorage`.
161
+ *
162
+ * Survives the authorize-redirect round-trip (sessionStorage persists
163
+ * across navigations within the same tab) but doesn't outlive the tab,
164
+ * so a forgotten in-flight authorization doesn't leak across browser
165
+ * sessions.
166
+ *
167
+ * Stale entries past {@link SessionStorageAuthRequestStorageOptions.ttlMs}
168
+ * are pruned on read.
169
+ */
170
+ export class SessionStorageAuthRequestStorage {
171
+ #keyPrefix;
172
+ #ttlMs;
173
+ /** Configures the key prefix and TTL; both default per option. */
174
+ constructor(options = {}) {
175
+ this.#keyPrefix = options.keyPrefix ?? "oauth2:auth-req:";
176
+ this.#ttlMs = options.ttlMs ?? 10 * 60 * 1000;
177
+ }
178
+ /**
179
+ * Returns the record for {@linkcode state}, or `null` when it is absent,
180
+ * unparseable, expired past the configured TTL, or `sessionStorage` is
181
+ * unavailable (SSR / non-browser). Expired or unparseable entries are pruned.
182
+ */
183
+ get(state) {
184
+ const storage = this.#storage();
185
+ if (!storage)
186
+ return null;
187
+ const raw = storage.getItem(this.#keyPrefix + state);
188
+ if (!raw)
189
+ return null;
190
+ try {
191
+ const parsed = JSON.parse(raw);
192
+ if (Date.now() - parsed.createdAt > this.#ttlMs) {
193
+ storage.removeItem(this.#keyPrefix + state);
194
+ return null;
195
+ }
196
+ return parsed;
197
+ }
198
+ catch {
199
+ storage.removeItem(this.#keyPrefix + state);
200
+ return null;
201
+ }
202
+ }
203
+ /**
204
+ * Stores {@linkcode value} under {@linkcode state}. A no-op when
205
+ * `sessionStorage` is unavailable (SSR / non-browser).
206
+ */
207
+ set(state, value) {
208
+ const storage = this.#storage();
209
+ if (!storage)
210
+ return;
211
+ storage.setItem(this.#keyPrefix + state, JSON.stringify(value));
212
+ }
213
+ /**
214
+ * Removes the record for {@linkcode state}. A no-op when `sessionStorage` is
215
+ * unavailable (SSR / non-browser).
216
+ */
217
+ delete(state) {
218
+ const storage = this.#storage();
219
+ if (!storage)
220
+ return;
221
+ storage.removeItem(this.#keyPrefix + state);
222
+ }
223
+ /**
224
+ * Removes every record under the configured key prefix, leaving unrelated
225
+ * `sessionStorage` entries intact. A no-op when `sessionStorage` is
226
+ * unavailable (SSR / non-browser).
227
+ */
228
+ clear() {
229
+ const storage = this.#storage();
230
+ if (!storage)
231
+ return;
232
+ const keysToRemove = [];
233
+ for (let i = 0; i < storage.length; i++) {
234
+ const key = storage.key(i);
235
+ if (key && key.startsWith(this.#keyPrefix))
236
+ keysToRemove.push(key);
237
+ }
238
+ for (const key of keysToRemove)
239
+ storage.removeItem(key);
240
+ }
241
+ #storage() {
242
+ if (typeof sessionStorage === "undefined")
243
+ return null;
244
+ return sessionStorage;
245
+ }
246
+ }