@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,250 @@
1
+ /**
2
+ * Session stores used by {@link HonoBff}.
3
+ *
4
+ * Two shapes are supported:
5
+ *
6
+ * - **Stateful**: the cookie holds a random session id; tokens live in a
7
+ * server-side store keyed by that id ({@link MemorySessionStore}, or
8
+ * your own DB-backed {@link SessionStore}). Pros: instant revocation,
9
+ * tiny cookie, leaked session id is useless outside your server. Cons:
10
+ * needs a store to scale horizontally.
11
+ *
12
+ * - **Stateless** ({@link EncryptedCookieSessionStore}): the cookie value
13
+ * *is* the encrypted token bundle (AES-GCM with a server-held secret),
14
+ * no server-side storage needed. Pros: no infrastructure beyond the
15
+ * secret. Cons: revocation requires `/revoke` + cookie clear, cookie
16
+ * grows with token size, key rotation needs care.
17
+ *
18
+ * A raw "token in cookie" store is deliberately **not** shipped: a leaked
19
+ * log line containing the cookie would yield a working bearer token. The
20
+ * AES-GCM encryption in the stateless store is what makes that pattern
21
+ * safe.
22
+ *
23
+ * @module
24
+ */
25
+ import type { TokenBundle, UserInfoClaims } from "../../../client/mod.js";
26
+ /** The data a BFF persists per session. */
27
+ export interface SessionData {
28
+ /** The current token bundle (access token + expiry + scope + id_token). */
29
+ tokens: TokenBundle;
30
+ /** The refresh token, if one was issued. */
31
+ refreshToken?: string;
32
+ /**
33
+ * Cached user claims surfaced by `GET /auth/session` without exposing
34
+ * tokens to the browser. Populated from the id_token or the userinfo
35
+ * endpoint at callback time.
36
+ */
37
+ user?: UserInfoClaims | null;
38
+ /**
39
+ * The OP session id (`sid` claim from the id_token), when present. Used to
40
+ * target a single session during OIDC Back-Channel Logout; matching falls
41
+ * back to the subject (`user.sub`) when the logout_token carries no `sid`.
42
+ */
43
+ sid?: string;
44
+ /** When the session was created (epoch ms). */
45
+ createdAt: number;
46
+ /** When the session was last updated (epoch ms). */
47
+ updatedAt: number;
48
+ }
49
+ /**
50
+ * Optional {@link SessionStore} capability: destroy sessions by subject and/or
51
+ * OP session id, for an OIDC Back-Channel Logout receiver. A store opts in by
52
+ * implementing this method (it is duck-typed at runtime). Stateful stores
53
+ * (server-side records) can; stateless stores (e.g.
54
+ * {@link EncryptedCookieSessionStore}) cannot enumerate their sessions, so they
55
+ * omit it and the BFF's `/auth/backchannel` route stays unavailable.
56
+ */
57
+ export interface BackchannelLogoutStore {
58
+ /**
59
+ * Destroy every session matching a validated logout_token. When `sid` is
60
+ * given, only the session(s) with that OP session id are destroyed; otherwise
61
+ * all sessions for `sub` are destroyed. Returns the number destroyed. Back a
62
+ * persistent implementation with an index on `sid`/`sub`.
63
+ */
64
+ destroyByLogout(criteria: {
65
+ sub?: string;
66
+ sid?: string;
67
+ }): Promise<number>;
68
+ }
69
+ /** Whether a store implements the {@link BackchannelLogoutStore} capability. */
70
+ export declare function supportsBackchannelLogout(store: SessionStore): store is SessionStore & BackchannelLogoutStore;
71
+ /**
72
+ * Default session lifetime — 14 days — shared by
73
+ * {@link EncryptedCookieSessionStore} and `HonoBff`'s
74
+ * `sessionMaxAgeMs`, so a BFF built with neither configured has a cookie
75
+ * and a server-side session that expire together.
76
+ *
77
+ * Long enough that an app used weekly never nags; short enough that a
78
+ * credential left on an unattended phone dies on its own. Set both knobs if
79
+ * your product wants a different window.
80
+ */
81
+ export declare const DEFAULT_SESSION_MAX_AGE_MS: number;
82
+ /**
83
+ * Optional {@link SessionStore} capability: the store enforces a maximum
84
+ * session age, in milliseconds, and says what it is.
85
+ *
86
+ * `HonoBff` reads it to refuse a configuration whose session cookie would die
87
+ * before the sessions this store keeps serving — the pairing that leaves a user
88
+ * signed out while their session is still listed as active. Expose it from a
89
+ * DB-backed store to get that check; omit it and the BFF falls back to its own
90
+ * `sessionMaxAgeMs` bound.
91
+ */
92
+ export interface BoundedSessionStore {
93
+ /** Maximum age, in ms, past which this store stops honoring a session. */
94
+ readonly maxAgeMs: number;
95
+ }
96
+ /**
97
+ * The maximum session age a store enforces, or `undefined` when it does not
98
+ * advertise one (see {@link BoundedSessionStore}).
99
+ */
100
+ export declare function sessionStoreMaxAgeMs(store: SessionStore): number | undefined;
101
+ /**
102
+ * Session persistence contract for {@link HonoBff}.
103
+ *
104
+ * Implementations may be stateful (cookie holds a session id, data lives
105
+ * server-side) or stateless (cookie holds the encrypted payload itself).
106
+ * `create` / `update` return the cookie value to set; `read` takes a
107
+ * cookie value and returns the data (or `null` if the cookie doesn't
108
+ * correspond to a live session). `destroy` removes a session.
109
+ */
110
+ export interface SessionStore {
111
+ /** Create a new session and return the cookie value to set. */
112
+ create(data: SessionData): Promise<string>;
113
+ /** Read an existing session's data, or `null` if not found / invalid. */
114
+ read(cookieValue: string): Promise<SessionData | null>;
115
+ /**
116
+ * Update an existing session's data. Returns the new cookie value to
117
+ * set (may be the same as `cookieValue` for stateful stores; changes
118
+ * with every update for stateless stores). Stateful implementations must
119
+ * atomically reject missing, expired or revoked sessions with
120
+ * `InvalidGrantError`; updating must never create or restore a session.
121
+ */
122
+ update(cookieValue: string, data: SessionData): Promise<string>;
123
+ /** Destroy the session identified by `cookieValue`. */
124
+ destroy(cookieValue: string): Promise<void>;
125
+ }
126
+ /**
127
+ * In-memory stateful session store. Data is lost when the process
128
+ * restarts. Suitable for single-process development or tests; use a
129
+ * persistent store in production.
130
+ */
131
+ export declare class MemorySessionStore implements SessionStore, BackchannelLogoutStore {
132
+ #private;
133
+ /** Stores the session under a fresh random id and returns it as the cookie value. */
134
+ create(data: SessionData): Promise<string>;
135
+ /** Looks up the session by its id, or `null` if no live session matches. */
136
+ read(cookieValue: string): Promise<SessionData | null>;
137
+ /** Replaces the stored data in place; the cookie value (session id) is unchanged. */
138
+ update(cookieValue: string, data: SessionData): Promise<string>;
139
+ /** Removes the session record for the given id. */
140
+ destroy(cookieValue: string): Promise<void>;
141
+ /**
142
+ * Destroys the sessions a validated logout_token targets: the specific `sid`
143
+ * when given, otherwise every session for `sub`. Returns the number
144
+ * destroyed.
145
+ */
146
+ destroyByLogout(criteria: {
147
+ sub?: string;
148
+ sid?: string;
149
+ }): Promise<number>;
150
+ }
151
+ /** Options for {@link EncryptedCookieSessionStore}. */
152
+ export interface EncryptedCookieSessionStoreOptions {
153
+ /**
154
+ * Server-held secret(s) used to derive the AES-256 key(s). Each must have at
155
+ * least 32 bytes of entropy; pass a base64 / hex string or raw bytes.
156
+ *
157
+ * Pass an **ordered list** to rotate the secret without signing everyone out:
158
+ * the **first** entry is current — every cookie is sealed under it — and
159
+ * `read` tries each entry in turn, so a cookie sealed under a still-listed
160
+ * older secret keeps unsealing. Rotate by deploying `[newSecret, oldSecret]`,
161
+ * then drop `oldSecret` once the grace window (at least {@linkcode maxAgeMs},
162
+ * or your longest session lifetime) has elapsed. This is the session-store
163
+ * analog of a multi-key JWKS grace window. A single secret behaves exactly as
164
+ * before — a fleet-wide forced sign-out on rotation.
165
+ */
166
+ secret: string | Uint8Array | Array<string | Uint8Array>;
167
+ /**
168
+ * Maximum age of a sealed cookie, in milliseconds: `read` returns `null` for
169
+ * any cookie sealed longer ago than this — even if it decrypts — bounding how
170
+ * long a captured cookie stays useful without any server-side state. This
171
+ * does **not** make `destroy` real; it only caps the window.
172
+ *
173
+ * Defaults to {@link DEFAULT_SESSION_MAX_AGE_MS} (14 days). There is no
174
+ * unbounded setting: an encrypted bearer cookie nobody can revoke must expire
175
+ * on its own. Pass a larger value for a longer window, and set the BFF's
176
+ * `sessionMaxAgeMs` to match — `HonoBff` throws when its session cookie
177
+ * would die before the sessions this store keeps accepting.
178
+ *
179
+ * `update` re-seals with a fresh stamp, so this is an **inactivity** window
180
+ * for a session the BFF keeps refreshing. Must be a positive number of
181
+ * milliseconds.
182
+ *
183
+ * The store stamps each cookie with a seal time at `create`/`update`; a
184
+ * pre-rotation cookie sealed before this field existed has no stamp and falls
185
+ * back to its own `updatedAt`, so old cookies are bounded too rather than
186
+ * grandfathered as unbounded.
187
+ */
188
+ maxAgeMs?: number;
189
+ }
190
+ /**
191
+ * Stateless session store. The cookie value is the AES-GCM-encrypted
192
+ * JSON-serialized {@link SessionData}. No server-side storage needed.
193
+ *
194
+ * Security notes:
195
+ * - Each secret must have at least 256 bits of entropy. A weak secret defeats
196
+ * the confidentiality of the cookie.
197
+ * - AES-GCM provides authenticated encryption; tampered cookies fail `read`
198
+ * (returns `null`).
199
+ * - `destroy` is a no-op on the server — the cookie must be cleared in the
200
+ * client response for the session to end, and the refresh token should be
201
+ * revoked at the authorization server. Genuine server-side revocation is not
202
+ * achievable statelessly; choose a DB-backed {@link SessionStore} for that.
203
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs} — 14 days unless
204
+ * you set it — bounds the window a captured cookie stays useful but does not
205
+ * make `destroy` real.
206
+ * - Rotate the secret without a fleet-wide sign-out by passing an ordered list;
207
+ * see {@linkcode EncryptedCookieSessionStoreOptions.secret}.
208
+ *
209
+ * @example Rotating the secret with a grace window
210
+ * ```ts
211
+ * // Deploy with the new secret first, the old one second: new cookies seal
212
+ * // under NEW, cookies still sealed under OLD keep unsealing until you drop it.
213
+ * const store = new EncryptedCookieSessionStore({
214
+ * secret: [Deno.env.get("SESSION_SECRET")!, Deno.env.get("SESSION_SECRET_OLD")!],
215
+ * maxAgeMs: 7 * 24 * 60 * 60 * 1000,
216
+ * });
217
+ * ```
218
+ */
219
+ export declare class EncryptedCookieSessionStore implements SessionStore, BoundedSessionStore {
220
+ #private;
221
+ /**
222
+ * Derives each AES-256 key on first use and reuses it for every later
223
+ * operation. The first secret is current (used to seal); all are tried on
224
+ * read.
225
+ *
226
+ * @throws {Error} When the secret list is empty, or when
227
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs} is not a positive
228
+ * finite number of milliseconds.
229
+ */
230
+ constructor(options: EncryptedCookieSessionStoreOptions);
231
+ /**
232
+ * The maximum age, in ms, past which a sealed cookie stops unsealing.
233
+ * Defaults to {@link DEFAULT_SESSION_MAX_AGE_MS}; `HonoBff` reads it to
234
+ * check that its session cookie outlives the sessions this store accepts.
235
+ */
236
+ get maxAgeMs(): number;
237
+ /** Encrypts the session into the cookie value under the current secret; there is no server-side record. */
238
+ create(data: SessionData): Promise<string>;
239
+ /**
240
+ * Decrypts the cookie value, trying each configured secret in turn. Returns
241
+ * `null` if it is tampered, malformed, sealed with an unknown secret, or
242
+ * sealed longer ago than
243
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs}.
244
+ */
245
+ read(cookieValue: string): Promise<SessionData | null>;
246
+ /** Re-encrypts the data into a fresh cookie value under the current secret. */
247
+ update(_cookieValue: string, data: SessionData): Promise<string>;
248
+ /** Stateless — the server has nothing to destroy. The caller must clear the cookie. */
249
+ destroy(_cookieValue: string): Promise<void>;
250
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Session stores used by {@link HonoBff}.
3
+ *
4
+ * Two shapes are supported:
5
+ *
6
+ * - **Stateful**: the cookie holds a random session id; tokens live in a
7
+ * server-side store keyed by that id ({@link MemorySessionStore}, or
8
+ * your own DB-backed {@link SessionStore}). Pros: instant revocation,
9
+ * tiny cookie, leaked session id is useless outside your server. Cons:
10
+ * needs a store to scale horizontally.
11
+ *
12
+ * - **Stateless** ({@link EncryptedCookieSessionStore}): the cookie value
13
+ * *is* the encrypted token bundle (AES-GCM with a server-held secret),
14
+ * no server-side storage needed. Pros: no infrastructure beyond the
15
+ * secret. Cons: revocation requires `/revoke` + cookie clear, cookie
16
+ * grows with token size, key rotation needs care.
17
+ *
18
+ * A raw "token in cookie" store is deliberately **not** shipped: a leaked
19
+ * log line containing the cookie would yield a working bearer token. The
20
+ * AES-GCM encryption in the stateless store is what makes that pattern
21
+ * safe.
22
+ *
23
+ * @module
24
+ */
25
+ import { InvalidGrantError } from "../../../errors.js";
26
+ import { deriveAesKey, sealJson, unsealJson } from "../../../utils/crypto.js";
27
+ /** Whether a store implements the {@link BackchannelLogoutStore} capability. */
28
+ export function supportsBackchannelLogout(store) {
29
+ return typeof store.destroyByLogout ===
30
+ "function";
31
+ }
32
+ /**
33
+ * Default session lifetime — 14 days — shared by
34
+ * {@link EncryptedCookieSessionStore} and `HonoBff`'s
35
+ * `sessionMaxAgeMs`, so a BFF built with neither configured has a cookie
36
+ * and a server-side session that expire together.
37
+ *
38
+ * Long enough that an app used weekly never nags; short enough that a
39
+ * credential left on an unattended phone dies on its own. Set both knobs if
40
+ * your product wants a different window.
41
+ */
42
+ export const DEFAULT_SESSION_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
43
+ /**
44
+ * The maximum session age a store enforces, or `undefined` when it does not
45
+ * advertise one (see {@link BoundedSessionStore}).
46
+ */
47
+ export function sessionStoreMaxAgeMs(store) {
48
+ const maxAgeMs = store.maxAgeMs;
49
+ return typeof maxAgeMs === "number" && Number.isFinite(maxAgeMs)
50
+ ? maxAgeMs
51
+ : undefined;
52
+ }
53
+ /**
54
+ * In-memory stateful session store. Data is lost when the process
55
+ * restarts. Suitable for single-process development or tests; use a
56
+ * persistent store in production.
57
+ */
58
+ export class MemorySessionStore {
59
+ #sessions = new Map();
60
+ /** Stores the session under a fresh random id and returns it as the cookie value. */
61
+ create(data) {
62
+ const id = crypto.randomUUID();
63
+ this.#sessions.set(id, data);
64
+ return Promise.resolve(id);
65
+ }
66
+ /** Looks up the session by its id, or `null` if no live session matches. */
67
+ read(cookieValue) {
68
+ return Promise.resolve(this.#sessions.get(cookieValue) ?? null);
69
+ }
70
+ /** Replaces the stored data in place; the cookie value (session id) is unchanged. */
71
+ update(cookieValue, data) {
72
+ if (!this.#sessions.has(cookieValue)) {
73
+ return Promise.reject(new InvalidGrantError("session is no longer active"));
74
+ }
75
+ this.#sessions.set(cookieValue, data);
76
+ return Promise.resolve(cookieValue);
77
+ }
78
+ /** Removes the session record for the given id. */
79
+ destroy(cookieValue) {
80
+ this.#sessions.delete(cookieValue);
81
+ return Promise.resolve();
82
+ }
83
+ /**
84
+ * Destroys the sessions a validated logout_token targets: the specific `sid`
85
+ * when given, otherwise every session for `sub`. Returns the number
86
+ * destroyed.
87
+ */
88
+ destroyByLogout(criteria) {
89
+ const { sub, sid } = criteria;
90
+ let destroyed = 0;
91
+ for (const [id, data] of this.#sessions) {
92
+ const match = sid !== undefined
93
+ ? data.sid === sid
94
+ : sub !== undefined && data.user?.sub === sub;
95
+ if (match) {
96
+ this.#sessions.delete(id);
97
+ destroyed++;
98
+ }
99
+ }
100
+ return Promise.resolve(destroyed);
101
+ }
102
+ }
103
+ function isSealedSession(value) {
104
+ return typeof value === "object" && value !== null &&
105
+ typeof value.issuedAt === "number" &&
106
+ typeof value.data === "object" &&
107
+ value.data !== null;
108
+ }
109
+ /**
110
+ * Stateless session store. The cookie value is the AES-GCM-encrypted
111
+ * JSON-serialized {@link SessionData}. No server-side storage needed.
112
+ *
113
+ * Security notes:
114
+ * - Each secret must have at least 256 bits of entropy. A weak secret defeats
115
+ * the confidentiality of the cookie.
116
+ * - AES-GCM provides authenticated encryption; tampered cookies fail `read`
117
+ * (returns `null`).
118
+ * - `destroy` is a no-op on the server — the cookie must be cleared in the
119
+ * client response for the session to end, and the refresh token should be
120
+ * revoked at the authorization server. Genuine server-side revocation is not
121
+ * achievable statelessly; choose a DB-backed {@link SessionStore} for that.
122
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs} — 14 days unless
123
+ * you set it — bounds the window a captured cookie stays useful but does not
124
+ * make `destroy` real.
125
+ * - Rotate the secret without a fleet-wide sign-out by passing an ordered list;
126
+ * see {@linkcode EncryptedCookieSessionStoreOptions.secret}.
127
+ *
128
+ * @example Rotating the secret with a grace window
129
+ * ```ts
130
+ * // Deploy with the new secret first, the old one second: new cookies seal
131
+ * // under NEW, cookies still sealed under OLD keep unsealing until you drop it.
132
+ * const store = new EncryptedCookieSessionStore({
133
+ * secret: [Deno.env.get("SESSION_SECRET")!, Deno.env.get("SESSION_SECRET_OLD")!],
134
+ * maxAgeMs: 7 * 24 * 60 * 60 * 1000,
135
+ * });
136
+ * ```
137
+ */
138
+ export class EncryptedCookieSessionStore {
139
+ #secrets;
140
+ #keyPromises;
141
+ #maxAgeMs;
142
+ /**
143
+ * Derives each AES-256 key on first use and reuses it for every later
144
+ * operation. The first secret is current (used to seal); all are tried on
145
+ * read.
146
+ *
147
+ * @throws {Error} When the secret list is empty, or when
148
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs} is not a positive
149
+ * finite number of milliseconds.
150
+ */
151
+ constructor(options) {
152
+ const secrets = Array.isArray(options.secret)
153
+ ? options.secret
154
+ : [options.secret];
155
+ if (secrets.length === 0) {
156
+ throw new Error("secret must not be an empty list.");
157
+ }
158
+ const maxAgeMs = options.maxAgeMs ?? DEFAULT_SESSION_MAX_AGE_MS;
159
+ if (!Number.isFinite(maxAgeMs) || maxAgeMs <= 0) {
160
+ throw new Error("maxAgeMs must be a positive number of milliseconds, got " +
161
+ `${options.maxAgeMs}. A cookie nobody can revoke has to expire on ` +
162
+ "its own; pass a larger value for a longer window.");
163
+ }
164
+ this.#secrets = secrets;
165
+ this.#keyPromises = secrets.map(() => undefined);
166
+ this.#maxAgeMs = maxAgeMs;
167
+ }
168
+ /**
169
+ * The maximum age, in ms, past which a sealed cookie stops unsealing.
170
+ * Defaults to {@link DEFAULT_SESSION_MAX_AGE_MS}; `HonoBff` reads it to
171
+ * check that its session cookie outlives the sessions this store accepts.
172
+ */
173
+ get maxAgeMs() {
174
+ return this.#maxAgeMs;
175
+ }
176
+ #key(index) {
177
+ return this.#keyPromises[index] ??= deriveAesKey(this.#secrets[index]);
178
+ }
179
+ /** Encrypts the session into the cookie value under the current secret; there is no server-side record. */
180
+ async create(data) {
181
+ const sealed = { issuedAt: Date.now(), data };
182
+ return await sealJson(await this.#key(0), sealed);
183
+ }
184
+ /**
185
+ * Decrypts the cookie value, trying each configured secret in turn. Returns
186
+ * `null` if it is tampered, malformed, sealed with an unknown secret, or
187
+ * sealed longer ago than
188
+ * {@linkcode EncryptedCookieSessionStoreOptions.maxAgeMs}.
189
+ */
190
+ async read(cookieValue) {
191
+ for (let index = 0; index < this.#secrets.length; index++) {
192
+ const unsealed = await unsealJson(await this.#key(index), cookieValue);
193
+ if (unsealed === null)
194
+ continue;
195
+ if (isSealedSession(unsealed)) {
196
+ return this.#isExpired(unsealed.issuedAt) ? null : unsealed.data;
197
+ }
198
+ const legacy = unsealed;
199
+ return this.#isExpired(legacy.updatedAt ?? legacy.createdAt)
200
+ ? null
201
+ : legacy;
202
+ }
203
+ return null;
204
+ }
205
+ #isExpired(issuedAt) {
206
+ return Date.now() - issuedAt > this.#maxAgeMs;
207
+ }
208
+ /** Re-encrypts the data into a fresh cookie value under the current secret. */
209
+ update(_cookieValue, data) {
210
+ return this.create(data);
211
+ }
212
+ /** Stateless — the server has nothing to destroy. The caller must clear the cookie. */
213
+ destroy(_cookieValue) {
214
+ return Promise.resolve();
215
+ }
216
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Testing helpers for the Hono BFF adapter.
3
+ *
4
+ * Lets a consumer's tests drive protected endpoints without walking
5
+ * through the full login → authorize → callback flow, and ages the
6
+ * access token in the underlying {@link SessionStore} so a follow-up
7
+ * request through `attachToken()` exercises the refresh path
8
+ * deterministically (no `setTimeout` required).
9
+ *
10
+ * The runner-style helper {@link runSessionStoreContractTests} is
11
+ * available for verifying custom {@link SessionStore} implementations
12
+ * (Postgres, Redis, DynamoDB, …) satisfy the create / read / update /
13
+ * destroy contract.
14
+ *
15
+ * @module
16
+ */
17
+ import "../../../_dnt.polyfills.js";
18
+ import type { ClientInterface } from "../../../models/client.js";
19
+ import type { AbstractScope } from "../../../models/scope.js";
20
+ import type { TokenServiceInterface } from "../../../server/services/token.js";
21
+ import type { TokenBundle, UserInfoClaims } from "../../../client/mod.js";
22
+ import type { HonoBff } from "./bff.js";
23
+ import { type SessionData, type SessionStore } from "./session-store.js";
24
+ /** Options for {@link createTestSession}. */
25
+ export interface CreateTestSessionOptions {
26
+ /**
27
+ * Token bundle to install. Anything you don't supply gets a sensible
28
+ * default (random access token, expires in 1 hour, no refresh token).
29
+ */
30
+ tokens?: Partial<TokenBundle>;
31
+ /** Refresh token to store alongside the bundle. */
32
+ refreshToken?: string;
33
+ /** Cached user claims surfaced by `GET /auth/session`. */
34
+ user?: UserInfoClaims | null;
35
+ /** Override `createdAt` (epoch ms). Defaults to now. */
36
+ createdAt?: number;
37
+ /** Override `updatedAt` (epoch ms). Defaults to now. */
38
+ updatedAt?: number;
39
+ }
40
+ /**
41
+ * Seeds a session in `bff.sessionStore` and returns a `Cookie` header
42
+ * value (`<name>=<value>`) ready to attach to subsequent
43
+ * `app.request(...)` calls. Skips the authorize / callback round-trip
44
+ * entirely — useful for tests that only care about authenticated
45
+ * endpoint behavior.
46
+ *
47
+ * **Important — token recognition.** The seeded access token is random by
48
+ * default and is NOT registered with any resource server. That is enough for
49
+ * `GET /auth/session` (which reads the cached `user` claim), but a route
50
+ * guarded by {@link HonoBff.protect} validates the session's access token
51
+ * against the resource server's token service — so an unrecognized token
52
+ * yields 401. For protected API routes, either pre-register the token
53
+ * (`tokenService.save({ accessToken, ... })` then pass the same
54
+ * `tokens.accessToken` here), or use {@link createAuthenticatedTestSession},
55
+ * which does both in one call.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * // Probe-only (GET /auth/session) — no resource server involved:
60
+ * const cookie = await createTestSession(bff, {
61
+ * user: { sub: "u1", name: "Alice" },
62
+ * });
63
+ * const res = await app.request("/auth/session", { headers: { cookie } });
64
+ * ```
65
+ */
66
+ export declare function createTestSession(bff: HonoBff, options?: CreateTestSessionOptions): Promise<string>;
67
+ /** Options for {@link createAuthenticatedTestSession}. */
68
+ export interface CreateAuthenticatedTestSessionOptions<Client extends ClientInterface, User, Scope extends AbstractScope> {
69
+ /**
70
+ * The resource server's token service. The minted access token is saved
71
+ * here so {@link HonoBff.protect} recognizes it — pass the SAME token
72
+ * service the BFF's resource server validates against.
73
+ */
74
+ tokenService: TokenServiceInterface<Client, User, Scope>;
75
+ /** Client the token is issued to. */
76
+ client: Client;
77
+ /** User the token is issued for. */
78
+ user?: User;
79
+ /** Scope granted to the token (drives `protect("scope")` checks). */
80
+ scope?: Scope;
81
+ /** Access-token lifetime in seconds. Defaults to one hour. */
82
+ accessTokenLifetime?: number;
83
+ /** Cached user claims surfaced by `GET /auth/session`. Defaults to null. */
84
+ claims?: UserInfoClaims | null;
85
+ }
86
+ /**
87
+ * Mints a real access token, registers it with the resource server's token
88
+ * service, AND seeds a matching BFF session — the one-call setup for testing a
89
+ * route guarded by {@link HonoBff.protect}. Returns a `Cookie` header ready to
90
+ * attach to `app.request(...)`.
91
+ *
92
+ * This is the combined form of the two-step pattern (save a token, then
93
+ * {@link createTestSession} with the same access token) that protected-route
94
+ * tests otherwise hand-roll. Works with any {@link SessionStore} the BFF can
95
+ * `create()` into (e.g. `MemorySessionStore`,
96
+ * `EncryptedCookieSessionStore`); apps with a DB-backed store that mints
97
+ * its own cookie/secret should seed via that store directly.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const cookie = await createAuthenticatedTestSession(bff, {
102
+ * tokenService,
103
+ * client: { id: "spa" },
104
+ * user: { id: "u1", username: "alice" },
105
+ * scope: new BasicScope("read write"),
106
+ * claims: { sub: "u1", name: "Alice" },
107
+ * });
108
+ * const res = await app.request("/api/me", { headers: { cookie } });
109
+ * ```
110
+ */
111
+ export declare function createAuthenticatedTestSession<Client extends ClientInterface, User, Scope extends AbstractScope>(bff: HonoBff, options: CreateAuthenticatedTestSessionOptions<Client, User, Scope>): Promise<string>;
112
+ /**
113
+ * Ages the access token in the underlying session so the next request
114
+ * through {@link HonoBff.attachToken} sees it as expired and triggers
115
+ * the refresh path.
116
+ *
117
+ * Returns a fresh `Cookie` header. For stateful stores
118
+ * (`MemorySessionStore` and DB-backed variants) the cookie value
119
+ * is unchanged. For stateless stores
120
+ * (`EncryptedCookieSessionStore`) the value is re-encrypted and
121
+ * differs from the input — use the returned header for subsequent
122
+ * requests.
123
+ *
124
+ * @param expiredAt Optional time to set the access token's expiry to
125
+ * (epoch ms). Defaults to one second in the past.
126
+ */
127
+ export declare function forceTokenExpiry(bff: HonoBff, cookieHeader: string, expiredAt?: number): Promise<string>;
128
+ /**
129
+ * Returns the session data currently stored under the given cookie
130
+ * header, or `null` if there is none. Convenience wrapper around
131
+ * `bff.sessionStore.read` that handles cookie parsing.
132
+ */
133
+ export declare function readTestSession(bff: HonoBff, cookieHeader: string): Promise<SessionData | null>;
134
+ /** Options for {@link runSessionStoreContractTests}. */
135
+ export interface SessionStoreContractOptions {
136
+ /** Returns a fresh, empty store for each test. */
137
+ makeStore(): Promise<SessionStore> | SessionStore;
138
+ /**
139
+ * Some stateless stores (`EncryptedCookieSessionStore`) cannot
140
+ * distinguish "session destroyed" from "session never existed" —
141
+ * destroy is a no-op on the server, with the cookie clear happening on
142
+ * the client side. Set this to `false` to skip the post-destroy
143
+ * `read` returns null assertion.
144
+ */
145
+ destroyClearsRead?: boolean;
146
+ /** Label for the generated `describe` block. Defaults to `"SessionStore contract"`. */
147
+ describeName?: string;
148
+ /**
149
+ * Returns a store bound to the given maximum session age, in milliseconds.
150
+ * Supply it whenever your store enforces an age bound — the suite builds a
151
+ * store with a one-second bound, waits it out, and requires `read` to stop
152
+ * returning the session. One second is whole-second-expressible, so a store
153
+ * whose TTL column or `EXPIRE` call has second resolution can honor it
154
+ * exactly.
155
+ *
156
+ * `HonoBff` trusts the `maxAgeMs` a store advertises when it refuses a
157
+ * configuration whose session cookie would expire before the store stops
158
+ * honoring the session. A store that advertises a bound it does not enforce
159
+ * defeats that check, so the suite fails a store that advertises one without
160
+ * supplying this factory.
161
+ */
162
+ makeBoundedStore?(maxAgeMs: number): Promise<SessionStore> | SessionStore;
163
+ }
164
+ /** Contract test suite for {@link SessionStore} implementations. */
165
+ export declare function runSessionStoreContractTests(options: SessionStoreContractOptions): void;