@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,72 @@
1
+ /**
2
+ * `__Host-` / `__Secure-` cookie-name resolution shared by the BFF's cookies,
3
+ * plus the attribute combinations a browser refuses outright.
4
+ *
5
+ * A browser honors those prefixes only when the cookie's own attributes satisfy
6
+ * them, and silently drops the cookie otherwise — as it does for
7
+ * `SameSite=None` without `Secure`. So the default name and the validation of
8
+ * an explicit name both have to be derived from the same attributes — kept here
9
+ * so the session cookie and the pending-authorization cookie cannot drift
10
+ * apart, and so a dropped-cookie configuration fails loudly at construction
11
+ * instead of as an inexplicably broken session.
12
+ *
13
+ * @module
14
+ */
15
+ /** Cookie attributes a browser validates a cookie's name and delivery against. */
16
+ export interface PrefixConstrainedAttributes {
17
+ /** Whether the cookie is marked `Secure`. */
18
+ secure: boolean;
19
+ /** The cookie's `Path`. */
20
+ path: string;
21
+ /** The cookie's `Domain`, or `undefined` when the cookie is host-bound. */
22
+ domain: string | undefined;
23
+ /** The cookie's `SameSite`, or `undefined` when the caller left it to the emitter. */
24
+ sameSite: string | undefined;
25
+ }
26
+ /** The subset of a cookie-options object the prefixes constrain. */
27
+ export interface PrefixConstrainedOptions {
28
+ /** Marks the cookie `Secure`. Defaults to `true`. */
29
+ secure?: boolean;
30
+ /** Cookie path. Defaults to `"/"`. */
31
+ path?: string;
32
+ /** Cookie domain. Omitted by default (host-bound). */
33
+ domain?: string;
34
+ /** Cookie `SameSite`. Left to the emitting cookie when unset. */
35
+ sameSite?: string;
36
+ }
37
+ /**
38
+ * Apply the attribute defaults every BFF cookie shares — `Secure`, `Path=/`,
39
+ * and no `Domain` — so name resolution and the emitted cookie agree on what
40
+ * the cookie will actually carry.
41
+ */
42
+ export declare function resolvePrefixConstrainedAttributes(opts: PrefixConstrainedOptions | undefined): PrefixConstrainedAttributes;
43
+ /** Inputs for {@link resolveCookieName}. */
44
+ export interface CookieNameResolution {
45
+ /** The explicit `cookie.name`, when the caller set one. */
46
+ name: string | undefined;
47
+ /**
48
+ * Unprefixed default name. `__Host-` is prepended to it when
49
+ * {@link attributes} satisfy that prefix.
50
+ */
51
+ base: string;
52
+ /** Attributes the emitted cookie carries. */
53
+ attributes: PrefixConstrainedAttributes;
54
+ /**
55
+ * What is lost when a browser drops the cookie, as a clause completing
56
+ * "Every browser rejects such a cookie, so …" in the thrown error.
57
+ */
58
+ consequence: string;
59
+ }
60
+ /**
61
+ * Resolve a cookie's name: the caller's explicit name when set, else
62
+ * `__Host-<base>` when the attributes satisfy that prefix, else the bare
63
+ * `base`.
64
+ *
65
+ * @throws {Error} When the attributes describe a cookie no browser will keep —
66
+ * an explicit name whose `__Host-` / `__Secure-` prefix they contradict, or
67
+ * `SameSite=None` without `Secure`. Either way the cookie is silently dropped
68
+ * and the failure surfaces as a broken flow rather than an error, so this
69
+ * refuses the configuration up front. `None` and the prefixes are matched
70
+ * case-insensitively, as browsers match them.
71
+ */
72
+ export declare function resolveCookieName(resolution: CookieNameResolution): string;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `__Host-` / `__Secure-` cookie-name resolution shared by the BFF's cookies,
3
+ * plus the attribute combinations a browser refuses outright.
4
+ *
5
+ * A browser honors those prefixes only when the cookie's own attributes satisfy
6
+ * them, and silently drops the cookie otherwise — as it does for
7
+ * `SameSite=None` without `Secure`. So the default name and the validation of
8
+ * an explicit name both have to be derived from the same attributes — kept here
9
+ * so the session cookie and the pending-authorization cookie cannot drift
10
+ * apart, and so a dropped-cookie configuration fails loudly at construction
11
+ * instead of as an inexplicably broken session.
12
+ *
13
+ * @module
14
+ */
15
+ /**
16
+ * Apply the attribute defaults every BFF cookie shares — `Secure`, `Path=/`,
17
+ * and no `Domain` — so name resolution and the emitted cookie agree on what
18
+ * the cookie will actually carry.
19
+ */
20
+ export function resolvePrefixConstrainedAttributes(opts) {
21
+ return {
22
+ secure: opts?.secure ?? true,
23
+ path: opts?.path ?? "/",
24
+ domain: opts?.domain || undefined,
25
+ sameSite: opts?.sameSite,
26
+ };
27
+ }
28
+ function assertSameSiteSatisfiable(attrs, consequence) {
29
+ if (attrs.sameSite?.toLowerCase() !== "none" || attrs.secure)
30
+ return;
31
+ throw new Error('cookie.sameSite is "None", which requires Secure, but cookie.secure is ' +
32
+ `false. Every browser rejects such a cookie, so ${consequence}. Leave ` +
33
+ 'cookie.secure on, or use "Lax" for local http development.');
34
+ }
35
+ function assertCookiePrefixSatisfiable(name, attrs, consequence) {
36
+ const lower = name.toLowerCase();
37
+ const quoted = JSON.stringify(name);
38
+ if (lower.startsWith("__host-")) {
39
+ const conflicts = [];
40
+ if (!attrs.secure)
41
+ conflicts.push("cookie.secure is false");
42
+ if (attrs.domain) {
43
+ conflicts.push(`cookie.domain is ${JSON.stringify(attrs.domain)}`);
44
+ }
45
+ if (attrs.path !== "/") {
46
+ conflicts.push(`cookie.path is ${JSON.stringify(attrs.path)}`);
47
+ }
48
+ if (conflicts.length > 0) {
49
+ throw new Error(`cookie.name ${quoted} uses the "__Host-" prefix, which requires ` +
50
+ `Secure, Path=/, and no Domain, but ${conflicts.join(" and ")}. ` +
51
+ `Every browser rejects such a cookie, so ${consequence}. Remove the ` +
52
+ "conflicting attribute or drop the prefix from cookie.name.");
53
+ }
54
+ return;
55
+ }
56
+ if (lower.startsWith("__secure-") && !attrs.secure) {
57
+ throw new Error(`cookie.name ${quoted} uses the "__Secure-" prefix, which requires ` +
58
+ `Secure, but cookie.secure is false. Every browser rejects such a ` +
59
+ `cookie, so ${consequence}. Leave cookie.secure on or drop the prefix ` +
60
+ "from cookie.name.");
61
+ }
62
+ }
63
+ /**
64
+ * Resolve a cookie's name: the caller's explicit name when set, else
65
+ * `__Host-<base>` when the attributes satisfy that prefix, else the bare
66
+ * `base`.
67
+ *
68
+ * @throws {Error} When the attributes describe a cookie no browser will keep —
69
+ * an explicit name whose `__Host-` / `__Secure-` prefix they contradict, or
70
+ * `SameSite=None` without `Secure`. Either way the cookie is silently dropped
71
+ * and the failure surfaces as a broken flow rather than an error, so this
72
+ * refuses the configuration up front. `None` and the prefixes are matched
73
+ * case-insensitively, as browsers match them.
74
+ */
75
+ export function resolveCookieName(resolution) {
76
+ const { name, base, attributes, consequence } = resolution;
77
+ assertSameSiteSatisfiable(attributes, consequence);
78
+ if (name) {
79
+ assertCookiePrefixSatisfiable(name, attributes, consequence);
80
+ return name;
81
+ }
82
+ const hostPrefixOk = attributes.secure && attributes.path === "/" &&
83
+ !attributes.domain;
84
+ return hostPrefixOk ? `__Host-${base}` : base;
85
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Backend-for-Frontend adapter for Hono.
3
+ *
4
+ * Exposes a {@link HonoBff} class that wraps an
5
+ * `DirectClient` constructed with a client secret, plus the
6
+ * {@link SessionStore} contract and two ready-to-use implementations
7
+ * ({@link MemorySessionStore}, {@link EncryptedCookieSessionStore}).
8
+ *
9
+ * Need a server-side store (Postgres, Redis, Deno KV, …)? Implement the
10
+ * {@link SessionStore} interface and verify it with
11
+ * `runSessionStoreContractTests` from `@udibo/oauth2/hono/bff/testing`.
12
+ *
13
+ * @module
14
+ */
15
+ import "../../../_dnt.polyfills.js";
16
+ export { type BackchannelLogoutSubject, type BffCallbackError, HonoBff, type HonoBffBackchannelOptions, type HonoBffCookieOptions, type HonoBffCsrfOptions, type HonoBffOptions, type HonoBffPaths, type HonoBffResourceServer, } from "./bff.js";
17
+ export { type AuthRequestCookieOptions, type AuthRequestStorageFactory, EncryptedCookieAuthRequestStorage, encryptedCookieAuthRequestStorage, type EncryptedCookieAuthRequestStorageOptions, } from "./auth-request-store.js";
18
+ export { DEFAULT_PROXY_FORWARD_HEADERS, type HonoBffProxyOptions, } from "./proxy.js";
19
+ export { type BackchannelLogoutStore, type BoundedSessionStore, DEFAULT_SESSION_MAX_AGE_MS, EncryptedCookieSessionStore, type EncryptedCookieSessionStoreOptions, MemorySessionStore, type SessionData, type SessionStore, sessionStoreMaxAgeMs, supportsBackchannelLogout, } from "./session-store.js";
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Backend-for-Frontend adapter for Hono.
3
+ *
4
+ * Exposes a {@link HonoBff} class that wraps an
5
+ * `DirectClient` constructed with a client secret, plus the
6
+ * {@link SessionStore} contract and two ready-to-use implementations
7
+ * ({@link MemorySessionStore}, {@link EncryptedCookieSessionStore}).
8
+ *
9
+ * Need a server-side store (Postgres, Redis, Deno KV, …)? Implement the
10
+ * {@link SessionStore} interface and verify it with
11
+ * `runSessionStoreContractTests` from `@udibo/oauth2/hono/bff/testing`.
12
+ *
13
+ * @module
14
+ */
15
+ import "../../../_dnt.polyfills.js";
16
+ export { HonoBff, } from "./bff.js";
17
+ export { EncryptedCookieAuthRequestStorage, encryptedCookieAuthRequestStorage, } from "./auth-request-store.js";
18
+ export { DEFAULT_PROXY_FORWARD_HEADERS, } from "./proxy.js";
19
+ export { DEFAULT_SESSION_MAX_AGE_MS, EncryptedCookieSessionStore, MemorySessionStore, sessionStoreMaxAgeMs, supportsBackchannelLogout, } from "./session-store.js";
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Header hygiene and URL resolution for {@link HonoBff.proxy}.
3
+ *
4
+ * The proxy is the "full BFF" shape from the IETF "OAuth 2.0 for Browser-Based
5
+ * Apps" BCP §6.1.1: the browser calls the BFF, the BFF forwards to a **separate**
6
+ * resource server with the session's access token attached, and the response is
7
+ * streamed back. This module owns the parts that are pure functions of the
8
+ * request — which headers cross the boundary in each direction, and which
9
+ * upstream URL an inbound path maps to.
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Options for {@link HonoBff.proxy}. The upstream base URL is a separate
15
+ * argument because it is fixed at configuration time: nothing about the target
16
+ * is ever derived from the request.
17
+ */
18
+ export interface HonoBffProxyOptions {
19
+ /**
20
+ * Mount prefix removed from the request path before it is appended to the
21
+ * upstream base. Mounting at `/api/*` against `https://api.example.com/v1`
22
+ * with `stripPrefix: "/api"` maps `GET /api/things?page=2` to
23
+ * `GET https://api.example.com/v1/things?page=2`. Omit to append the full
24
+ * request path.
25
+ */
26
+ stripPrefix?: string;
27
+ /**
28
+ * Request headers forwarded upstream, replacing
29
+ * {@link DEFAULT_PROXY_FORWARD_HEADERS}. Spread the default to extend it:
30
+ * `[...DEFAULT_PROXY_FORWARD_HEADERS, "x-tenant"]`. Matched
31
+ * case-insensitively. `Cookie`, `Authorization`, `Host`, the CSRF header, and
32
+ * the hop-by-hop headers are dropped even when listed here.
33
+ */
34
+ forwardHeaders?: readonly string[];
35
+ /**
36
+ * Retry once with a freshly refreshed access token when the upstream answers
37
+ * `401` with an `error="invalid_token"` challenge. Defaults to `true`.
38
+ * Requests that carry a body are never retried — the body has already been
39
+ * streamed upstream and buffering it would defeat the streaming — so those
40
+ * `401`s pass through as-is.
41
+ */
42
+ retryOn401?: boolean;
43
+ /**
44
+ * `fetch` used for the upstream call. Defaults to the global. Inject a stub
45
+ * in tests to assert what crosses the boundary without running a server.
46
+ */
47
+ fetch?: (input: URL, init: RequestInit) => Promise<Response>;
48
+ }
49
+ /**
50
+ * Request headers forwarded upstream unless {@link HonoBffProxyOptions.forwardHeaders}
51
+ * replaces the list. Content negotiation, conditional requests, ranges, and the
52
+ * common tracing headers — everything a resource server needs to answer
53
+ * correctly, and nothing that identifies the BFF's own session.
54
+ */
55
+ export declare const DEFAULT_PROXY_FORWARD_HEADERS: readonly string[];
56
+ /**
57
+ * Resolves the effective request-forward allowlist: the caller's list (or the
58
+ * default) minus everything that must never leave the BFF, including the CSRF
59
+ * header, which is a BFF-internal signal.
60
+ */
61
+ export declare function resolveForwardHeaders(forwardHeaders: readonly string[] | undefined, csrfHeaderName: string | undefined): Set<string>;
62
+ /**
63
+ * Builds the upstream request headers: the allowlisted inbound headers plus the
64
+ * session's bearer token. The session cookie, any inbound `Authorization`, and
65
+ * every hop-by-hop header are left behind by construction — this starts from an
66
+ * empty `Headers` and only copies what `allowed` names.
67
+ */
68
+ export declare function buildUpstreamHeaders(inbound: Headers, allowed: Set<string>, accessToken: string): Headers;
69
+ /**
70
+ * Builds the browser-facing response headers: everything except hop-by-hop
71
+ * headers (the fixed set and whatever `Connection` names), upstream
72
+ * `Set-Cookie`, and — when a body is actually streamed back — the content
73
+ * coding and length that `fetch` already resolved.
74
+ *
75
+ * URL-valued headers are rewritten into the mount's namespace, and the result
76
+ * is marked `private` + `Vary: Cookie` (it is cookie-authenticated) and
77
+ * `nosniff` (an upstream body is served from the BFF's own origin).
78
+ */
79
+ export declare function buildDownstreamHeaders(upstream: Response, context: {
80
+ upstreamUrl: URL;
81
+ base: URL;
82
+ mountPrefix: string;
83
+ }): Headers;
84
+ /**
85
+ * Maps an inbound request URL onto the configured upstream base.
86
+ *
87
+ * The base is fixed at configuration time and only the path and query travel
88
+ * with the request, so the target can never be steered by the caller. `URL`
89
+ * resolves dot segments (percent-encoded ones included) while parsing, so what
90
+ * remains to defend against is a segment that only becomes `../` once an
91
+ * upstream server decodes it again — see {@link assertSafeSegment}.
92
+ *
93
+ * Query strings are merged rather than replaced: a parameter pinned on the
94
+ * target survives every request and **wins** over an inbound parameter of the
95
+ * same name, so a browser cannot override what the mount configured.
96
+ *
97
+ * @param stripPrefix Mount prefix removed from the path before joining.
98
+ * @throws {TypeError} If a path segment has a malformed escape or hides `..`.
99
+ */
100
+ export declare function resolveProxyUrl(base: URL, requestUrl: string, stripPrefix?: string): URL;
101
+ /**
102
+ * Whether an upstream `401` is the kind a token refresh could fix. Mirrors the
103
+ * `WWW-Authenticate: ... error="invalid_token"` gate `DirectClient.fetch`
104
+ * applies (quotes optional here, since not every server emits them), so a `401`
105
+ * meaning "wrong user" or "no such thing" passes straight through instead of
106
+ * burning a refresh.
107
+ */
108
+ export declare function isInvalidTokenChallenge(response: Response): boolean;
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Header hygiene and URL resolution for {@link HonoBff.proxy}.
3
+ *
4
+ * The proxy is the "full BFF" shape from the IETF "OAuth 2.0 for Browser-Based
5
+ * Apps" BCP §6.1.1: the browser calls the BFF, the BFF forwards to a **separate**
6
+ * resource server with the session's access token attached, and the response is
7
+ * streamed back. This module owns the parts that are pure functions of the
8
+ * request — which headers cross the boundary in each direction, and which
9
+ * upstream URL an inbound path maps to.
10
+ *
11
+ * @module
12
+ */
13
+ /**
14
+ * Hop-by-hop headers (RFC 9110 §7.6.1). They describe a single transport
15
+ * connection, so a proxy must not pass them through in either direction.
16
+ */
17
+ const HOP_BY_HOP_HEADERS = [
18
+ "connection",
19
+ "keep-alive",
20
+ "proxy-authenticate",
21
+ "proxy-authorization",
22
+ "te",
23
+ "trailer",
24
+ "transfer-encoding",
25
+ "upgrade",
26
+ ];
27
+ /**
28
+ * Request headers forwarded upstream unless {@link HonoBffProxyOptions.forwardHeaders}
29
+ * replaces the list. Content negotiation, conditional requests, ranges, and the
30
+ * common tracing headers — everything a resource server needs to answer
31
+ * correctly, and nothing that identifies the BFF's own session.
32
+ */
33
+ export const DEFAULT_PROXY_FORWARD_HEADERS = [
34
+ "accept",
35
+ "accept-language",
36
+ "content-language",
37
+ "content-type",
38
+ "if-match",
39
+ "if-modified-since",
40
+ "if-none-match",
41
+ "if-unmodified-since",
42
+ "range",
43
+ "user-agent",
44
+ "baggage",
45
+ "traceparent",
46
+ "tracestate",
47
+ "x-request-id",
48
+ ];
49
+ /**
50
+ * Never forwarded upstream, even when named in
51
+ * {@link HonoBffProxyOptions.forwardHeaders}. `cookie` and `authorization` are
52
+ * the security-critical entries: the browser's session cookie is the BFF's
53
+ * credential (not the API's), and an inbound `Authorization` must never be able
54
+ * to override the token the BFF attaches. `accept-encoding` and `content-length`
55
+ * are dropped because `fetch` owns content coding and body framing.
56
+ */
57
+ const NEVER_FORWARDED_REQUEST_HEADERS = [
58
+ ...HOP_BY_HOP_HEADERS,
59
+ "accept-encoding",
60
+ "authorization",
61
+ "content-length",
62
+ "cookie",
63
+ "cookie2",
64
+ "host",
65
+ ];
66
+ /**
67
+ * Never returned to the browser. `set-cookie` is dropped so an upstream API
68
+ * cannot plant cookies on the BFF's origin, where they would ride along with
69
+ * the session cookie.
70
+ */
71
+ const NEVER_RETURNED_RESPONSE_HEADERS = [
72
+ ...HOP_BY_HOP_HEADERS,
73
+ "set-cookie",
74
+ ];
75
+ /**
76
+ * Dropped only when a body is actually streamed back: `fetch` has already
77
+ * decoded it, so the upstream's content coding and length no longer describe
78
+ * what the browser receives. A body-less response (`HEAD`, `204`, `304`) was
79
+ * never decoded, and there `Content-Length` *is* the answer.
80
+ */
81
+ const DECODED_BODY_RESPONSE_HEADERS = [
82
+ "content-encoding",
83
+ "content-length",
84
+ ];
85
+ /** Case-insensitive header name set. */
86
+ function headerSet(names) {
87
+ return new Set(names.map((name) => name.toLowerCase()));
88
+ }
89
+ /**
90
+ * Resolves the effective request-forward allowlist: the caller's list (or the
91
+ * default) minus everything that must never leave the BFF, including the CSRF
92
+ * header, which is a BFF-internal signal.
93
+ */
94
+ export function resolveForwardHeaders(forwardHeaders, csrfHeaderName) {
95
+ const allowed = headerSet(forwardHeaders ?? DEFAULT_PROXY_FORWARD_HEADERS);
96
+ const denied = headerSet(NEVER_FORWARDED_REQUEST_HEADERS);
97
+ if (csrfHeaderName)
98
+ denied.add(csrfHeaderName.toLowerCase());
99
+ for (const name of denied)
100
+ allowed.delete(name);
101
+ return allowed;
102
+ }
103
+ /**
104
+ * Builds the upstream request headers: the allowlisted inbound headers plus the
105
+ * session's bearer token. The session cookie, any inbound `Authorization`, and
106
+ * every hop-by-hop header are left behind by construction — this starts from an
107
+ * empty `Headers` and only copies what `allowed` names.
108
+ */
109
+ export function buildUpstreamHeaders(inbound, allowed, accessToken) {
110
+ const headers = new Headers();
111
+ for (const [name, value] of inbound) {
112
+ if (allowed.has(name.toLowerCase()))
113
+ headers.set(name, value);
114
+ }
115
+ headers.set("Authorization", `Bearer ${accessToken}`);
116
+ return headers;
117
+ }
118
+ /**
119
+ * The header names an upstream `Connection` field lists. RFC 9110 §7.6.1 makes
120
+ * those connection-specific too, so a proxy must drop them alongside the
121
+ * well-known hop-by-hop set.
122
+ */
123
+ function connectionNamedHeaders(headers) {
124
+ return (headers.get("connection") ?? "")
125
+ .split(",")
126
+ .map((name) => name.trim().toLowerCase())
127
+ .filter((name) => name.length > 0);
128
+ }
129
+ /** Headers whose value is a URL into the upstream's namespace. */
130
+ const URL_VALUED_RESPONSE_HEADERS = [
131
+ "location",
132
+ "content-location",
133
+ ];
134
+ /**
135
+ * Rewrites an upstream URL into the mount's namespace, so a `Location` naming
136
+ * the resource server neither leaks internal topology to the browser nor points
137
+ * it at a host it cannot reach. A relative value resolves against the upstream
138
+ * request first. Anything outside the configured base is left alone — it is a
139
+ * genuinely external destination, not part of what this mount proxies.
140
+ */
141
+ function rewriteUpstreamUrl(value, upstreamUrl, base, mountPrefix) {
142
+ let resolved;
143
+ try {
144
+ resolved = new URL(value, upstreamUrl);
145
+ }
146
+ catch {
147
+ return value;
148
+ }
149
+ if (resolved.origin !== base.origin)
150
+ return value;
151
+ const basePath = base.pathname.endsWith("/")
152
+ ? base.pathname.slice(0, -1)
153
+ : base.pathname;
154
+ const underBase = basePath === "" ||
155
+ resolved.pathname === basePath ||
156
+ resolved.pathname.startsWith(`${basePath}/`);
157
+ if (!underBase)
158
+ return value;
159
+ const suffix = resolved.pathname.slice(basePath.length) || "/";
160
+ return `${mountPrefix}${suffix}${resolved.search}${resolved.hash}`;
161
+ }
162
+ /** Adds `name` to `Vary` without disturbing what the upstream already varies on. */
163
+ function appendVary(headers, name) {
164
+ const existing = headers.get("vary");
165
+ if (!existing) {
166
+ headers.set("vary", name);
167
+ return;
168
+ }
169
+ if (existing.trim() === "*")
170
+ return;
171
+ const present = existing
172
+ .split(",")
173
+ .some((token) => token.trim().toLowerCase() === name.toLowerCase());
174
+ if (!present)
175
+ headers.set("vary", `${existing}, ${name}`);
176
+ }
177
+ /**
178
+ * Forces the response private. Upstream sees an `Authorization`-protected
179
+ * request, which RFC 9111 §3.5 already keeps out of shared caches; downstream
180
+ * the same response is authenticated by a cookie, which carries no such rule.
181
+ */
182
+ function forcePrivateCache(headers) {
183
+ const existing = headers.get("cache-control");
184
+ if (!existing) {
185
+ headers.set("cache-control", "private");
186
+ return;
187
+ }
188
+ const directives = existing
189
+ .split(",")
190
+ .map((directive) => directive.trim().toLowerCase());
191
+ if (directives.some((directive) => directive === "private" || directive === "no-store" ||
192
+ directive.startsWith("private="))) {
193
+ return;
194
+ }
195
+ headers.set("cache-control", `private, ${existing}`);
196
+ }
197
+ /**
198
+ * Builds the browser-facing response headers: everything except hop-by-hop
199
+ * headers (the fixed set and whatever `Connection` names), upstream
200
+ * `Set-Cookie`, and — when a body is actually streamed back — the content
201
+ * coding and length that `fetch` already resolved.
202
+ *
203
+ * URL-valued headers are rewritten into the mount's namespace, and the result
204
+ * is marked `private` + `Vary: Cookie` (it is cookie-authenticated) and
205
+ * `nosniff` (an upstream body is served from the BFF's own origin).
206
+ */
207
+ export function buildDownstreamHeaders(upstream, context) {
208
+ const denied = headerSet(NEVER_RETURNED_RESPONSE_HEADERS);
209
+ for (const name of connectionNamedHeaders(upstream.headers)) {
210
+ denied.add(name);
211
+ }
212
+ if (upstream.body !== null) {
213
+ for (const name of DECODED_BODY_RESPONSE_HEADERS)
214
+ denied.add(name);
215
+ }
216
+ const urlValued = headerSet(URL_VALUED_RESPONSE_HEADERS);
217
+ const headers = new Headers();
218
+ for (const [name, value] of upstream.headers) {
219
+ if (denied.has(name.toLowerCase()))
220
+ continue;
221
+ headers.append(name, urlValued.has(name.toLowerCase())
222
+ ? rewriteUpstreamUrl(value, context.upstreamUrl, context.base, context.mountPrefix)
223
+ : value);
224
+ }
225
+ appendVary(headers, "Cookie");
226
+ forcePrivateCache(headers);
227
+ headers.set("X-Content-Type-Options", "nosniff");
228
+ return headers;
229
+ }
230
+ /**
231
+ * Rejects a path segment that would become a dot segment once decoded.
232
+ *
233
+ * Fails closed on a malformed escape: `decodeURIComponent` throws on overlong
234
+ * or invalid UTF-8 (`%C0%AF`, the classic `/` smuggle), and a lenient upstream
235
+ * decoder may still reconstruct the separator, so the segment cannot be
236
+ * forwarded unexamined. Encoded separators themselves are allowed through —
237
+ * `a%2Fb` is a legitimate resource id — it is only `..` between them that is
238
+ * refused.
239
+ *
240
+ * @throws {TypeError} If the segment has a malformed escape or hides `..`.
241
+ */
242
+ function assertSafeSegment(segment) {
243
+ let decoded;
244
+ try {
245
+ decoded = decodeURIComponent(segment);
246
+ }
247
+ catch {
248
+ throw new TypeError("proxy path segment has a malformed escape sequence");
249
+ }
250
+ if (decoded.split(/[/\\]/).some((part) => part === "..")) {
251
+ throw new TypeError("proxy path segment hides a dot segment");
252
+ }
253
+ }
254
+ /**
255
+ * Maps an inbound request URL onto the configured upstream base.
256
+ *
257
+ * The base is fixed at configuration time and only the path and query travel
258
+ * with the request, so the target can never be steered by the caller. `URL`
259
+ * resolves dot segments (percent-encoded ones included) while parsing, so what
260
+ * remains to defend against is a segment that only becomes `../` once an
261
+ * upstream server decodes it again — see {@link assertSafeSegment}.
262
+ *
263
+ * Query strings are merged rather than replaced: a parameter pinned on the
264
+ * target survives every request and **wins** over an inbound parameter of the
265
+ * same name, so a browser cannot override what the mount configured.
266
+ *
267
+ * @param stripPrefix Mount prefix removed from the path before joining.
268
+ * @throws {TypeError} If a path segment has a malformed escape or hides `..`.
269
+ */
270
+ export function resolveProxyUrl(base, requestUrl, stripPrefix) {
271
+ const inbound = new URL(requestUrl);
272
+ let path = inbound.pathname;
273
+ if (stripPrefix) {
274
+ const prefix = stripPrefix.endsWith("/")
275
+ ? stripPrefix.slice(0, -1)
276
+ : stripPrefix;
277
+ if (path === prefix) {
278
+ path = "/";
279
+ }
280
+ else if (path.startsWith(`${prefix}/`)) {
281
+ path = path.slice(prefix.length);
282
+ }
283
+ }
284
+ for (const segment of path.split("/"))
285
+ assertSafeSegment(segment);
286
+ const basePath = base.pathname.endsWith("/")
287
+ ? base.pathname.slice(0, -1)
288
+ : base.pathname;
289
+ const suffix = path.startsWith("/") ? path : `/${path}`;
290
+ const url = new URL(base);
291
+ url.pathname = `${basePath}${suffix}`;
292
+ url.search = inbound.search;
293
+ for (const name of new Set(base.searchParams.keys())) {
294
+ url.searchParams.delete(name);
295
+ }
296
+ for (const [name, value] of base.searchParams) {
297
+ url.searchParams.append(name, value);
298
+ }
299
+ url.hash = "";
300
+ return url;
301
+ }
302
+ /**
303
+ * Whether an upstream `401` is the kind a token refresh could fix. Mirrors the
304
+ * `WWW-Authenticate: ... error="invalid_token"` gate `DirectClient.fetch`
305
+ * applies (quotes optional here, since not every server emits them), so a `401`
306
+ * meaning "wrong user" or "no such thing" passes straight through instead of
307
+ * burning a refresh.
308
+ */
309
+ export function isInvalidTokenChallenge(response) {
310
+ if (response.status !== 401)
311
+ return false;
312
+ const challenge = response.headers.get("WWW-Authenticate") ?? "";
313
+ return /error\s*=\s*"?invalid_token"?/i.test(challenge);
314
+ }