acrylblends 0.6.3 → 0.7.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 (285) hide show
  1. package/README.md +4 -3
  2. package/docs/framework/README.md +2 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/guides/jsx.md +51 -1
  5. package/docs/framework/helpers/ssg.md +3 -6
  6. package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
  7. package/docs/framework/upstream.json +2 -2
  8. package/lib/accepts.d.ts +1 -1
  9. package/lib/acryl-base.d.ts +1 -1
  10. package/lib/adapter.d.ts +1 -1
  11. package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
  12. package/lib/{base-ddiVmiE7.js → base-NSN_SNv-.js} +1 -0
  13. package/lib/basic-auth.d.ts +1 -1
  14. package/lib/bearer-auth.d.ts +1 -1
  15. package/lib/body-limit.d.ts +1 -1
  16. package/lib/cache.d.ts +1 -1
  17. package/lib/cache.js +4 -4
  18. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  19. package/lib/client.d.ts +2 -2
  20. package/lib/client.js +2 -1
  21. package/lib/combine.d.ts +1 -1
  22. package/lib/{components-DYZdxJIJ.js → components-JXVm45kK.js} +1 -1
  23. package/lib/{components-Ce5FRyHU.js → components-cEW3ovK0.js} +3 -3
  24. package/lib/compress.d.ts +1 -1
  25. package/lib/conninfo.d.ts +2 -2
  26. package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
  27. package/lib/context-storage.d.ts +1 -1
  28. package/lib/cookie.d.ts +1 -1
  29. package/lib/cors.d.ts +1 -1
  30. package/lib/csrf.d.ts +1 -1
  31. package/lib/dev.d.ts +2 -2
  32. package/lib/etag.d.ts +1 -1
  33. package/lib/factory.d.ts +2 -2
  34. package/lib/{hooks-Bq5q9IMl.js → hooks-w72iLeFR.js} +6 -6
  35. package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
  36. package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
  37. package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
  38. package/lib/index.d.ts +4 -4
  39. package/lib/ip-restriction.d.ts +2 -2
  40. package/lib/jsx/dom/client.js +1 -1
  41. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  42. package/lib/jsx/dom/jsx-runtime.js +1 -1
  43. package/lib/jsx/dom/server.js +2 -2
  44. package/lib/jsx/dom.js +4 -4
  45. package/lib/jsx/jsx-dev-runtime.js +1 -1
  46. package/lib/jsx/jsx-runtime.js +1 -1
  47. package/lib/jsx/streaming.js +1 -1
  48. package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-CWLRA2F3.js} +2 -2
  49. package/lib/jsx-renderer.d.ts +75 -0
  50. package/lib/jsx-renderer.js +99 -0
  51. package/lib/jsx.js +4 -4
  52. package/lib/jwk.d.ts +1 -1
  53. package/lib/jwt.d.ts +1 -1
  54. package/lib/language.d.ts +1 -1
  55. package/lib/logger.d.ts +1 -1
  56. package/lib/loopback-only.d.ts +1 -1
  57. package/lib/method-not-allowed.d.ts +2 -2
  58. package/lib/method-override.d.ts +2 -2
  59. package/lib/mount.d.ts +1 -1
  60. package/lib/node-server/conninfo.d.ts +1 -1
  61. package/lib/node-server/register.d.ts +1 -0
  62. package/lib/node-server/register.js +73 -0
  63. package/lib/node-server/serve-static.d.ts +1 -1
  64. package/lib/node-server/serve-static.js +1 -1
  65. package/lib/node-server.d.ts +1 -1
  66. package/lib/node-server.js +1 -1
  67. package/lib/powered-by.d.ts +1 -1
  68. package/lib/pretty-json.d.ts +1 -1
  69. package/lib/quick.d.ts +1 -1
  70. package/lib/request-id.d.ts +1 -1
  71. package/lib/request.d.ts +1 -1
  72. package/lib/route.d.ts +1 -1
  73. package/lib/schema-validator.d.ts +1 -1
  74. package/lib/secure-headers.d.ts +1 -1
  75. package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
  76. package/lib/serve-static.d.ts +1 -1
  77. package/lib/ssg-BwjcA-Ge.js +389 -0
  78. package/lib/ssg.d.ts +156 -0
  79. package/lib/ssg.js +2 -0
  80. package/lib/streaming.d.ts +1 -1
  81. package/lib/testing.d.ts +3 -3
  82. package/lib/testing.js +1 -1
  83. package/lib/timeout.d.ts +1 -1
  84. package/lib/timing.d.ts +1 -1
  85. package/lib/tiny.d.ts +1 -1
  86. package/lib/trailing-slash.d.ts +1 -1
  87. package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  88. package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
  89. package/lib/types.d.ts +1 -1
  90. package/lib/utils/body.d.ts +1 -1
  91. package/lib/utils/ipaddr.d.ts +1 -1
  92. package/lib/utils-Cx61GFMC.js +125 -0
  93. package/lib/validator.d.ts +1 -1
  94. package/lib/ws.d.ts +1 -1
  95. package/package.json +13 -1
  96. package/plugins/acryl-inspect/upstream.json +1 -1
  97. package/plugins/acryl-node-server/src/register.ts +86 -0
  98. package/plugins/acryl-node-server/upstream.json +5 -3
  99. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  100. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  101. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  102. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  103. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  104. package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
  105. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  106. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  107. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  108. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  109. package/plugins/acryl-router/upstream.json +2 -2
  110. package/plugins/acryl-router-http/upstream.json +1 -1
  111. package/plugins/acryl-state/upstream.json +1 -1
  112. package/plugins/desktop/main.ts +4 -1
  113. package/plugins/desktop/upstream.json +1 -1
  114. package/plugins/desktop-web/main.ts +3 -1
  115. package/plugins/desktop-web/upstream.json +1 -1
  116. package/plugins/extensions/resolve.ts +4 -0
  117. package/shared/AGENTS.core.md +3 -0
  118. package/templates/agent/package.json +7 -3
  119. package/templates/agent/tsconfig.json +10 -2
  120. package/templates/blank/package.json +7 -3
  121. package/templates/blank/src/about.tsx +14 -0
  122. package/templates/blank/src/routes.ts +2 -0
  123. package/templates/blank/test/app.test.ts +15 -0
  124. package/templates/blank/tsconfig.json +3 -1
  125. package/templates/todo/package.json +7 -3
  126. package/templates/todo/tsconfig.json +3 -1
  127. package/templates/todo/upstream.json +1 -1
  128. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  129. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  130. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  131. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  132. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  133. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  134. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  135. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  136. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  137. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  138. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  139. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  140. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  141. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  142. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  143. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  144. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  145. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  146. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  147. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  148. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  149. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  150. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  151. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  152. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  153. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  154. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  155. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  156. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  157. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  158. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  159. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  160. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  161. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  162. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  163. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  164. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  165. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  166. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  167. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  168. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  169. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  170. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  171. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  172. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  173. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  174. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  175. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  176. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  177. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
  178. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
  179. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  180. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  181. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  182. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  183. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  184. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
  185. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  186. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  187. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  188. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  189. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  190. package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
  191. package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
  192. package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
  193. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
  194. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
  195. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
  196. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
  197. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
  198. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
  199. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
  200. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
  201. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
  202. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
  203. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
  204. package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
  205. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
  206. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
  207. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
  208. package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
  209. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  210. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  211. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  212. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  213. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  214. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  215. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  216. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  217. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  218. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  219. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  220. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  221. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  222. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  223. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  224. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  225. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  226. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  227. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  228. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  229. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  230. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  231. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  232. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  233. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  234. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  235. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  236. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  237. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  238. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  239. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  240. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  241. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  242. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  243. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  244. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  245. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  246. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  247. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  248. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  249. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  250. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  251. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  252. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  253. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  254. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  255. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  256. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  257. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  258. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  259. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  260. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  261. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  262. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  263. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  264. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  265. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  266. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  267. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  268. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  269. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  270. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  271. package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
  272. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  273. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  274. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  275. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  276. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  277. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  278. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  279. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  280. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  281. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  282. package/plugins/acryl-state/src/react.ts +0 -11
  283. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  284. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  285. package/plugins/acryl-state/tests/service.spec.ts +0 -132
