acrylblends 0.6.3 → 0.6.4

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 (269) hide show
  1. package/README.md +3 -2
  2. package/docs/framework/README.md +2 -2
  3. package/docs/framework/UPSTREAM.md +1 -1
  4. package/docs/framework/guides/jsx.md +24 -0
  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/basic-auth.d.ts +1 -1
  13. package/lib/bearer-auth.d.ts +1 -1
  14. package/lib/body-limit.d.ts +1 -1
  15. package/lib/cache.d.ts +1 -1
  16. package/lib/cache.js +4 -4
  17. package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
  18. package/lib/client.d.ts +2 -2
  19. package/lib/client.js +2 -1
  20. package/lib/combine.d.ts +1 -1
  21. package/lib/{components-Ce5FRyHU.js → components-0o0CBYNv.js} +2 -2
  22. package/lib/{components-DYZdxJIJ.js → components-DdI7-ns7.js} +1 -1
  23. package/lib/compress.d.ts +1 -1
  24. package/lib/conninfo.d.ts +2 -2
  25. package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
  26. package/lib/context-storage.d.ts +1 -1
  27. package/lib/cookie.d.ts +1 -1
  28. package/lib/cors.d.ts +1 -1
  29. package/lib/csrf.d.ts +1 -1
  30. package/lib/dev.d.ts +2 -2
  31. package/lib/etag.d.ts +1 -1
  32. package/lib/factory.d.ts +2 -2
  33. package/lib/{hooks-Bq5q9IMl.js → hooks-BMw6BAyP.js} +5 -5
  34. package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
  35. package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
  36. package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
  37. package/lib/index.d.ts +4 -4
  38. package/lib/ip-restriction.d.ts +2 -2
  39. package/lib/jsx/dom/client.js +1 -1
  40. package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
  41. package/lib/jsx/dom/jsx-runtime.js +1 -1
  42. package/lib/jsx/dom/server.js +1 -1
  43. package/lib/jsx/dom.js +3 -3
  44. package/lib/jsx/streaming.js +1 -1
  45. package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-D18Y35TI.js} +1 -1
  46. package/lib/jsx-renderer.d.ts +75 -0
  47. package/lib/jsx-renderer.js +99 -0
  48. package/lib/jsx.js +3 -3
  49. package/lib/jwk.d.ts +1 -1
  50. package/lib/jwt.d.ts +1 -1
  51. package/lib/language.d.ts +1 -1
  52. package/lib/logger.d.ts +1 -1
  53. package/lib/loopback-only.d.ts +1 -1
  54. package/lib/method-not-allowed.d.ts +2 -2
  55. package/lib/method-override.d.ts +2 -2
  56. package/lib/mount.d.ts +1 -1
  57. package/lib/node-server/conninfo.d.ts +1 -1
  58. package/lib/node-server/serve-static.d.ts +1 -1
  59. package/lib/node-server/serve-static.js +1 -1
  60. package/lib/node-server.d.ts +1 -1
  61. package/lib/node-server.js +1 -1
  62. package/lib/powered-by.d.ts +1 -1
  63. package/lib/pretty-json.d.ts +1 -1
  64. package/lib/quick.d.ts +1 -1
  65. package/lib/request-id.d.ts +1 -1
  66. package/lib/request.d.ts +1 -1
  67. package/lib/route.d.ts +1 -1
  68. package/lib/schema-validator.d.ts +1 -1
  69. package/lib/secure-headers.d.ts +1 -1
  70. package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
  71. package/lib/serve-static.d.ts +1 -1
  72. package/lib/ssg-BwjcA-Ge.js +389 -0
  73. package/lib/ssg.d.ts +156 -0
  74. package/lib/ssg.js +2 -0
  75. package/lib/streaming.d.ts +1 -1
  76. package/lib/testing.d.ts +3 -3
  77. package/lib/testing.js +1 -1
  78. package/lib/timeout.d.ts +1 -1
  79. package/lib/timing.d.ts +1 -1
  80. package/lib/tiny.d.ts +1 -1
  81. package/lib/trailing-slash.d.ts +1 -1
  82. package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
  83. package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
  84. package/lib/types.d.ts +1 -1
  85. package/lib/utils/body.d.ts +1 -1
  86. package/lib/utils/ipaddr.d.ts +1 -1
  87. package/lib/utils-Cx61GFMC.js +125 -0
  88. package/lib/validator.d.ts +1 -1
  89. package/lib/ws.d.ts +1 -1
  90. package/package.json +9 -1
  91. package/plugins/acryl-inspect/upstream.json +1 -1
  92. package/plugins/acryl-node-server/upstream.json +1 -1
  93. package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
  94. package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
  95. package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
  96. package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
  97. package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
  98. package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
  99. package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
  100. package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
  101. package/plugins/acryl-router/src/helpers/index.ts +1 -0
  102. package/plugins/acryl-router/src/middleware/index.ts +1 -0
  103. package/plugins/acryl-router/upstream.json +2 -2
  104. package/plugins/acryl-router-http/upstream.json +1 -1
  105. package/plugins/acryl-state/upstream.json +1 -1
  106. package/plugins/desktop/upstream.json +1 -1
  107. package/plugins/desktop-web/upstream.json +1 -1
  108. package/plugins/extensions/resolve.ts +4 -0
  109. package/shared/AGENTS.core.md +4 -0
  110. package/templates/agent/tsconfig.json +7 -1
  111. package/templates/todo/upstream.json +1 -1
  112. package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
  113. package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
  114. package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
  115. package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
  116. package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
  117. package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
  118. package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
  119. package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
  120. package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
  121. package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
  122. package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
  123. package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
  124. package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
  125. package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
  126. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
  127. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
  128. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
  129. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
  130. package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
  131. package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
  132. package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
  133. package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
  134. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
  135. package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
  136. package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
  137. package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
  138. package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
  139. package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
  140. package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
  141. package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
  142. package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
  143. package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
  144. package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
  145. package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
  146. package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
  147. package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
  148. package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
  149. package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
  150. package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
  151. package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
  152. package/plugins/acryl-router/src/core/app.test.ts +0 -3833
  153. package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
  154. package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
  155. package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
  156. package/plugins/acryl-router/src/core/compose.test.ts +0 -751
  157. package/plugins/acryl-router/src/core/context.test.ts +0 -629
  158. package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
  159. package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
  160. package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
  161. package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
  162. package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
  163. package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
  164. package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
  165. package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
  166. package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
  167. package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
  168. package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
  169. package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
  170. package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
  171. package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
  172. package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
  173. package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
  174. package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
  175. package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
  176. package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
  177. package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
  178. package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
  179. package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
  180. package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
  181. package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
  182. package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
  183. package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
  184. package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
  185. package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
  186. package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
  187. package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
  188. package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
  189. package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
  190. package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
  191. package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
  192. package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
  193. package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
  194. package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
  195. package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
  196. package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
  197. package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
  198. package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
  199. package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
  200. package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
  201. package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
  202. package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
  203. package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
  204. package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
  205. package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
  206. package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
  207. package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
  208. package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
  209. package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
  210. package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
  211. package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
  212. package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
  213. package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
  214. package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
  215. package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
  216. package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
  217. package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
  218. package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
  219. package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
  220. package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
  221. package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
  222. package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
  223. package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
  224. package/plugins/acryl-router/src/core/request.test.ts +0 -751
  225. package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
  226. package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
  227. package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
  228. package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
  229. package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
  230. package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
  231. package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
  232. package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
  233. package/plugins/acryl-router/src/core/types.test.ts +0 -4134
  234. package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
  235. package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
  236. package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
  237. package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
  238. package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
  239. package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
  240. package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
  241. package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
  242. package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
  243. package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
  244. package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
  245. package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
  246. package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
  247. package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
  248. package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
  249. package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
  250. package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
  251. package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
  252. package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
  253. package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
  254. package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
  255. package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
  256. package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
  257. package/plugins/acryl-router/tests/loader.spec.ts +0 -72
  258. package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
  259. package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
  260. package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
  261. package/plugins/acryl-router/tests/service.spec.ts +0 -426
  262. package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
  263. package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
  264. package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
  265. package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
  266. package/plugins/acryl-state/src/react.ts +0 -11
  267. package/plugins/acryl-state/tests/cell.spec.ts +0 -71
  268. package/plugins/acryl-state/tests/react.spec.ts +0 -39
  269. 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
- })