@@ -1,1135 +0,0 @@
1
- import { HttpResponse, http } from 'msw'
2
- import { setupServer } from 'msw/node'
3
- import { jwk } from './index.ts'
4
- import { setSignedCookie } from '../../helper/cookie/index.ts'
5
- import { Acryl } from '../../app.ts'
6
- import { HTTPException } from '../../http-exception.ts'
7
- import { encodeBase64Url } from '../../utils/encode.ts'
8
- import { Jwt } from '../../utils/jwt/index.ts'
9
- import type { AcrylJsonWebKey } from '../../utils/jwt/jws.ts'
10
- import { signing } from '../../utils/jwt/jws.ts'
11
- import { verifyWithJwks } from '../../utils/jwt/jwt.ts'
12
- import type { JWTPayload } from '../../utils/jwt/types.ts'
13
- import { utf8Encoder } from '../../utils/jwt/utf8.ts'
14
- import test_keys from './keys.test.json' with { type: 'json' }
15
-
16
- const verify_keys = test_keys.public_keys
17
-
18
- describe('JWK', () => {
19
- const server = setupServer(
20
- http.get('http://localhost/.well-known/jwks.json', () => {
21
- return HttpResponse.json({ keys: verify_keys })
22
- }),
23
- http.get('http://localhost/.well-known/missing-jwks.json', () => {
24
- return HttpResponse.json({})
25
- }),
26
- http.get('http://localhost/.well-known/bad-jwks.json', () => {
27
- return HttpResponse.json({ keys: 'bad-keys' })
28
- }),
29
- http.get('http://localhost/.well-known/404-jwks.json', () => {
30
- return HttpResponse.text('Not Found', { status: 404 })
31
- })
32
- )
33
- beforeAll(() => server.listen())
34
- afterEach(() => server.resetHandlers())
35
- afterAll(() => server.close())
36
-
37
- describe('verifyWithJwks', () => {
38
- it('Should throw error on missing keys/jwks_uri options', async () => {
39
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
40
- await expect(verifyWithJwks(credential, { allowedAlgorithms: ['RS256'] })).rejects.toThrow(
41
- 'verifyWithJwks requires options for either "keys" or "jwks_uri" or both'
42
- )
43
- })
44
- })
45
-
46
- describe('options.allow_anon = true', () => {
47
- let handlerExecuted: boolean
48
-
49
- beforeEach(() => {
50
- handlerExecuted = false
51
- })
52
-
53
- const app = new Acryl()
54
-
55
- app.use('/backend-auth-or-anon/*', jwk({ keys: verify_keys, allow_anon: true, alg: ['RS256'] }))
56
-
57
- app.get('/backend-auth-or-anon/*', (c) => {
58
- handlerExecuted = true
59
- const payload = c.get('jwtPayload')
60
- return c.json(payload ?? { message: 'hello anon' })
61
- })
62
-
63
- it('Should skip JWK if no token is present', async () => {
64
- const req = new Request('http://localhost/backend-auth-or-anon/a')
65
- const res = await app.request(req)
66
- expect(res).not.toBeNull()
67
- expect(res.status).toBe(200)
68
- expect(await res.json()).toEqual({ message: 'hello anon' })
69
- expect(handlerExecuted).toBeTruthy()
70
- })
71
-
72
- it('Should authorize if token is present', async () => {
73
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
74
- const req = new Request('http://localhost/backend-auth-or-anon/a')
75
- req.headers.set('Authorization', `Bearer ${credential}`)
76
- const res = await app.request(req)
77
- expect(res).not.toBeNull()
78
- expect(res.status).toBe(200)
79
- expect(await res.json()).toEqual({ message: 'hello world' })
80
- expect(handlerExecuted).toBeTruthy()
81
- })
82
-
83
- it('Should not authorize if a non-Bearer scheme is present', async () => {
84
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
85
- const url = 'http://localhost/backend-auth-or-anon/a'
86
- const req = new Request(url)
87
- req.headers.set('Authorization', `Basic ${credential}`)
88
- const res = await app.request(req)
89
- expect(res).not.toBeNull()
90
- expect(res.status).toBe(401)
91
- expect(res.headers.get('www-authenticate')).toEqual(
92
- `Bearer realm="${url}",error="invalid_request",error_description="invalid credentials structure"`
93
- )
94
- expect(handlerExecuted).toBeFalsy()
95
- })
96
-
97
- it('Should not authorize if an invalid Bearer token is present', async () => {
98
- const invalidToken =
99
- 'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
100
- const url = 'http://localhost/backend-auth-or-anon/a'
101
- const req = new Request(url)
102
- req.headers.set('Authorization', `Bearer ${invalidToken}`)
103
- const res = await app.request(req)
104
- expect(res).not.toBeNull()
105
- expect(res.status).toBe(401)
106
- expect(res.headers.get('www-authenticate')).toEqual(
107
- `Bearer realm="${url}",error="invalid_token",error_description="token verification failure"`
108
- )
109
- expect(handlerExecuted).toBeFalsy()
110
- })
111
- })
112
-
113
- describe('Credentials in header', () => {
114
- let handlerExecuted: boolean
115
-
116
- beforeEach(() => {
117
- handlerExecuted = false
118
- })
119
-
120
- const app = new Acryl()
121
-
122
- app.use('/auth-with-keys/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
123
- app.use('/auth-with-keys-unicode/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
124
- app.use('/auth-with-keys-nested/*', async (c, next) => {
125
- const auth = jwk({ keys: verify_keys, alg: ['RS256'] })
126
- return auth(c, next)
127
- })
128
- app.use(
129
- '/auth-with-keys-fn/*',
130
- jwk({
131
- keys: async () => {
132
- const response = await fetch('http://localhost/.well-known/jwks.json')
133
- const data = await response.json()
134
- return data.keys
135
- },
136
- alg: ['RS256'],
137
- })
138
- )
139
- app.use(
140
- '/auth-with-jwks_uri/*',
141
- jwk({
142
- jwks_uri: 'http://localhost/.well-known/jwks.json',
143
- alg: ['RS256'],
144
- })
145
- )
146
- app.use(
147
- '/auth-with-keys-and-jwks_uri/*',
148
- jwk({
149
- keys: verify_keys,
150
- jwks_uri: () => 'http://localhost/.well-known/jwks.json',
151
- alg: ['RS256'],
152
- })
153
- )
154
- app.use(
155
- '/auth-with-missing-jwks_uri/*',
156
- jwk({
157
- jwks_uri: 'http://localhost/.well-known/missing-jwks.json',
158
- alg: ['RS256'],
159
- })
160
- )
161
- app.use(
162
- '/auth-with-404-jwks_uri/*',
163
- jwk({
164
- jwks_uri: 'http://localhost/.well-known/404-jwks.json',
165
- alg: ['RS256'],
166
- })
167
- )
168
- app.use(
169
- '/auth-with-bad-jwks_uri/*',
170
- jwk({
171
- jwks_uri: 'http://localhost/.well-known/bad-jwks.json',
172
- alg: ['RS256'],
173
- })
174
- )
175
-
176
- app.get('/auth-with-keys/*', (c) => {
177
- handlerExecuted = true
178
- const payload = c.get('jwtPayload')
179
- return c.json(payload)
180
- })
181
- app.get('/auth-with-keys-unicode/*', (c) => {
182
- handlerExecuted = true
183
- const payload = c.get('jwtPayload')
184
- return c.json(payload)
185
- })
186
- app.get('/auth-with-keys-nested/*', (c) => {
187
- handlerExecuted = true
188
- const payload = c.get('jwtPayload')
189
- return c.json(payload)
190
- })
191
- app.get('/auth-with-keys-fn/*', (c) => {
192
- handlerExecuted = true
193
- const payload = c.get('jwtPayload')
194
- return c.json(payload)
195
- })
196
- app.get('/auth-with-jwks_uri/*', (c) => {
197
- handlerExecuted = true
198
- const payload = c.get('jwtPayload')
199
- return c.json(payload)
200
- })
201
- app.get('/auth-with-keys-and-jwks_uri/*', (c) => {
202
- handlerExecuted = true
203
- const payload = c.get('jwtPayload')
204
- return c.json(payload)
205
- })
206
- app.get('/auth-with-missing-jwks_uri/*', (c) => {
207
- handlerExecuted = true
208
- const payload = c.get('jwtPayload')
209
- return c.json(payload)
210
- })
211
- app.get('/auth-with-404-jwks_uri/*', (c) => {
212
- handlerExecuted = true
213
- const payload = c.get('jwtPayload')
214
- return c.json(payload)
215
- })
216
- app.get('/auth-with-bad-jwks_uri/*', (c) => {
217
- handlerExecuted = true
218
- const payload = c.get('jwtPayload')
219
- return c.json(payload)
220
- })
221
-
222
- it('Should throw an error if the middleware is missing both keys and jwks_uri (empty)', async () => {
223
- // @ts-expect-error - Testing runtime error with missing required alg option
224
- expect(() => app.use('/auth-with-empty-middleware/*', jwk({}))).toThrow(
225
- 'JWK auth middleware requires options for either "keys" or "jwks_uri"'
226
- )
227
- })
228
-
229
- it('Should throw an error when crypto.subtle is missing', async () => {
230
- const subtleSpy = vi.spyOn(global.crypto, 'subtle', 'get').mockReturnValue({
231
- importKey: undefined,
232
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
- } as any)
234
- expect(() =>
235
- app.use('/auth-with-bad-env/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
236
- ).toThrow()
237
- subtleSpy.mockRestore()
238
- })
239
-
240
- it('Should return a server error if options.jwks_uri returns a 404', async () => {
241
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
242
- const req = new Request('http://localhost/auth-with-404-jwks_uri/a')
243
- req.headers.set('Authorization', `Bearer ${credential}`)
244
- const res = await app.request(req)
245
- expect(res).not.toBeNull()
246
- expect(res.status).toBe(500)
247
- })
248
-
249
- it('Should return a server error if the remotely fetched keys from options.jwks_uri are missing', async () => {
250
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
251
- const req = new Request('http://localhost/auth-with-missing-jwks_uri/a')
252
- req.headers.set('Authorization', `Bearer ${credential}`)
253
- const res = await app.request(req)
254
- expect(res).not.toBeNull()
255
- expect(res.status).toBe(500)
256
- })
257
-
258
- it('Should return a server error if the remotely fetched keys from options.jwks_uri are malformed', async () => {
259
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
260
- const req = new Request('http://localhost/auth-with-bad-jwks_uri/a')
261
- req.headers.set('Authorization', `Bearer ${credential}`)
262
- const res = await app.request(req)
263
- expect(res).not.toBeNull()
264
- expect(res.status).toBe(500)
265
- })
266
-
267
- it('Should not authorize requests with missing access token', async () => {
268
- const req = new Request('http://localhost/auth-with-keys/a')
269
- const res = await app.request(req)
270
- expect(res).not.toBeNull()
271
- expect(res.status).toBe(401)
272
- expect(await res.text()).toBe('Unauthorized')
273
- expect(handlerExecuted).toBeFalsy()
274
- })
275
-
276
- it('Should authorize from a static array passed to options.keys (key 1)', async () => {
277
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
278
- const req = new Request('http://localhost/auth-with-keys/a')
279
- req.headers.set('Authorization', `Bearer ${credential}`)
280
- const res = await app.request(req)
281
- expect(res).not.toBeNull()
282
- expect(res.status).toBe(200)
283
- expect(await res.json()).toEqual({ message: 'hello world' })
284
- expect(handlerExecuted).toBeTruthy()
285
- })
286
-
287
- it('Should authorize from a static array passed to options.keys (key 2)', async () => {
288
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])
289
- const req = new Request('http://localhost/auth-with-keys/a')
290
- req.headers.set('Authorization', `Bearer ${credential}`)
291
- const res = await app.request(req)
292
- expect(res).not.toBeNull()
293
- expect(res.status).toBe(200)
294
- expect(await res.json()).toEqual({ message: 'hello world' })
295
- expect(handlerExecuted).toBeTruthy()
296
- expect(res.status).toBe(200)
297
- })
298
-
299
- it('Should not authorize a token without header', async () => {
300
- const encodeJwtPart = (part: unknown): string =>
301
- encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')
302
- const encodeSignaturePart = (buf: ArrayBufferLike): string =>
303
- encodeBase64Url(buf).replace(/=/g, '')
304
- const jwtSignWithoutHeader = async (payload: JWTPayload, privateKey: AcrylJsonWebKey) => {
305
- const encodedPayload = encodeJwtPart(payload)
306
- const signaturePart = await signing(
307
- privateKey,
308
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
- privateKey.alg as any,
310
- utf8Encoder.encode(encodedPayload)
311
- )
312
- const signature = encodeSignaturePart(signaturePart)
313
- return `${encodedPayload}.${signature}`
314
- }
315
- const credential = await jwtSignWithoutHeader(
316
- { message: 'hello world' },
317
- test_keys.private_keys[1]
318
- )
319
- const req = new Request('http://localhost/auth-with-keys/a')
320
- req.headers.set('Authorization', `Bearer ${credential}`)
321
- const res = await app.request(req)
322
- expect(res).not.toBeNull()
323
- expect(res.status).toBe(401)
324
- })
325
-
326
- it('Should not authorize a token with missing "kid" in header', async () => {
327
- const encodeJwtPart = (part: unknown): string =>
328
- encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')
329
- const encodeSignaturePart = (buf: ArrayBufferLike): string =>
330
- encodeBase64Url(buf).replace(/=/g, '')
331
- const jwtSignWithoutKid = async (payload: JWTPayload, privateKey: AcrylJsonWebKey) => {
332
- const encodedPayload = encodeJwtPart(payload)
333
- const encodedHeader = encodeJwtPart({ alg: privateKey.alg, typ: 'JWT' })
334
- const partialToken = `${encodedHeader}.${encodedPayload}`
335
- const signaturePart = await signing(
336
- privateKey,
337
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
338
- privateKey.alg as any,
339
- utf8Encoder.encode(partialToken)
340
- )
341
- const signature = encodeSignaturePart(signaturePart)
342
- return `${partialToken}.${signature}`
343
- }
344
- const credential = await jwtSignWithoutKid(
345
- { message: 'hello world' },
346
- test_keys.private_keys[1]
347
- )
348
- const req = new Request('http://localhost/auth-with-keys/a')
349
- req.headers.set('Authorization', `Bearer ${credential}`)
350
- const res = await app.request(req)
351
- expect(res).not.toBeNull()
352
- expect(res.status).toBe(401)
353
- })
354
-
355
- it('Should not authorize a token with invalid "kid" in header', async () => {
356
- const copy = structuredClone(test_keys.private_keys[1])
357
- copy.kid = 'invalid-kid'
358
- const credential = await Jwt.sign({ message: 'hello world' }, copy)
359
- const req = new Request('http://localhost/auth-with-keys/a')
360
- req.headers.set('Authorization', `Bearer ${credential}`)
361
- const res = await app.request(req)
362
- expect(res).not.toBeNull()
363
- expect(res.status).toBe(401)
364
- })
365
-
366
- it('Should authorize with Unicode payload from a static array passed to options.keys', async () => {
367
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
368
- const req = new Request('http://localhost/auth-with-keys-unicode/a')
369
- req.headers.set('Authorization', `bearer ${credential}`)
370
- const res = await app.request(req)
371
- expect(res).not.toBeNull()
372
- expect(res.status).toBe(200)
373
- expect(await res.json()).toEqual({ message: 'hello world' })
374
- expect(handlerExecuted).toBeTruthy()
375
- })
376
-
377
- it('Should authorize with mixed-case Bearer scheme', async () => {
378
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
379
- const req = new Request('http://localhost/auth-with-keys-unicode/a')
380
- req.headers.set('Authorization', `bEaReR ${credential}`)
381
- const res = await app.request(req)
382
- expect(res).not.toBeNull()
383
- expect(res.status).toBe(200)
384
- expect(await res.json()).toEqual({ message: 'hello world' })
385
- expect(handlerExecuted).toBeTruthy()
386
- })
387
-
388
- it('Should authorize from a function passed to options.keys', async () => {
389
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
390
- const req = new Request('http://localhost/auth-with-keys-fn/a')
391
- req.headers.set('Authorization', `Bearer ${credential}`)
392
- const res = await app.request(req)
393
- expect(res).not.toBeNull()
394
- expect(res.status).toBe(200)
395
- expect(await res.json()).toEqual({ message: 'hello world' })
396
- expect(handlerExecuted).toBeTruthy()
397
- })
398
-
399
- it('Should authorize from keys remotely fetched from options.jwks_uri', async () => {
400
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
401
- const req = new Request('http://localhost/auth-with-jwks_uri/a')
402
- req.headers.set('Authorization', `Bearer ${credential}`)
403
- const res = await app.request(req)
404
- expect(res).not.toBeNull()
405
- expect(res.status).toBe(200)
406
- expect(await res.json()).toEqual({ message: 'hello world' })
407
- expect(handlerExecuted).toBeTruthy()
408
- })
409
-
410
- it('Should authorize from keys and hard-coded and remotely fetched from options.jwks_uri', async () => {
411
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
412
- const req = new Request('http://localhost/auth-with-keys-and-jwks_uri/a')
413
- req.headers.set('Authorization', `Bearer ${credential}`)
414
- const res = await app.request(req)
415
- expect(res).not.toBeNull()
416
- expect(res.status).toBe(200)
417
- expect(await res.json()).toEqual({ message: 'hello world' })
418
- expect(handlerExecuted).toBeTruthy()
419
- })
420
-
421
- it('Should not authorize requests with non-Bearer scheme in header', async () => {
422
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
423
- const url = 'http://localhost/auth-with-keys-unicode/a'
424
- const req = new Request(url)
425
- req.headers.set('Authorization', `Basic ${credential}`)
426
- const res = await app.request(req)
427
- expect(res).not.toBeNull()
428
- expect(res.status).toBe(401)
429
- expect(res.headers.get('www-authenticate')).toEqual(
430
- `Bearer realm="${url}",error="invalid_request",error_description="invalid credentials structure"`
431
- )
432
- expect(handlerExecuted).toBeFalsy()
433
- })
434
-
435
- it('Should not authorize requests with invalid token in Bearer header', async () => {
436
- const invalidToken =
437
- 'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
438
-
439
- const url = 'http://localhost/auth-with-keys-unicode/a'
440
- const req = new Request(url)
441
- req.headers.set('Authorization', `Bearer ${invalidToken}`)
442
- const res = await app.request(req)
443
- expect(res).not.toBeNull()
444
- expect(res.status).toBe(401)
445
- expect(res.headers.get('www-authenticate')).toEqual(
446
- `Bearer realm="${url}",error="invalid_token",error_description="token verification failure"`
447
- )
448
- expect(handlerExecuted).toBeFalsy()
449
- })
450
-
451
- it('Should not authorize requests with malformed token structure in header', async () => {
452
- const invalid_token = 'invalid token'
453
- const url = 'http://localhost/auth-with-keys/a'
454
- const req = new Request(url)
455
- req.headers.set('Authorization', `Bearer ${invalid_token}`)
456
- const res = await app.request(req)
457
- expect(res).not.toBeNull()
458
- expect(res.status).toBe(401)
459
- expect(res.headers.get('www-authenticate')).toEqual(
460
- `Bearer realm="${url}",error="invalid_request",error_description="invalid credentials structure"`
461
- )
462
- expect(handlerExecuted).toBeFalsy()
463
- })
464
-
465
- it('Should not authorize requests without authorization in nested JWK middleware', async () => {
466
- const req = new Request('http://localhost/auth-with-keys-nested/a')
467
- const res = await app.request(req)
468
- expect(res).not.toBeNull()
469
- expect(res.status).toBe(401)
470
- expect(await res.text()).toBe('Unauthorized')
471
- expect(handlerExecuted).toBeFalsy()
472
- })
473
-
474
- it('Should authorize requests with authorization in nested JWK middleware', async () => {
475
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
476
- const req = new Request('http://localhost/auth-with-keys-nested/a')
477
- req.headers.set('Authorization', `Bearer ${credential}`)
478
- const res = await app.request(req)
479
- expect(res).not.toBeNull()
480
- expect(res.status).toBe(200)
481
- expect(await res.json()).toEqual({ message: 'hello world' })
482
- expect(handlerExecuted).toBeTruthy()
483
- })
484
- })
485
-
486
- describe('Credentials in custom header', () => {
487
- let handlerExecuted: boolean
488
-
489
- beforeEach(() => {
490
- handlerExecuted = false
491
- })
492
-
493
- const app = new Acryl()
494
-
495
- app.use(
496
- '/auth-with-keys/*',
497
- jwk({ keys: verify_keys, headerName: 'x-custom-auth-header', alg: ['RS256'] })
498
- )
499
-
500
- app.get('/auth-with-keys/*', (c) => {
501
- handlerExecuted = true
502
- const payload = c.get('jwtPayload')
503
- return c.json(payload)
504
- })
505
-
506
- it('Should not authorize', async () => {
507
- const req = new Request('http://localhost/auth-with-keys/a')
508
- const res = await app.request(req)
509
- expect(res).not.toBeNull()
510
- expect(res.status).toBe(401)
511
- expect(await res.text()).toBe('Unauthorized')
512
- expect(handlerExecuted).toBeFalsy()
513
- })
514
-
515
- it('Should not authorize even if default authorization header present', async () => {
516
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
517
-
518
- const req = new Request('http://localhost/auth-with-keys/a')
519
- req.headers.set('Authorization', `Bearer ${credential}`)
520
- const res = await app.request(req)
521
- expect(res).not.toBeNull()
522
- expect(res.status).toBe(401)
523
- expect(await res.text()).toBe('Unauthorized')
524
- expect(handlerExecuted).toBeFalsy()
525
- })
526
-
527
- it('Should authorize', async () => {
528
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])
529
-
530
- const req = new Request('http://localhost/auth-with-keys/a')
531
- req.headers.set('x-custom-auth-header', `Bearer ${credential}`)
532
- const res = await app.request(req)
533
- expect(res).not.toBeNull()
534
- expect(res.status).toBe(200)
535
- expect(await res.json()).toEqual({ message: 'hello world' })
536
- expect(handlerExecuted).toBeTruthy()
537
- })
538
-
539
- it('Should authorize with mixed-case Bearer scheme in custom header', async () => {
540
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])
541
-
542
- const req = new Request('http://localhost/auth-with-keys/a')
543
- req.headers.set('x-custom-auth-header', `bEaReR ${credential}`)
544
- const res = await app.request(req)
545
- expect(res).not.toBeNull()
546
- expect(res.status).toBe(200)
547
- expect(await res.json()).toEqual({ message: 'hello world' })
548
- expect(handlerExecuted).toBeTruthy()
549
- })
550
-
551
- it('Should not authorize with non-Bearer scheme in custom header', async () => {
552
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])
553
- const url = 'http://localhost/auth-with-keys/a'
554
-
555
- const req = new Request(url)
556
- req.headers.set('x-custom-auth-header', `Basic ${credential}`)
557
- const res = await app.request(req)
558
- expect(res).not.toBeNull()
559
- expect(res.status).toBe(401)
560
- expect(res.headers.get('www-authenticate')).toEqual(
561
- `Bearer realm="${url}",error="invalid_request",error_description="invalid credentials structure"`
562
- )
563
- expect(handlerExecuted).toBeFalsy()
564
- })
565
-
566
- it('Should not authorize invalid token with Bearer scheme in custom header', async () => {
567
- const invalidToken =
568
- 'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
569
- const url = 'http://localhost/auth-with-keys/a'
570
-
571
- const req = new Request(url)
572
- req.headers.set('x-custom-auth-header', `Bearer ${invalidToken}`)
573
- const res = await app.request(req)
574
- expect(res).not.toBeNull()
575
- expect(res.status).toBe(401)
576
- expect(res.headers.get('www-authenticate')).toEqual(
577
- `Bearer realm="${url}",error="invalid_token",error_description="token verification failure"`
578
- )
579
- expect(handlerExecuted).toBeFalsy()
580
- })
581
- })
582
-
583
- describe('Credentials in cookie', () => {
584
- let handlerExecuted: boolean
585
-
586
- beforeEach(() => {
587
- handlerExecuted = false
588
- })
589
-
590
- const app = new Acryl()
591
-
592
- app.use('/auth-with-keys/*', jwk({ keys: verify_keys, cookie: 'access_token', alg: ['RS256'] }))
593
- app.use(
594
- '/auth-with-keys-unicode/*',
595
- jwk({ keys: verify_keys, cookie: 'access_token', alg: ['RS256'] })
596
- )
597
- app.use(
598
- '/auth-with-keys-prefixed/*',
599
- jwk({
600
- keys: verify_keys,
601
- cookie: { key: 'access_token', prefixOptions: 'host' },
602
- alg: ['RS256'],
603
- })
604
- )
605
- app.use(
606
- '/auth-with-keys-unprefixed/*',
607
- jwk({ keys: verify_keys, cookie: { key: 'access_token' }, alg: ['RS256'] })
608
- )
609
-
610
- app.get('/auth-with-keys/*', (c) => {
611
- handlerExecuted = true
612
- const payload = c.get('jwtPayload')
613
- return c.json(payload)
614
- })
615
- app.get('/auth-with-keys-prefixed/*', (c) => {
616
- handlerExecuted = true
617
- const payload = c.get('jwtPayload')
618
- return c.json(payload)
619
- })
620
- app.get('/auth-with-keys-unprefixed/*', (c) => {
621
- handlerExecuted = true
622
- const payload = c.get('jwtPayload')
623
- return c.json(payload)
624
- })
625
- app.get('/auth-with-keys-unicode/*', (c) => {
626
- handlerExecuted = true
627
- const payload = c.get('jwtPayload')
628
- return c.json(payload)
629
- })
630
-
631
- it('Should not authorize requests with missing access token', async () => {
632
- const req = new Request('http://localhost/auth-with-keys/a')
633
- const res = await app.request(req)
634
- expect(res).not.toBeNull()
635
- expect(res.status).toBe(401)
636
- expect(await res.text()).toBe('Unauthorized')
637
- expect(handlerExecuted).toBeFalsy()
638
- })
639
-
640
- it('Should authorize cookie from a static array passed to options.keys', async () => {
641
- const url = 'http://localhost/auth-with-keys/a'
642
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
643
- const req = new Request(url, {
644
- headers: new Headers({
645
- Cookie: `access_token=${credential}`,
646
- }),
647
- })
648
- const res = await app.request(req)
649
- expect(res).not.toBeNull()
650
- expect(await res.json()).toEqual({ message: 'hello world' })
651
- expect(res.status).toBe(200)
652
- expect(handlerExecuted).toBeTruthy()
653
- })
654
-
655
- it('Should authorize prefixed cookie from a static array passed to options.keys', async () => {
656
- const url = 'http://localhost/auth-with-keys-prefixed/a'
657
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
658
- const req = new Request(url, {
659
- headers: new Headers({
660
- Cookie: `__Host-access_token=${credential}`,
661
- }),
662
- })
663
- const res = await app.request(req)
664
- expect(res).not.toBeNull()
665
- expect(await res.json()).toEqual({ message: 'hello world' })
666
- expect(res.status).toBe(200)
667
- expect(handlerExecuted).toBeTruthy()
668
- })
669
-
670
- it('Should authorize unprefixed cookie from a static array passed to options.keys', async () => {
671
- const url = 'http://localhost/auth-with-keys-unprefixed/a'
672
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
673
- const req = new Request(url, {
674
- headers: new Headers({
675
- Cookie: `access_token=${credential}`,
676
- }),
677
- })
678
- const res = await app.request(req)
679
- expect(res).not.toBeNull()
680
- expect(await res.json()).toEqual({ message: 'hello world' })
681
- expect(res.status).toBe(200)
682
- expect(handlerExecuted).toBeTruthy()
683
- })
684
-
685
- it('Should authorize with Unicode payload from a static array passed to options.keys', async () => {
686
- const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])
687
- const req = new Request('http://localhost/auth-with-keys-unicode/a', {
688
- headers: new Headers({
689
- Cookie: `access_token=${credential}`,
690
- }),
691
- })
692
- const res = await app.request(req)
693
- expect(res).not.toBeNull()
694
- expect(res.status).toBe(200)
695
- expect(await res.json()).toEqual({ message: 'hello world' })
696
- expect(handlerExecuted).toBeTruthy()
697
- })
698
-
699
- it('Should not authorize requests with invalid Unicode payload in cookie', async () => {
700
- const invalidToken =
701
- 'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
702
-
703
- const url = 'http://localhost/auth-with-keys-unicode/a'
704
- const req = new Request(url)
705
- req.headers.set('Cookie', `access_token=${invalidToken}`)
706
- const res = await app.request(req)
707
- expect(res).not.toBeNull()
708
- expect(res.status).toBe(401)
709
- expect(res.headers.get('www-authenticate')).toEqual(
710
- `Bearer realm="${url}",error="invalid_token",error_description="token verification failure"`
711
- )
712
- expect(handlerExecuted).toBeFalsy()
713
- })
714
-
715
- it('Should not authorize requests with malformed token structure in cookie', async () => {
716
- const invalidToken = 'invalid token'
717
- const url = 'http://localhost/auth-with-keys/a'
718
- const req = new Request(url)
719
- req.headers.set('Cookie', `access_token=${invalidToken}`)
720
- const res = await app.request(req)
721
- expect(res).not.toBeNull()
722
- expect(res.status).toBe(401)
723
- expect(res.headers.get('www-authenticate')).toEqual(
724
- `Bearer realm="${url}",error="invalid_token",error_description="token verification failure"`
725
- )
726
- expect(handlerExecuted).toBeFalsy()
727
- })
728
- })
729
-
730
- describe('Credentials in a signed cookie', () => {
731
- let handlerExecuted: boolean
732
-
733
- beforeEach(() => {
734
- handlerExecuted = false
735
- })
736
-
737
- const app = new Acryl()
738
- const test_secret = 'Shhh'
739
-
740
- app.use(
741
- '/auth-with-signed-cookie/*',
742
- jwk({
743
- keys: verify_keys,
744
- cookie: { key: 'access_token', secret: test_secret },
745
- alg: ['RS256'],
746
- })
747
- )
748
- app.use(
749
- '/auth-with-signed-with-prefix-options-cookie/*',
750
- jwk({
751
- keys: verify_keys,
752
- cookie: { key: 'access_token', secret: test_secret, prefixOptions: 'host' },
753
- alg: ['RS256'],
754
- })
755
- )
756
-
757
- app.get('/sign-cookie', async (c) => {
758
- const credential = await Jwt.sign(
759
- { message: 'signed hello world' },
760
- test_keys.private_keys[0]
761
- )
762
- await setSignedCookie(c, 'access_token', credential, test_secret)
763
- return c.text('OK')
764
- })
765
- app.get('/sign-cookie-with-prefix', async (c) => {
766
- const credential = await Jwt.sign(
767
- { message: 'signed hello world' },
768
- test_keys.private_keys[0]
769
- )
770
- await setSignedCookie(c, 'access_token', credential, test_secret, { prefix: 'host' })
771
- return c.text('OK')
772
- })
773
- app.get('/auth-with-signed-cookie/*', async (c) => {
774
- handlerExecuted = true
775
- const payload = c.get('jwtPayload')
776
- return c.json(payload)
777
- })
778
- app.get('/auth-with-signed-with-prefix-options-cookie/*', async (c) => {
779
- handlerExecuted = true
780
- const payload = c.get('jwtPayload')
781
- return c.json(payload)
782
- })
783
-
784
- it('Should authorize signed cookie', async () => {
785
- const url = 'http://localhost/auth-with-signed-cookie/a'
786
- const sign_res = await app.request('http://localhost/sign-cookie')
787
- const cookieHeader = sign_res.headers.get('Set-Cookie') as string
788
- expect(cookieHeader).not.toBeNull()
789
- const req = new Request(url)
790
- req.headers.set('Cookie', cookieHeader)
791
- const res = await app.request(req)
792
- expect(res).not.toBeNull()
793
- expect(res.status).toBe(200)
794
- expect(await res.json()).toEqual({ message: 'signed hello world' })
795
- expect(handlerExecuted).toBeTruthy()
796
- })
797
-
798
- it('Should authorize prefixed signed cookie', async () => {
799
- const url = 'http://localhost/auth-with-signed-with-prefix-options-cookie/a'
800
- const sign_res = await app.request('http://localhost/sign-cookie-with-prefix')
801
- const cookieHeader = sign_res.headers.get('Set-Cookie') as string
802
- expect(cookieHeader).not.toBeNull()
803
- const req = new Request(url)
804
- req.headers.set('Cookie', cookieHeader)
805
- const res = await app.request(req)
806
- expect(res).not.toBeNull()
807
- expect(res.status).toBe(200)
808
- expect(await res.json()).toEqual({ message: 'signed hello world' })
809
- expect(handlerExecuted).toBeTruthy()
810
- })
811
-
812
- it('Should not authorize an unsigned cookie', async () => {
813
- const url = 'http://localhost/auth-with-signed-cookie/a'
814
- const credential = await Jwt.sign(
815
- { message: 'unsigned hello world' },
816
- test_keys.private_keys[0]
817
- )
818
- const unsignedCookie = `access_token=${credential}`
819
- const req = new Request(url)
820
- req.headers.set('Cookie', unsignedCookie)
821
- const res = await app.request(req)
822
- expect(res.status).toBe(401)
823
- expect(await res.text()).toBe('Unauthorized')
824
- expect(handlerExecuted).toBeFalsy()
825
- })
826
- })
827
-
828
- describe('Error handling with `cause`', () => {
829
- const app = new Acryl()
830
-
831
- app.use('/auth-with-keys/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
832
- app.get('/auth-with-keys/*', (c) => c.text('Authorized'))
833
-
834
- app.onError((e, c) => {
835
- if (e instanceof HTTPException && e.cause instanceof Error) {
836
- return c.json({ name: e.cause.name, message: e.cause.message }, 401)
837
- }
838
- return c.text(e.message, 401)
839
- })
840
-
841
- it('Should not authorize', async () => {
842
- const credential = 'abc.def.ghi'
843
- const req = new Request('http://localhost/auth-with-keys')
844
- req.headers.set('Authorization', `Bearer ${credential}`)
845
- const res = await app.request(req)
846
- expect(res.status).toBe(401)
847
- expect(await res.json()).toEqual({
848
- name: 'JwtTokenInvalid',
849
- message: `invalid JWT token: ${credential}`,
850
- })
851
- })
852
- })
853
-
854
- describe('Verification of token attributes', () => {
855
- let handlerExecuted: boolean
856
-
857
- beforeEach(() => {
858
- handlerExecuted = false
859
- })
860
-
861
- function inFuture() {
862
- return Date.now() / 1000 + 100
863
- }
864
-
865
- function inPast() {
866
- return Date.now() / 1000 - 100
867
- }
868
-
869
- const app = new Acryl()
870
-
871
- app.use('/auth-with-keys-default/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
872
- app.use(
873
- '/auth-with-keys-and-issuer/*',
874
- jwk({ keys: verify_keys, verification: { iss: 'http://issuer.test' }, alg: ['RS256'] })
875
- )
876
-
877
- app.get('/auth-with-keys-default/*', (c) => {
878
- handlerExecuted = true
879
- const payload = c.get('jwtPayload')
880
- return c.json(payload)
881
- })
882
- app.get('/auth-with-keys-and-issuer/*', (c) => {
883
- handlerExecuted = true
884
- const payload = c.get('jwtPayload')
885
- return c.json(payload)
886
- })
887
-
888
- it('Should validate exp/nbf/iat and pass when good by default', async () => {
889
- const payload = {
890
- exp: inFuture(),
891
- nbf: inPast(),
892
- iat: inPast(),
893
- iss: 'http://not-checked.test',
894
- }
895
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
896
- const req = new Request('http://localhost/auth-with-keys-default/a')
897
- req.headers.set('Authorization', `Bearer ${credential}`)
898
- const res = await app.request(req)
899
- expect(res).not.toBeNull()
900
- expect(res.status).toBe(200)
901
- expect(await res.json()).toEqual(payload)
902
- expect(handlerExecuted).toBeTruthy()
903
- })
904
-
905
- it('Should validate exp and fail when bad', async () => {
906
- const payload = {
907
- exp: inPast(),
908
- }
909
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
910
- const req = new Request('http://localhost/auth-with-keys-default/a')
911
- req.headers.set('Authorization', `Bearer ${credential}`)
912
- const res = await app.request(req)
913
- expect(res).not.toBeNull()
914
- expect(res.status).toBe(401)
915
- expect(handlerExecuted).toBeFalsy()
916
- })
917
-
918
- it('Should validate nbf and fail when bad', async () => {
919
- const payload = {
920
- nbf: inFuture(),
921
- }
922
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
923
- const req = new Request('http://localhost/auth-with-keys-default/a')
924
- req.headers.set('Authorization', `Bearer ${credential}`)
925
- const res = await app.request(req)
926
- expect(res).not.toBeNull()
927
- expect(res.status).toBe(401)
928
- expect(handlerExecuted).toBeFalsy()
929
- })
930
-
931
- it('Should validate iat and fail when bad', async () => {
932
- const payload = {
933
- iat: inFuture(),
934
- }
935
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
936
- const req = new Request('http://localhost/auth-with-keys-default/a')
937
- req.headers.set('Authorization', `Bearer ${credential}`)
938
- const res = await app.request(req)
939
- expect(res).not.toBeNull()
940
- expect(res.status).toBe(401)
941
- expect(handlerExecuted).toBeFalsy()
942
- })
943
-
944
- it('Should validate iss when supplied', async () => {
945
- const payload = {
946
- iss: 'http://issuer.test',
947
- }
948
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
949
- const req = new Request('http://localhost/auth-with-keys-and-issuer/a')
950
- req.headers.set('Authorization', `Bearer ${credential}`)
951
- const res = await app.request(req)
952
- expect(res).not.toBeNull()
953
- expect(res.status).toBe(200)
954
- expect(await res.json()).toEqual(payload)
955
- expect(handlerExecuted).toBeTruthy()
956
- })
957
-
958
- it('Should reject missing iss when required', async () => {
959
- const payload = {
960
- // Nothing
961
- }
962
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
963
- const req = new Request('http://localhost/auth-with-keys-and-issuer/a')
964
- req.headers.set('Authorization', `Bearer ${credential}`)
965
- const res = await app.request(req)
966
- expect(res).not.toBeNull()
967
- expect(res.status).toBe(401)
968
- expect(handlerExecuted).toBeFalsy()
969
- })
970
-
971
- it('Should reject iss when different', async () => {
972
- const payload = {
973
- iss: 'http://bad-issuer.test',
974
- }
975
- const credential = await Jwt.sign(payload, test_keys.private_keys[0])
976
- const req = new Request('http://localhost/auth-with-keys-and-issuer/a')
977
- req.headers.set('Authorization', `Bearer ${credential}`)
978
- const res = await app.request(req)
979
- expect(res).not.toBeNull()
980
- expect(res.status).toBe(401)
981
- expect(handlerExecuted).toBeFalsy()
982
- })
983
- })
984
-
985
- describe('Algorithm whitelist (options.alg)', () => {
986
- let handlerExecuted: boolean
987
-
988
- beforeEach(() => {
989
- handlerExecuted = false
990
- })
991
-
992
- const app = new Acryl()
993
-
994
- // Only allow RS256
995
- app.use('/auth-whitelist-rs256/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
996
- app.get('/auth-whitelist-rs256/*', (c) => {
997
- handlerExecuted = true
998
- return c.json(c.get('jwtPayload'))
999
- })
1000
-
1001
- // Allow multiple algorithms
1002
- app.use('/auth-whitelist-multi/*', jwk({ keys: verify_keys, alg: ['RS256', 'ES256'] }))
1003
- app.get('/auth-whitelist-multi/*', (c) => {
1004
- handlerExecuted = true
1005
- return c.json(c.get('jwtPayload'))
1006
- })
1007
-
1008
- // Note: Test for "no whitelist" was removed because alg is now required.
1009
- // This is a breaking change that enforces explicit algorithm specification for security.
1010
-
1011
- it('Should authorize RS256 token when RS256 is in whitelist', async () => {
1012
- const payload = { message: 'hello world' }
1013
- const credential = await Jwt.sign(payload, test_keys.private_keys[0]) // RS256 key
1014
- const req = new Request('http://localhost/auth-whitelist-rs256/a')
1015
- req.headers.set('Authorization', `Bearer ${credential}`)
1016
- const res = await app.request(req)
1017
- expect(res).not.toBeNull()
1018
- expect(res.status).toBe(200)
1019
- expect(await res.json()).toEqual(payload)
1020
- expect(handlerExecuted).toBeTruthy()
1021
- })
1022
-
1023
- it('Should reject token when algorithm is not in whitelist', async () => {
1024
- // Create a token with ES256 algorithm manually
1025
- const kid = 'acryl-test-kid-1' // Use existing kid but header will have different alg
1026
- const payload = { message: 'hello world' }
1027
-
1028
- // Generate ES256 key pair for signing
1029
- const keyPair = await crypto.subtle.generateKey(
1030
- {
1031
- name: 'ECDSA',
1032
- namedCurve: 'P-256',
1033
- },
1034
- true,
1035
- ['sign', 'verify']
1036
- )
1037
-
1038
- // Create JWT with ES256
1039
- const header = { alg: 'ES256', typ: 'JWT', kid }
1040
- const encode = (obj: object) =>
1041
- encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
1042
- const encodedHeader = encode(header)
1043
- const encodedPayload = encode(payload)
1044
- const signingInput = `${encodedHeader}.${encodedPayload}`
1045
-
1046
- const signatureBuffer = await signing(
1047
- keyPair.privateKey,
1048
- 'ES256',
1049
- utf8Encoder.encode(signingInput)
1050
- )
1051
- const signature = encodeBase64Url(signatureBuffer)
1052
-
1053
- const token = `${encodedHeader}.${encodedPayload}.${signature}`
1054
-
1055
- const url = 'http://localhost/auth-whitelist-rs256/a'
1056
- const req = new Request(url)
1057
- req.headers.set('Authorization', `Bearer ${token}`)
1058
- const res = await app.request(req)
1059
- expect(res).not.toBeNull()
1060
- expect(res.status).toBe(401)
1061
- expect(res.headers.get('www-authenticate')).toMatch(/token verification failure/)
1062
- expect(handlerExecuted).toBeFalsy()
1063
- })
1064
-
1065
- it('Should authorize RS256 token when multiple algorithms are in whitelist', async () => {
1066
- const payload = { message: 'hello world' }
1067
- const credential = await Jwt.sign(payload, test_keys.private_keys[0]) // RS256 key
1068
- const req = new Request('http://localhost/auth-whitelist-multi/a')
1069
- req.headers.set('Authorization', `Bearer ${credential}`)
1070
- const res = await app.request(req)
1071
- expect(res).not.toBeNull()
1072
- expect(res.status).toBe(200)
1073
- expect(await res.json()).toEqual(payload)
1074
- expect(handlerExecuted).toBeTruthy()
1075
- })
1076
-
1077
- // Note: Test for "no whitelist" was removed because alg is now required.
1078
- // This is a breaking change that enforces explicit algorithm specification for security.
1079
- })
1080
- describe('WWW-Authenticate realm', () => {
1081
- it('Should default to the request URL', async () => {
1082
- const app = new Acryl()
1083
- app.use('/auth/*', jwk({ keys: verify_keys, alg: ['RS256'] }))
1084
- app.get('/auth/*', (c) => c.text('ok'))
1085
-
1086
- const res = await app.request('http://localhost/auth/page?a=1')
1087
-
1088
- expect(res.status).toBe(401)
1089
- expect(res.headers.get('WWW-Authenticate')).toBe(
1090
- 'Bearer realm="http://localhost/auth/page?a=1",error="invalid_request",error_description="no authorization included in request"'
1091
- )
1092
- })
1093
-
1094
- it('Should use the configured realm', async () => {
1095
- const app = new Acryl()
1096
- app.use('/auth/*', jwk({ keys: verify_keys, alg: ['RS256'], realm: 'my-api' }))
1097
- app.get('/auth/*', (c) => c.text('ok'))
1098
-
1099
- const res = await app.request('http://localhost/auth/page?a=1')
1100
-
1101
- expect(res.status).toBe(401)
1102
- expect(res.headers.get('WWW-Authenticate')).toBe(
1103
- 'Bearer realm="my-api",error="invalid_request",error_description="no authorization included in request"'
1104
- )
1105
- })
1106
-
1107
- it('Should use the configured realm for an invalid token', async () => {
1108
- const app = new Acryl()
1109
- app.use('/auth/*', jwk({ keys: verify_keys, alg: ['RS256'], realm: 'my-api' }))
1110
- app.get('/auth/*', (c) => c.text('ok'))
1111
-
1112
- const req = new Request('http://localhost/auth/page')
1113
- req.headers.set('Authorization', 'Bearer invalid-token')
1114
- const res = await app.request(req)
1115
-
1116
- expect(res.status).toBe(401)
1117
- expect(res.headers.get('WWW-Authenticate')).toBe(
1118
- 'Bearer realm="my-api",error="invalid_token",error_description="token verification failure"'
1119
- )
1120
- })
1121
-
1122
- it('Should escape double quotes in the realm', async () => {
1123
- const app = new Acryl()
1124
- app.use('/auth/*', jwk({ keys: verify_keys, alg: ['RS256'], realm: 'my "quoted" api' }))
1125
- app.get('/auth/*', (c) => c.text('ok'))
1126
-
1127
- const res = await app.request('http://localhost/auth/page')
1128
-
1129
- expect(res.status).toBe(401)
1130
- expect(res.headers.get('WWW-Authenticate')).toBe(
1131
- 'Bearer realm="my \\"quoted\\" api",error="invalid_request",error_description="no authorization included in request"'
1132
- )
1133
- })
1134
- })
1135
- })