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.
- package/README.md +4 -3
- package/docs/framework/README.md +2 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/guides/jsx.md +51 -1
- package/docs/framework/helpers/ssg.md +3 -6
- package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
- package/docs/framework/upstream.json +2 -2
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
- package/lib/{base-ddiVmiE7.js → base-NSN_SNv-.js} +1 -0
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/cache.js +4 -4
- package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
- package/lib/client.d.ts +2 -2
- package/lib/client.js +2 -1
- package/lib/combine.d.ts +1 -1
- package/lib/{components-DYZdxJIJ.js → components-JXVm45kK.js} +1 -1
- package/lib/{components-Ce5FRyHU.js → components-cEW3ovK0.js} +3 -3
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
- package/lib/context-storage.d.ts +1 -1
- package/lib/cookie.d.ts +1 -1
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/{hooks-Bq5q9IMl.js → hooks-w72iLeFR.js} +6 -6
- package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
- package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
- package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +1 -1
- package/lib/jsx/dom/server.js +2 -2
- package/lib/jsx/dom.js +4 -4
- package/lib/jsx/jsx-dev-runtime.js +1 -1
- package/lib/jsx/jsx-runtime.js +1 -1
- package/lib/jsx/streaming.js +1 -1
- package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-CWLRA2F3.js} +2 -2
- package/lib/jsx-renderer.d.ts +75 -0
- package/lib/jsx-renderer.js +99 -0
- package/lib/jsx.js +4 -4
- package/lib/jwk.d.ts +1 -1
- package/lib/jwt.d.ts +1 -1
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/register.d.ts +1 -0
- package/lib/node-server/register.js +73 -0
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/ssg-BwjcA-Ge.js +389 -0
- package/lib/ssg.d.ts +156 -0
- package/lib/ssg.js +2 -0
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +3 -3
- package/lib/testing.js +1 -1
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
- package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils-Cx61GFMC.js +125 -0
- package/lib/validator.d.ts +1 -1
- package/lib/ws.d.ts +1 -1
- package/package.json +13 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/src/register.ts +86 -0
- package/plugins/acryl-node-server/upstream.json +5 -3
- package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -0
- package/plugins/acryl-router/src/middleware/index.ts +1 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/main.ts +4 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +3 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/resolve.ts +4 -0
- package/shared/AGENTS.core.md +3 -0
- package/templates/agent/package.json +7 -3
- package/templates/agent/tsconfig.json +10 -2
- package/templates/blank/package.json +7 -3
- package/templates/blank/src/about.tsx +14 -0
- package/templates/blank/src/routes.ts +2 -0
- package/templates/blank/test/app.test.ts +15 -0
- package/templates/blank/tsconfig.json +3 -1
- package/templates/todo/package.json +7 -3
- package/templates/todo/tsconfig.json +3 -1
- package/templates/todo/upstream.json +1 -1
- package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
- package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
- package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
- package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
- package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
- package/plugins/acryl-router/src/core/app.test.ts +0 -3833
- package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
- package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
- package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
- package/plugins/acryl-router/src/core/compose.test.ts +0 -751
- package/plugins/acryl-router/src/core/context.test.ts +0 -629
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
- package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
- package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
- package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
- package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
- package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
- package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
- package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
- package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
- package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
- package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
- package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
- package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
- package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
- package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
- package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
- package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
- package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
- package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
- package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
- package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
- package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
- package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
- package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
- package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
- package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
- package/plugins/acryl-router/src/core/request.test.ts +0 -751
- package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
- package/plugins/acryl-router/src/core/types.test.ts +0 -4134
- package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
- package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
- package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
- package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
- package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
- package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
- package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
- package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
- package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
- package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
- package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
- package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
- package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
- package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
- package/plugins/acryl-router/tests/loader.spec.ts +0 -72
- package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
- package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
- package/plugins/acryl-router/tests/service.spec.ts +0 -426
- package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
- package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
- package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
- package/plugins/acryl-state/src/react.ts +0 -11
- package/plugins/acryl-state/tests/cell.spec.ts +0 -71
- package/plugins/acryl-state/tests/react.spec.ts +0 -39
- package/plugins/acryl-state/tests/service.spec.ts +0 -132
|
@@ -1,1788 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
2
|
-
import { vi } from 'vitest'
|
|
3
|
-
import { encodeBase64, encodeBase64Url } from '../encode.ts'
|
|
4
|
-
import { AlgorithmTypes } from './jwa.ts'
|
|
5
|
-
import type { AcrylJsonWebKey } from './jws.ts'
|
|
6
|
-
import { signing } from './jws.ts'
|
|
7
|
-
import * as JWT from './jwt.ts'
|
|
8
|
-
import { verifyWithJwks } from './jwt.ts'
|
|
9
|
-
import {
|
|
10
|
-
JwtAlgorithmMismatch,
|
|
11
|
-
JwtAlgorithmNotAllowed,
|
|
12
|
-
JwtAlgorithmNotImplemented,
|
|
13
|
-
JwtAlgorithmRequired,
|
|
14
|
-
JwtPayloadRequiresAud,
|
|
15
|
-
JwtSymmetricAlgorithmNotAllowed,
|
|
16
|
-
JwtTokenAudience,
|
|
17
|
-
JwtTokenExpired,
|
|
18
|
-
JwtTokenInvalid,
|
|
19
|
-
JwtTokenIssuedAt,
|
|
20
|
-
JwtTokenIssuer,
|
|
21
|
-
JwtTokenNotBefore,
|
|
22
|
-
JwtTokenSignatureMismatched,
|
|
23
|
-
} from './types.ts'
|
|
24
|
-
import { utf8Encoder } from './utf8.ts'
|
|
25
|
-
|
|
26
|
-
describe('isTokenHeader', () => {
|
|
27
|
-
it('should return true for valid TokenHeader', () => {
|
|
28
|
-
const validTokenHeader: JWT.TokenHeader = {
|
|
29
|
-
alg: AlgorithmTypes.HS256,
|
|
30
|
-
typ: 'JWT',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
expect(JWT.isTokenHeader(validTokenHeader)).toBe(true)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should return false for invalid TokenHeader', () => {
|
|
37
|
-
const invalidTokenHeader = {
|
|
38
|
-
alg: 'invalid',
|
|
39
|
-
typ: 'JWT',
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
expect(JWT.isTokenHeader(invalidTokenHeader)).toBe(false)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('returns true even if the typ field is absent in a TokenHeader', () => {
|
|
46
|
-
const validTokenHeader: JWT.TokenHeader = {
|
|
47
|
-
alg: AlgorithmTypes.HS256,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
expect(JWT.isTokenHeader(validTokenHeader)).toBe(true)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
it('returns false when the typ field is present but empty', () => {
|
|
54
|
-
const invalidTokenHeader = {
|
|
55
|
-
alg: AlgorithmTypes.HS256,
|
|
56
|
-
typ: '',
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
expect(JWT.isTokenHeader(invalidTokenHeader)).toBe(false)
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe('JWT', () => {
|
|
64
|
-
it('JwtAlgorithmNotImplemented', async () => {
|
|
65
|
-
const payload = { message: 'hello world' }
|
|
66
|
-
const secret = 'a-secret'
|
|
67
|
-
const alg = ''
|
|
68
|
-
let tok = ''
|
|
69
|
-
let err: JwtAlgorithmNotImplemented
|
|
70
|
-
try {
|
|
71
|
-
tok = await JWT.sign(payload, secret, alg as AlgorithmTypes)
|
|
72
|
-
} catch (e) {
|
|
73
|
-
err = e as JwtAlgorithmNotImplemented
|
|
74
|
-
}
|
|
75
|
-
expect(tok).toBe('')
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
expect(err).toEqual(new JwtAlgorithmNotImplemented(alg))
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('JwtTokenInvalid', async () => {
|
|
81
|
-
const tok = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ'
|
|
82
|
-
const secret = 'a-secret'
|
|
83
|
-
let err: JwtTokenInvalid
|
|
84
|
-
let authorized
|
|
85
|
-
try {
|
|
86
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
87
|
-
} catch (e) {
|
|
88
|
-
err = e as JwtTokenInvalid
|
|
89
|
-
}
|
|
90
|
-
// @ts-ignore
|
|
91
|
-
expect(err).toEqual(new JwtTokenInvalid(tok))
|
|
92
|
-
expect(authorized).toBeUndefined()
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('JwtTokenInvalid for a signature that is not valid base64url', async () => {
|
|
96
|
-
const tok = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.@@@@'
|
|
97
|
-
const err = await JWT.verify(tok, 'a-secret', AlgorithmTypes.HS256).catch((e: unknown) => e)
|
|
98
|
-
expect(err).toBeInstanceOf(JwtTokenInvalid)
|
|
99
|
-
expect(err).toHaveProperty('message', `invalid JWT token: ${tok}`)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('JwtTokenSignatureMismatched for an empty signature', async () => {
|
|
103
|
-
const tok = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.'
|
|
104
|
-
const err = await JWT.verify(tok, 'a-secret', AlgorithmTypes.HS256).catch((e: unknown) => e)
|
|
105
|
-
expect(err).toBeInstanceOf(JwtTokenSignatureMismatched)
|
|
106
|
-
expect(err).toHaveProperty('message', `token(${tok}) signature mismatched`)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('JwtTokenNotBefore', async () => {
|
|
110
|
-
const tok =
|
|
111
|
-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NjQ2MDYzMzQsImV4cCI6MTY2NDYwOTkzNCwibmJmIjoiMzEwNDYwNjI2NCJ9.hpSDT_cfkxeiLWEpWVT8TDxFP3dFi27q1K7CcMcLXHc'
|
|
112
|
-
const secret = 'a-secret'
|
|
113
|
-
let err: JwtTokenNotBefore
|
|
114
|
-
let authorized
|
|
115
|
-
try {
|
|
116
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
117
|
-
} catch (e) {
|
|
118
|
-
err = e as JwtTokenNotBefore
|
|
119
|
-
}
|
|
120
|
-
// @ts-ignore
|
|
121
|
-
expect(err).toEqual(new JwtTokenNotBefore(tok))
|
|
122
|
-
expect(authorized).toBeUndefined()
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
describe('JwtTokenNotBefore with malformed nbf claim', () => {
|
|
126
|
-
it('rejects token with nbf as a non-numeric string', async () => {
|
|
127
|
-
const secret = 'a-secret'
|
|
128
|
-
const tok = await JWT.sign(
|
|
129
|
-
// @ts-expect-error - testing malformed payload (nbf must be number)
|
|
130
|
-
{ message: 'hello', nbf: 'tomorrow' },
|
|
131
|
-
secret,
|
|
132
|
-
AlgorithmTypes.HS256
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
let err
|
|
136
|
-
let authorized
|
|
137
|
-
try {
|
|
138
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
139
|
-
} catch (e) {
|
|
140
|
-
err = e
|
|
141
|
-
}
|
|
142
|
-
expect(err).toEqual(new JwtTokenNotBefore(tok))
|
|
143
|
-
expect(authorized).toBeUndefined()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('rejects token with nbf = Infinity (parsed from 1e400 in JSON)', async () => {
|
|
147
|
-
// JSON.stringify converts Infinity to null, so hand-craft the payload.
|
|
148
|
-
const secret = 'a-secret'
|
|
149
|
-
const encode = (s: string) => encodeBase64Url(utf8Encoder.encode(s).buffer).replace(/=/g, '')
|
|
150
|
-
const encodedHeader = encode('{"alg":"HS256","typ":"JWT"}')
|
|
151
|
-
const encodedPayload = encode('{"message":"hello","nbf":1e400}')
|
|
152
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
153
|
-
const signatureBuffer = await signing(
|
|
154
|
-
secret,
|
|
155
|
-
AlgorithmTypes.HS256,
|
|
156
|
-
utf8Encoder.encode(signingInput)
|
|
157
|
-
)
|
|
158
|
-
const tok = `${signingInput}.${encodeBase64Url(signatureBuffer).replace(/=/g, '')}`
|
|
159
|
-
|
|
160
|
-
let err
|
|
161
|
-
let authorized
|
|
162
|
-
try {
|
|
163
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
164
|
-
} catch (e) {
|
|
165
|
-
err = e
|
|
166
|
-
}
|
|
167
|
-
expect(err).toEqual(new JwtTokenNotBefore(tok))
|
|
168
|
-
expect(authorized).toBeUndefined()
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
it('JwtTokenExpired', async () => {
|
|
173
|
-
const tok =
|
|
174
|
-
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzMwNDYxMDAsImV4cCI6MTYzMzA0NjQwMH0.H-OI1TWAbmK8RonvcpPaQcNvOKS9sxinEOsgKwjoiVo'
|
|
175
|
-
const secret = 'a-secret'
|
|
176
|
-
let err
|
|
177
|
-
let authorized
|
|
178
|
-
try {
|
|
179
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
180
|
-
} catch (e) {
|
|
181
|
-
err = e
|
|
182
|
-
}
|
|
183
|
-
expect(err).toEqual(new JwtTokenExpired(tok))
|
|
184
|
-
expect(authorized).toBeUndefined()
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('JwtTokenExpired after Y2038', async () => {
|
|
188
|
-
const postY2038 = 2147483648 // 2038-01-19 03:14:08 UTC
|
|
189
|
-
vi.useFakeTimers().setSystemTime(new Date(postY2038 * 1000))
|
|
190
|
-
|
|
191
|
-
const expIn2025 = 1735689600 // 2025-01-01 00:00:00 UTC
|
|
192
|
-
const payload = { message: 'hello', exp: expIn2025 }
|
|
193
|
-
const secret = 'a-secret'
|
|
194
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
195
|
-
|
|
196
|
-
let err
|
|
197
|
-
let authorized
|
|
198
|
-
try {
|
|
199
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
200
|
-
} catch (e) {
|
|
201
|
-
err = e
|
|
202
|
-
}
|
|
203
|
-
expect(err).toEqual(new JwtTokenExpired(tok))
|
|
204
|
-
expect(authorized).toBeUndefined()
|
|
205
|
-
|
|
206
|
-
vi.useRealTimers()
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
describe('JwtTokenExpired with malformed exp claim', () => {
|
|
210
|
-
it('rejects token with exp = 0 (epoch zero)', async () => {
|
|
211
|
-
const secret = 'a-secret'
|
|
212
|
-
const tok = await JWT.sign({ message: 'hello', exp: 0 }, secret, AlgorithmTypes.HS256)
|
|
213
|
-
|
|
214
|
-
let err
|
|
215
|
-
let authorized
|
|
216
|
-
try {
|
|
217
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
218
|
-
} catch (e) {
|
|
219
|
-
err = e
|
|
220
|
-
}
|
|
221
|
-
expect(err).toEqual(new JwtTokenExpired(tok))
|
|
222
|
-
expect(authorized).toBeUndefined()
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('rejects token with exp as a non-numeric string', async () => {
|
|
226
|
-
const secret = 'a-secret'
|
|
227
|
-
const tok = await JWT.sign(
|
|
228
|
-
// @ts-expect-error - testing malformed payload (exp must be number)
|
|
229
|
-
{ message: 'hello', exp: 'tomorrow' },
|
|
230
|
-
secret,
|
|
231
|
-
AlgorithmTypes.HS256
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
let err
|
|
235
|
-
let authorized
|
|
236
|
-
try {
|
|
237
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
238
|
-
} catch (e) {
|
|
239
|
-
err = e
|
|
240
|
-
}
|
|
241
|
-
expect(err).toEqual(new JwtTokenExpired(tok))
|
|
242
|
-
expect(authorized).toBeUndefined()
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('rejects token with exp = Infinity (parsed from 1e400 in JSON)', async () => {
|
|
246
|
-
// JSON.stringify converts Infinity to null, so hand-craft the payload.
|
|
247
|
-
const secret = 'a-secret'
|
|
248
|
-
const encode = (s: string) => encodeBase64Url(utf8Encoder.encode(s).buffer).replace(/=/g, '')
|
|
249
|
-
const encodedHeader = encode('{"alg":"HS256","typ":"JWT"}')
|
|
250
|
-
const encodedPayload = encode('{"message":"hello","exp":1e400}')
|
|
251
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
252
|
-
const signatureBuffer = await signing(
|
|
253
|
-
secret,
|
|
254
|
-
AlgorithmTypes.HS256,
|
|
255
|
-
utf8Encoder.encode(signingInput)
|
|
256
|
-
)
|
|
257
|
-
const tok = `${signingInput}.${encodeBase64Url(signatureBuffer).replace(/=/g, '')}`
|
|
258
|
-
|
|
259
|
-
let err
|
|
260
|
-
let authorized
|
|
261
|
-
try {
|
|
262
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
263
|
-
} catch (e) {
|
|
264
|
-
err = e
|
|
265
|
-
}
|
|
266
|
-
expect(err).toEqual(new JwtTokenExpired(tok))
|
|
267
|
-
expect(authorized).toBeUndefined()
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('JwtTokenIssuedAt', async () => {
|
|
272
|
-
const now = 1633046400
|
|
273
|
-
vi.useFakeTimers().setSystemTime(new Date().setTime(now * 1000))
|
|
274
|
-
|
|
275
|
-
const iat = now + 1000 // after 1s
|
|
276
|
-
const payload = { role: 'api_role', iat }
|
|
277
|
-
const secret = 'a-secret'
|
|
278
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
279
|
-
|
|
280
|
-
let err
|
|
281
|
-
let authorized
|
|
282
|
-
try {
|
|
283
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
284
|
-
} catch (e) {
|
|
285
|
-
err = e
|
|
286
|
-
}
|
|
287
|
-
expect(err).toEqual(new JwtTokenIssuedAt(now, iat))
|
|
288
|
-
expect(authorized).toBeUndefined()
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
describe('JwtTokenIssuedAt with malformed iat claim', () => {
|
|
292
|
-
it('rejects token with iat as a non-numeric string', async () => {
|
|
293
|
-
const now = 1633046400
|
|
294
|
-
vi.useFakeTimers().setSystemTime(new Date(now * 1000))
|
|
295
|
-
|
|
296
|
-
const secret = 'a-secret'
|
|
297
|
-
const tok = await JWT.sign(
|
|
298
|
-
// @ts-expect-error - testing malformed payload (iat must be number)
|
|
299
|
-
{ message: 'hello', iat: 'tomorrow' },
|
|
300
|
-
secret,
|
|
301
|
-
AlgorithmTypes.HS256
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
let err
|
|
305
|
-
let authorized
|
|
306
|
-
try {
|
|
307
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
308
|
-
} catch (e) {
|
|
309
|
-
err = e
|
|
310
|
-
}
|
|
311
|
-
expect(err).toEqual(new JwtTokenIssuedAt(now, 'tomorrow' as unknown as number))
|
|
312
|
-
expect(authorized).toBeUndefined()
|
|
313
|
-
|
|
314
|
-
vi.useRealTimers()
|
|
315
|
-
})
|
|
316
|
-
|
|
317
|
-
it('rejects token with iat = Infinity (parsed from 1e400 in JSON)', async () => {
|
|
318
|
-
// JSON.stringify converts Infinity to null, so hand-craft the payload.
|
|
319
|
-
const now = 1633046400
|
|
320
|
-
vi.useFakeTimers().setSystemTime(new Date(now * 1000))
|
|
321
|
-
|
|
322
|
-
const secret = 'a-secret'
|
|
323
|
-
const encode = (s: string) => encodeBase64Url(utf8Encoder.encode(s).buffer).replace(/=/g, '')
|
|
324
|
-
const encodedHeader = encode('{"alg":"HS256","typ":"JWT"}')
|
|
325
|
-
const encodedPayload = encode('{"message":"hello","iat":1e400}')
|
|
326
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
327
|
-
const signatureBuffer = await signing(
|
|
328
|
-
secret,
|
|
329
|
-
AlgorithmTypes.HS256,
|
|
330
|
-
utf8Encoder.encode(signingInput)
|
|
331
|
-
)
|
|
332
|
-
const tok = `${signingInput}.${encodeBase64Url(signatureBuffer).replace(/=/g, '')}`
|
|
333
|
-
|
|
334
|
-
let err
|
|
335
|
-
let authorized
|
|
336
|
-
try {
|
|
337
|
-
authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
338
|
-
} catch (e) {
|
|
339
|
-
err = e
|
|
340
|
-
}
|
|
341
|
-
expect(err).toEqual(new JwtTokenIssuedAt(now, Infinity))
|
|
342
|
-
expect(authorized).toBeUndefined()
|
|
343
|
-
|
|
344
|
-
vi.useRealTimers()
|
|
345
|
-
})
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
it('JwtTokenIssuer (none)', async () => {
|
|
349
|
-
const tok =
|
|
350
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDB9.Ha3tPZzmnLGyFfZYd7GSV0iCn2F9kbZffFVZcTe5kJo'
|
|
351
|
-
const secret = 'a-secret'
|
|
352
|
-
let err
|
|
353
|
-
let authorized
|
|
354
|
-
try {
|
|
355
|
-
authorized = await JWT.verify(tok, secret, {
|
|
356
|
-
alg: AlgorithmTypes.HS256,
|
|
357
|
-
iss: 'some',
|
|
358
|
-
})
|
|
359
|
-
} catch (e) {
|
|
360
|
-
err = e
|
|
361
|
-
}
|
|
362
|
-
expect(err).toEqual(new JwtTokenIssuer('some', null))
|
|
363
|
-
expect(authorized).toBeUndefined()
|
|
364
|
-
})
|
|
365
|
-
|
|
366
|
-
it('JwtTokenIssuer (wrong - string)', async () => {
|
|
367
|
-
const tok =
|
|
368
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImZha2UtaXNzdWVyIn0.miyPU40DBvhxpAUsndssJOMBsP1aqc8JClGnriPHfXk'
|
|
369
|
-
const secret = 'a-secret'
|
|
370
|
-
let err
|
|
371
|
-
let authorized
|
|
372
|
-
try {
|
|
373
|
-
authorized = await JWT.verify(tok, secret, {
|
|
374
|
-
alg: AlgorithmTypes.HS256,
|
|
375
|
-
iss: 'expected-issuer',
|
|
376
|
-
})
|
|
377
|
-
} catch (e) {
|
|
378
|
-
err = e
|
|
379
|
-
}
|
|
380
|
-
expect(err).toEqual(new JwtTokenIssuer('expected-issuer', 'fake-issuer'))
|
|
381
|
-
expect(authorized).toBeUndefined()
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
it('JwtTokenIssuer (wrong - RegExp)', async () => {
|
|
385
|
-
const tok =
|
|
386
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImhleSJ9.Q0NJwoj-meWy42pFFrPNlREe2lOagWioJUjR4eJCx0k'
|
|
387
|
-
const secret = 'a-secret'
|
|
388
|
-
let err
|
|
389
|
-
let authorized
|
|
390
|
-
try {
|
|
391
|
-
authorized = await JWT.verify(tok, secret, {
|
|
392
|
-
alg: AlgorithmTypes.HS256,
|
|
393
|
-
iss: /^(hello|hi)$/,
|
|
394
|
-
})
|
|
395
|
-
} catch (e) {
|
|
396
|
-
err = e
|
|
397
|
-
}
|
|
398
|
-
expect(err).toEqual(new JwtTokenIssuer(/^(hello|hi)$/, 'hey'))
|
|
399
|
-
expect(authorized).toBeUndefined()
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
it('JwtTokenIssuer (correct - string)', async () => {
|
|
403
|
-
const tok =
|
|
404
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImNvcnJlY3QtaXNzdWVyIn0.gF8S6M2QcfTTscgxeyihNk28JAOa8mfL1bXPb3_E3rk'
|
|
405
|
-
const secret = 'a-secret'
|
|
406
|
-
let err
|
|
407
|
-
let authorized
|
|
408
|
-
try {
|
|
409
|
-
authorized = await JWT.verify(tok, secret, {
|
|
410
|
-
alg: AlgorithmTypes.HS256,
|
|
411
|
-
iss: 'correct-issuer',
|
|
412
|
-
})
|
|
413
|
-
} catch (e) {
|
|
414
|
-
err = e
|
|
415
|
-
}
|
|
416
|
-
expect(err).toBeUndefined()
|
|
417
|
-
expect(authorized?.iss).toEqual('correct-issuer')
|
|
418
|
-
})
|
|
419
|
-
|
|
420
|
-
it('JwtTokenIssuer (correct - RegExp)', async () => {
|
|
421
|
-
const tok =
|
|
422
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImhlbGxvIn0.5DDuValGGQu4EfS3DY7C4hwwHyTNSTD93K_YEjBzgAc'
|
|
423
|
-
const secret = 'a-secret'
|
|
424
|
-
let err
|
|
425
|
-
let authorized
|
|
426
|
-
try {
|
|
427
|
-
authorized = await JWT.verify(tok, secret, {
|
|
428
|
-
alg: AlgorithmTypes.HS256,
|
|
429
|
-
iss: /^(hello|hi)$/,
|
|
430
|
-
})
|
|
431
|
-
} catch (e) {
|
|
432
|
-
err = e
|
|
433
|
-
}
|
|
434
|
-
expect(err).toBeUndefined()
|
|
435
|
-
expect(authorized?.iss).toEqual('hello')
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
it('JwtPayloadRequireAud', async () => {
|
|
439
|
-
const tok =
|
|
440
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiaWF0IjoxfQ.3Yd0dDicCKA6zu_G6AvxMX_fRH5wMz9gMCedOsYNAGc'
|
|
441
|
-
const secret = 'a-secret'
|
|
442
|
-
let err
|
|
443
|
-
let authorized
|
|
444
|
-
try {
|
|
445
|
-
authorized = await JWT.verify(tok, secret, {
|
|
446
|
-
alg: AlgorithmTypes.HS256,
|
|
447
|
-
aud: 'correct-audience',
|
|
448
|
-
})
|
|
449
|
-
} catch (e) {
|
|
450
|
-
err = e
|
|
451
|
-
}
|
|
452
|
-
expect(err).toEqual(new JwtPayloadRequiresAud({ iss: 'https://issuer.example', iat: 1 }))
|
|
453
|
-
expect(authorized).toBeUndefined()
|
|
454
|
-
})
|
|
455
|
-
|
|
456
|
-
it('JwtTokenAudience(correct string - string)', async () => {
|
|
457
|
-
const tok =
|
|
458
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'
|
|
459
|
-
const secret = 'a-secret'
|
|
460
|
-
let err
|
|
461
|
-
let authorized
|
|
462
|
-
try {
|
|
463
|
-
authorized = await JWT.verify(tok, secret, {
|
|
464
|
-
alg: AlgorithmTypes.HS256,
|
|
465
|
-
aud: 'correct-audience',
|
|
466
|
-
})
|
|
467
|
-
} catch (e) {
|
|
468
|
-
err = e
|
|
469
|
-
}
|
|
470
|
-
expect(err).toBeUndefined()
|
|
471
|
-
expect(authorized?.aud).toEqual('correct-audience')
|
|
472
|
-
})
|
|
473
|
-
|
|
474
|
-
it('JwtTokenAudience(correct string - string[])', async () => {
|
|
475
|
-
const tok =
|
|
476
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'
|
|
477
|
-
const secret = 'a-secret'
|
|
478
|
-
let err
|
|
479
|
-
let authorized
|
|
480
|
-
try {
|
|
481
|
-
authorized = await JWT.verify(tok, secret, {
|
|
482
|
-
alg: AlgorithmTypes.HS256,
|
|
483
|
-
aud: ['correct-audience', 'other-audience'],
|
|
484
|
-
})
|
|
485
|
-
} catch (e) {
|
|
486
|
-
err = e
|
|
487
|
-
}
|
|
488
|
-
expect(err).toBeUndefined()
|
|
489
|
-
expect(authorized?.aud).toEqual('correct-audience')
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
it('JwtTokenAudience(correct string - RegExp)', async () => {
|
|
493
|
-
const tok =
|
|
494
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'
|
|
495
|
-
const secret = 'a-secret'
|
|
496
|
-
let err
|
|
497
|
-
let authorized
|
|
498
|
-
try {
|
|
499
|
-
authorized = await JWT.verify(tok, secret, {
|
|
500
|
-
alg: AlgorithmTypes.HS256,
|
|
501
|
-
aud: /^correct-audience$/,
|
|
502
|
-
})
|
|
503
|
-
} catch (e) {
|
|
504
|
-
err = e
|
|
505
|
-
}
|
|
506
|
-
expect(err).toBeUndefined()
|
|
507
|
-
expect(authorized?.aud).toEqual('correct-audience')
|
|
508
|
-
})
|
|
509
|
-
|
|
510
|
-
it('JwtTokenAudience(correct string[] - string)', async () => {
|
|
511
|
-
const tok =
|
|
512
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'
|
|
513
|
-
const secret = 'a-secret'
|
|
514
|
-
let err
|
|
515
|
-
let authorized
|
|
516
|
-
try {
|
|
517
|
-
authorized = await JWT.verify(tok, secret, {
|
|
518
|
-
alg: AlgorithmTypes.HS256,
|
|
519
|
-
aud: 'correct-audience',
|
|
520
|
-
})
|
|
521
|
-
} catch (e) {
|
|
522
|
-
err = e
|
|
523
|
-
}
|
|
524
|
-
expect(err).toBeUndefined()
|
|
525
|
-
expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])
|
|
526
|
-
})
|
|
527
|
-
|
|
528
|
-
it('JwtTokenAudience(correct string[] - string[])', async () => {
|
|
529
|
-
const tok =
|
|
530
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'
|
|
531
|
-
const secret = 'a-secret'
|
|
532
|
-
let err
|
|
533
|
-
let authorized
|
|
534
|
-
try {
|
|
535
|
-
authorized = await JWT.verify(tok, secret, {
|
|
536
|
-
alg: AlgorithmTypes.HS256,
|
|
537
|
-
aud: ['correct-audience', 'test'],
|
|
538
|
-
})
|
|
539
|
-
} catch (e) {
|
|
540
|
-
err = e
|
|
541
|
-
}
|
|
542
|
-
expect(err).toBeUndefined()
|
|
543
|
-
expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])
|
|
544
|
-
})
|
|
545
|
-
|
|
546
|
-
it('JwtTokenAudience(correct string[] - RegExp)', async () => {
|
|
547
|
-
const tok =
|
|
548
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'
|
|
549
|
-
const secret = 'a-secret'
|
|
550
|
-
let err
|
|
551
|
-
let authorized
|
|
552
|
-
try {
|
|
553
|
-
authorized = await JWT.verify(tok, secret, {
|
|
554
|
-
alg: AlgorithmTypes.HS256,
|
|
555
|
-
aud: /^correct-audience$/,
|
|
556
|
-
})
|
|
557
|
-
} catch (e) {
|
|
558
|
-
err = e
|
|
559
|
-
}
|
|
560
|
-
expect(err).toBeUndefined()
|
|
561
|
-
expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])
|
|
562
|
-
})
|
|
563
|
-
|
|
564
|
-
it('JwtTokenAudience(wrong string - string)', async () => {
|
|
565
|
-
const tok =
|
|
566
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'
|
|
567
|
-
const secret = 'a-secret'
|
|
568
|
-
let err
|
|
569
|
-
let authorized
|
|
570
|
-
try {
|
|
571
|
-
authorized = await JWT.verify(tok, secret, {
|
|
572
|
-
alg: AlgorithmTypes.HS256,
|
|
573
|
-
aud: 'correct-audience',
|
|
574
|
-
})
|
|
575
|
-
} catch (e) {
|
|
576
|
-
err = e
|
|
577
|
-
}
|
|
578
|
-
expect(err).toEqual(new JwtTokenAudience('correct-audience', 'wrong-audience'))
|
|
579
|
-
expect(authorized).toBeUndefined()
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
it('JwtTokenAudience(wrong string - string[])', async () => {
|
|
583
|
-
const tok =
|
|
584
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'
|
|
585
|
-
const secret = 'a-secret'
|
|
586
|
-
let err
|
|
587
|
-
let authorized
|
|
588
|
-
try {
|
|
589
|
-
authorized = await JWT.verify(tok, secret, {
|
|
590
|
-
alg: AlgorithmTypes.HS256,
|
|
591
|
-
aud: ['correct-audience', 'other-audience'],
|
|
592
|
-
})
|
|
593
|
-
} catch (e) {
|
|
594
|
-
err = e
|
|
595
|
-
}
|
|
596
|
-
expect(err).toEqual(
|
|
597
|
-
new JwtTokenAudience(['correct-audience', 'other-audience'], 'wrong-audience')
|
|
598
|
-
)
|
|
599
|
-
expect(authorized).toBeUndefined()
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
it('JwtTokenAudience(wrong string - RegExp)', async () => {
|
|
603
|
-
const tok =
|
|
604
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'
|
|
605
|
-
const secret = 'a-secret'
|
|
606
|
-
let err
|
|
607
|
-
let authorized
|
|
608
|
-
try {
|
|
609
|
-
authorized = await JWT.verify(tok, secret, {
|
|
610
|
-
alg: AlgorithmTypes.HS256,
|
|
611
|
-
aud: /^correct-audience$/,
|
|
612
|
-
})
|
|
613
|
-
} catch (e) {
|
|
614
|
-
err = e
|
|
615
|
-
}
|
|
616
|
-
expect(err).toEqual(new JwtTokenAudience(/^correct-audience$/, 'wrong-audience'))
|
|
617
|
-
expect(authorized).toBeUndefined()
|
|
618
|
-
})
|
|
619
|
-
|
|
620
|
-
it('JwtTokenAudience(wrong string[] - string)', async () => {
|
|
621
|
-
const tok =
|
|
622
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'
|
|
623
|
-
const secret = 'a-secret'
|
|
624
|
-
let err
|
|
625
|
-
let authorized
|
|
626
|
-
try {
|
|
627
|
-
authorized = await JWT.verify(tok, secret, {
|
|
628
|
-
alg: AlgorithmTypes.HS256,
|
|
629
|
-
aud: 'correct-audience',
|
|
630
|
-
})
|
|
631
|
-
} catch (e) {
|
|
632
|
-
err = e
|
|
633
|
-
}
|
|
634
|
-
expect(err).toEqual(
|
|
635
|
-
new JwtTokenAudience('correct-audience', ['wrong-audience', 'other-audience'])
|
|
636
|
-
)
|
|
637
|
-
expect(authorized).toBeUndefined()
|
|
638
|
-
})
|
|
639
|
-
|
|
640
|
-
it('JwtTokenAudience(wrong string[] - string[])', async () => {
|
|
641
|
-
const tok =
|
|
642
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'
|
|
643
|
-
const secret = 'a-secret'
|
|
644
|
-
let err
|
|
645
|
-
let authorized
|
|
646
|
-
try {
|
|
647
|
-
authorized = await JWT.verify(tok, secret, {
|
|
648
|
-
alg: AlgorithmTypes.HS256,
|
|
649
|
-
aud: ['correct-audience', 'test'],
|
|
650
|
-
})
|
|
651
|
-
} catch (e) {
|
|
652
|
-
err = e
|
|
653
|
-
}
|
|
654
|
-
expect(err).toEqual(
|
|
655
|
-
new JwtTokenAudience(['correct-audience', 'test'], ['wrong-audience', 'other-audience'])
|
|
656
|
-
)
|
|
657
|
-
expect(authorized).toBeUndefined()
|
|
658
|
-
})
|
|
659
|
-
|
|
660
|
-
it('JwtTokenAudience(wrong string[] - RegExp)', async () => {
|
|
661
|
-
const tok =
|
|
662
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'
|
|
663
|
-
const secret = 'a-secret'
|
|
664
|
-
let err
|
|
665
|
-
let authorized
|
|
666
|
-
try {
|
|
667
|
-
authorized = await JWT.verify(tok, secret, {
|
|
668
|
-
alg: AlgorithmTypes.HS256,
|
|
669
|
-
aud: /^correct-audience$/,
|
|
670
|
-
})
|
|
671
|
-
} catch (e) {
|
|
672
|
-
err = e
|
|
673
|
-
}
|
|
674
|
-
expect(err).toEqual(
|
|
675
|
-
new JwtTokenAudience(/^correct-audience$/, ['wrong-audience', 'other-audience'])
|
|
676
|
-
)
|
|
677
|
-
expect(authorized).toBeUndefined()
|
|
678
|
-
})
|
|
679
|
-
|
|
680
|
-
it('JwtTokenAudience (no aud option and wrong aud in payload)', async () => {
|
|
681
|
-
const tok =
|
|
682
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'
|
|
683
|
-
const secret = 'a-secret'
|
|
684
|
-
let err
|
|
685
|
-
let authorized
|
|
686
|
-
try {
|
|
687
|
-
authorized = await JWT.verify(tok, secret, {
|
|
688
|
-
alg: AlgorithmTypes.HS256,
|
|
689
|
-
})
|
|
690
|
-
} catch (e) {
|
|
691
|
-
err = e
|
|
692
|
-
}
|
|
693
|
-
expect(err).toBeUndefined()
|
|
694
|
-
expect(authorized?.aud).toEqual('wrong-audience')
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
it('HS256 sign & verify & decode', async () => {
|
|
698
|
-
const payload = { message: 'hello world' }
|
|
699
|
-
const secret = 'a-secret'
|
|
700
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
701
|
-
const expected =
|
|
702
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
|
|
703
|
-
expect(tok).toEqual(expected)
|
|
704
|
-
|
|
705
|
-
const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
706
|
-
expect(verifiedPayload).not.toBeUndefined()
|
|
707
|
-
expect(verifiedPayload).toEqual(payload)
|
|
708
|
-
|
|
709
|
-
expect(JWT.decode(tok)).toEqual({
|
|
710
|
-
header: {
|
|
711
|
-
alg: 'HS256',
|
|
712
|
-
typ: 'JWT',
|
|
713
|
-
},
|
|
714
|
-
payload: {
|
|
715
|
-
message: 'hello world',
|
|
716
|
-
},
|
|
717
|
-
})
|
|
718
|
-
})
|
|
719
|
-
|
|
720
|
-
it('HS256 sign & verify', async () => {
|
|
721
|
-
const payload = { message: 'hello world' }
|
|
722
|
-
const secret = 'a-secret'
|
|
723
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
724
|
-
const expected =
|
|
725
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'
|
|
726
|
-
expect(tok).toEqual(expected)
|
|
727
|
-
|
|
728
|
-
let err = null
|
|
729
|
-
let authorized
|
|
730
|
-
try {
|
|
731
|
-
authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS256)
|
|
732
|
-
} catch (e) {
|
|
733
|
-
err = e
|
|
734
|
-
}
|
|
735
|
-
expect(authorized).toBeUndefined()
|
|
736
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
737
|
-
})
|
|
738
|
-
|
|
739
|
-
it('HS512 sign & verify & decode', async () => {
|
|
740
|
-
const payload = { message: 'hello world' }
|
|
741
|
-
const secret = 'a-secret'
|
|
742
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)
|
|
743
|
-
const expected =
|
|
744
|
-
'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.RqVLgExB_GXF1-9T-k4V4HjFmiuQKTEjVSiZd-YL0WERIlywZ7PfzAuTZSJU4gg8cscGamQa030cieEWrYcywg'
|
|
745
|
-
expect(tok).toEqual(expected)
|
|
746
|
-
|
|
747
|
-
const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS512)
|
|
748
|
-
expect(verifiedPayload).not.toBeUndefined()
|
|
749
|
-
expect(verifiedPayload).toEqual(payload)
|
|
750
|
-
|
|
751
|
-
expect(JWT.decode(tok)).toEqual({
|
|
752
|
-
header: {
|
|
753
|
-
alg: 'HS512',
|
|
754
|
-
typ: 'JWT',
|
|
755
|
-
},
|
|
756
|
-
payload: {
|
|
757
|
-
message: 'hello world',
|
|
758
|
-
},
|
|
759
|
-
})
|
|
760
|
-
})
|
|
761
|
-
|
|
762
|
-
it('HS512 sign & verify', async () => {
|
|
763
|
-
const payload = { message: 'hello world' }
|
|
764
|
-
const secret = 'a-secret'
|
|
765
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)
|
|
766
|
-
const expected =
|
|
767
|
-
'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.RqVLgExB_GXF1-9T-k4V4HjFmiuQKTEjVSiZd-YL0WERIlywZ7PfzAuTZSJU4gg8cscGamQa030cieEWrYcywg'
|
|
768
|
-
expect(tok).toEqual(expected)
|
|
769
|
-
|
|
770
|
-
let err = null
|
|
771
|
-
let authorized
|
|
772
|
-
try {
|
|
773
|
-
authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS512)
|
|
774
|
-
} catch (e) {
|
|
775
|
-
err = e
|
|
776
|
-
}
|
|
777
|
-
expect(authorized).toBeUndefined()
|
|
778
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
779
|
-
})
|
|
780
|
-
|
|
781
|
-
it('HS384 sign & verify', async () => {
|
|
782
|
-
const payload = { message: 'hello world' }
|
|
783
|
-
const secret = 'a-secret%你好'
|
|
784
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS384)
|
|
785
|
-
const expected =
|
|
786
|
-
'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.C1Br1183Oy6O7th4NDCOaI9WB75i3FMCuYlv1tCL9HggsU89T-SNutghwhJykD3r'
|
|
787
|
-
expect(tok).toEqual(expected)
|
|
788
|
-
|
|
789
|
-
let err = null
|
|
790
|
-
let authorized
|
|
791
|
-
try {
|
|
792
|
-
authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS384)
|
|
793
|
-
} catch (e) {
|
|
794
|
-
err = e
|
|
795
|
-
}
|
|
796
|
-
expect(authorized).toBeUndefined()
|
|
797
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
798
|
-
})
|
|
799
|
-
|
|
800
|
-
it('sign & verify & decode with a custom secret', async () => {
|
|
801
|
-
const payload = { message: 'hello world' }
|
|
802
|
-
const algorithm = {
|
|
803
|
-
name: 'HMAC',
|
|
804
|
-
hash: {
|
|
805
|
-
name: 'SHA-256',
|
|
806
|
-
},
|
|
807
|
-
}
|
|
808
|
-
const secret = await crypto.subtle.importKey(
|
|
809
|
-
'raw',
|
|
810
|
-
Buffer.from('cefb73234d5fae4bf27662900732b52943e8d53e871fe0f353da95de4599c21d', 'hex'),
|
|
811
|
-
algorithm,
|
|
812
|
-
false,
|
|
813
|
-
['sign', 'verify']
|
|
814
|
-
)
|
|
815
|
-
const tok = await JWT.sign(payload, secret)
|
|
816
|
-
const expected =
|
|
817
|
-
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.qunGhchNXH_unqWXN6hB0Elhzr5SykSXVhklLti1aFI'
|
|
818
|
-
expect(tok).toEqual(expected)
|
|
819
|
-
|
|
820
|
-
const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
821
|
-
expect(verifiedPayload).not.toBeUndefined()
|
|
822
|
-
expect(verifiedPayload).toEqual(payload)
|
|
823
|
-
|
|
824
|
-
const invalidSecret = await crypto.subtle.importKey(
|
|
825
|
-
'raw',
|
|
826
|
-
Buffer.from('cefb73234d5fae4bf27662900732b52943e8d53e871fe0f353da95de41111111', 'hex'),
|
|
827
|
-
algorithm,
|
|
828
|
-
false,
|
|
829
|
-
['sign', 'verify']
|
|
830
|
-
)
|
|
831
|
-
let err = null
|
|
832
|
-
let authorized
|
|
833
|
-
try {
|
|
834
|
-
authorized = await JWT.verify(tok, invalidSecret, AlgorithmTypes.HS256)
|
|
835
|
-
} catch (e) {
|
|
836
|
-
err = e
|
|
837
|
-
}
|
|
838
|
-
expect(authorized).toBeUndefined()
|
|
839
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
840
|
-
})
|
|
841
|
-
|
|
842
|
-
const rsTestCases = [
|
|
843
|
-
{
|
|
844
|
-
alg: AlgorithmTypes.RS256,
|
|
845
|
-
hash: 'SHA-256',
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
alg: AlgorithmTypes.RS384,
|
|
849
|
-
hash: 'SHA-384',
|
|
850
|
-
},
|
|
851
|
-
{
|
|
852
|
-
alg: AlgorithmTypes.RS512,
|
|
853
|
-
hash: 'SHA-512',
|
|
854
|
-
},
|
|
855
|
-
]
|
|
856
|
-
for (const tc of rsTestCases) {
|
|
857
|
-
it(`${tc.alg} sign & verify`, async () => {
|
|
858
|
-
const alg = tc.alg
|
|
859
|
-
const payload = { message: 'hello world' }
|
|
860
|
-
const keyPair = await generateRSAKey(tc.hash)
|
|
861
|
-
const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)
|
|
862
|
-
const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)
|
|
863
|
-
const jwkPublicKey = await exportJWK(keyPair.publicKey)
|
|
864
|
-
|
|
865
|
-
const tok = await JWT.sign(payload, pemPrivateKey, alg)
|
|
866
|
-
expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)
|
|
867
|
-
expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)
|
|
868
|
-
expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)
|
|
869
|
-
|
|
870
|
-
const keyPair2 = await generateRSAKey(tc.hash)
|
|
871
|
-
const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)
|
|
872
|
-
|
|
873
|
-
let err = null
|
|
874
|
-
let authorized
|
|
875
|
-
try {
|
|
876
|
-
authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)
|
|
877
|
-
} catch (e) {
|
|
878
|
-
err = e
|
|
879
|
-
}
|
|
880
|
-
expect(authorized).toBeUndefined()
|
|
881
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
882
|
-
})
|
|
883
|
-
|
|
884
|
-
it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {
|
|
885
|
-
const alg = tc.alg
|
|
886
|
-
const payload = { message: 'hello world' }
|
|
887
|
-
const keyPair = await generateRSAKey(tc.hash)
|
|
888
|
-
|
|
889
|
-
const tok = await JWT.sign(payload, keyPair.privateKey, alg)
|
|
890
|
-
expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)
|
|
891
|
-
expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)
|
|
892
|
-
})
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const psTestCases = [
|
|
896
|
-
{
|
|
897
|
-
alg: AlgorithmTypes.PS256,
|
|
898
|
-
hash: 'SHA-256',
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
alg: AlgorithmTypes.PS384,
|
|
902
|
-
hash: 'SHA-384',
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
alg: AlgorithmTypes.PS512,
|
|
906
|
-
hash: 'SHA-512',
|
|
907
|
-
},
|
|
908
|
-
]
|
|
909
|
-
for (const tc of psTestCases) {
|
|
910
|
-
it(`${tc.alg} sign & verify`, async () => {
|
|
911
|
-
const alg = tc.alg
|
|
912
|
-
const payload = { message: 'hello world' }
|
|
913
|
-
const keyPair = await generateRSAPSSKey(tc.hash)
|
|
914
|
-
const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)
|
|
915
|
-
const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)
|
|
916
|
-
const jwkPublicKey = await exportJWK(keyPair.publicKey)
|
|
917
|
-
|
|
918
|
-
const tok = await JWT.sign(payload, pemPrivateKey, alg)
|
|
919
|
-
expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)
|
|
920
|
-
expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)
|
|
921
|
-
expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)
|
|
922
|
-
|
|
923
|
-
const keyPair2 = await generateRSAPSSKey(tc.hash)
|
|
924
|
-
const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)
|
|
925
|
-
|
|
926
|
-
let err = null
|
|
927
|
-
let authorized
|
|
928
|
-
try {
|
|
929
|
-
authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)
|
|
930
|
-
} catch (e) {
|
|
931
|
-
err = e
|
|
932
|
-
}
|
|
933
|
-
expect(authorized).toBeUndefined()
|
|
934
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
935
|
-
})
|
|
936
|
-
|
|
937
|
-
it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {
|
|
938
|
-
const alg = tc.alg
|
|
939
|
-
const payload = { message: 'hello world' }
|
|
940
|
-
const keyPair = await generateRSAPSSKey(tc.hash)
|
|
941
|
-
|
|
942
|
-
const tok = await JWT.sign(payload, keyPair.privateKey, alg)
|
|
943
|
-
expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)
|
|
944
|
-
expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)
|
|
945
|
-
})
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const esTestCases = [
|
|
949
|
-
{
|
|
950
|
-
alg: AlgorithmTypes.ES256,
|
|
951
|
-
namedCurve: 'P-256',
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
alg: AlgorithmTypes.ES384,
|
|
955
|
-
namedCurve: 'P-384',
|
|
956
|
-
},
|
|
957
|
-
{
|
|
958
|
-
alg: AlgorithmTypes.ES512,
|
|
959
|
-
namedCurve: 'P-521',
|
|
960
|
-
},
|
|
961
|
-
]
|
|
962
|
-
for (const tc of esTestCases) {
|
|
963
|
-
it(`${tc.alg} sign & verify`, async () => {
|
|
964
|
-
const alg = tc.alg
|
|
965
|
-
const payload = { message: 'hello world' }
|
|
966
|
-
const keyPair = await generateECDSAKey(tc.namedCurve)
|
|
967
|
-
const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)
|
|
968
|
-
const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)
|
|
969
|
-
const jwkPublicKey = await exportJWK(keyPair.publicKey)
|
|
970
|
-
|
|
971
|
-
const tok = await JWT.sign(payload, pemPrivateKey, alg)
|
|
972
|
-
expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)
|
|
973
|
-
expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)
|
|
974
|
-
expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)
|
|
975
|
-
|
|
976
|
-
const keyPair2 = await generateECDSAKey(tc.namedCurve)
|
|
977
|
-
const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)
|
|
978
|
-
|
|
979
|
-
let err = null
|
|
980
|
-
let authorized
|
|
981
|
-
try {
|
|
982
|
-
authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)
|
|
983
|
-
} catch (e) {
|
|
984
|
-
err = e
|
|
985
|
-
}
|
|
986
|
-
expect(authorized).toBeUndefined()
|
|
987
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
988
|
-
})
|
|
989
|
-
|
|
990
|
-
it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {
|
|
991
|
-
const alg = tc.alg
|
|
992
|
-
const payload = { message: 'hello world' }
|
|
993
|
-
const keyPair = await generateECDSAKey(tc.namedCurve)
|
|
994
|
-
|
|
995
|
-
const tok = await JWT.sign(payload, keyPair.privateKey, alg)
|
|
996
|
-
expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)
|
|
997
|
-
expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)
|
|
998
|
-
})
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
it('EdDSA sign & verify', async () => {
|
|
1002
|
-
const alg = 'EdDSA'
|
|
1003
|
-
const payload = { message: 'hello world' }
|
|
1004
|
-
const keyPair = await generateEd25519Key()
|
|
1005
|
-
const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)
|
|
1006
|
-
const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)
|
|
1007
|
-
const jwkPublicKey = await exportJWK(keyPair.publicKey)
|
|
1008
|
-
|
|
1009
|
-
const tok = await JWT.sign(payload, pemPrivateKey, alg)
|
|
1010
|
-
expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)
|
|
1011
|
-
expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)
|
|
1012
|
-
expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)
|
|
1013
|
-
|
|
1014
|
-
const keyPair2 = await generateEd25519Key()
|
|
1015
|
-
const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)
|
|
1016
|
-
|
|
1017
|
-
let err = null
|
|
1018
|
-
let authorized
|
|
1019
|
-
try {
|
|
1020
|
-
authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)
|
|
1021
|
-
} catch (e) {
|
|
1022
|
-
err = e
|
|
1023
|
-
}
|
|
1024
|
-
expect(authorized).toBeUndefined()
|
|
1025
|
-
expect(err instanceof JwtTokenSignatureMismatched).toBe(true)
|
|
1026
|
-
})
|
|
1027
|
-
|
|
1028
|
-
it('EdDSA sign & verify w/ CryptoKey', async () => {
|
|
1029
|
-
const alg = 'EdDSA'
|
|
1030
|
-
const payload = { message: 'hello world' }
|
|
1031
|
-
const keyPair = await generateEd25519Key()
|
|
1032
|
-
|
|
1033
|
-
const tok = await JWT.sign(payload, keyPair.privateKey, alg)
|
|
1034
|
-
expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)
|
|
1035
|
-
expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)
|
|
1036
|
-
})
|
|
1037
|
-
})
|
|
1038
|
-
|
|
1039
|
-
describe('verifyWithJwks header.alg fallback', () => {
|
|
1040
|
-
it('Should use header.alg as fallback when matchingKey.alg is missing', async () => {
|
|
1041
|
-
// Setup: Create a JWT signed with RS256 (asymmetric algorithm)
|
|
1042
|
-
const payload = { message: 'hello world' }
|
|
1043
|
-
const headerAlg = 'RS256'
|
|
1044
|
-
const kid = 'dummy'
|
|
1045
|
-
|
|
1046
|
-
// Generate RSA key pair
|
|
1047
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1048
|
-
{
|
|
1049
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1050
|
-
modulusLength: 2048,
|
|
1051
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1052
|
-
hash: 'SHA-256',
|
|
1053
|
-
},
|
|
1054
|
-
true,
|
|
1055
|
-
['sign', 'verify']
|
|
1056
|
-
)
|
|
1057
|
-
|
|
1058
|
-
// Create JWT (signed with RS256)
|
|
1059
|
-
const header = { alg: headerAlg, typ: 'JWT', kid }
|
|
1060
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1061
|
-
const encodedHeader = encode(header)
|
|
1062
|
-
const encodedPayload = encode(payload)
|
|
1063
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1064
|
-
|
|
1065
|
-
// Sign with private key
|
|
1066
|
-
const signatureBuffer = await signing(
|
|
1067
|
-
keyPair.privateKey,
|
|
1068
|
-
headerAlg,
|
|
1069
|
-
utf8Encoder.encode(signingInput)
|
|
1070
|
-
)
|
|
1071
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1072
|
-
|
|
1073
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1074
|
-
|
|
1075
|
-
// Export public key as JWK without alg property
|
|
1076
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1077
|
-
const keyWithoutAlg = {
|
|
1078
|
-
...jwk,
|
|
1079
|
-
kid,
|
|
1080
|
-
use: 'sig',
|
|
1081
|
-
}
|
|
1082
|
-
delete keyWithoutAlg.alg // intentionally omit alg
|
|
1083
|
-
|
|
1084
|
-
const keys = [keyWithoutAlg]
|
|
1085
|
-
|
|
1086
|
-
// Execute: Verify the JWT token signed with RS256 with allowedAlgorithms required
|
|
1087
|
-
const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })
|
|
1088
|
-
|
|
1089
|
-
// If verification succeeds, it means header.alg was used
|
|
1090
|
-
expect(result).toEqual(payload)
|
|
1091
|
-
})
|
|
1092
|
-
})
|
|
1093
|
-
|
|
1094
|
-
describe('verifyWithJwks security', () => {
|
|
1095
|
-
it('Should reject symmetric algorithm HS256', async () => {
|
|
1096
|
-
const payload = { message: 'hello world' }
|
|
1097
|
-
const secret = 'secret'
|
|
1098
|
-
const kid = 'dummy'
|
|
1099
|
-
|
|
1100
|
-
// Create JWT (signed with HS256)
|
|
1101
|
-
const header = { alg: 'HS256', typ: 'JWT', kid }
|
|
1102
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1103
|
-
const encodedHeader = encode(header)
|
|
1104
|
-
const encodedPayload = encode(payload)
|
|
1105
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1106
|
-
|
|
1107
|
-
const signatureBuffer = await signing(secret, 'HS256', utf8Encoder.encode(signingInput))
|
|
1108
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1109
|
-
|
|
1110
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1111
|
-
|
|
1112
|
-
const keys = [
|
|
1113
|
-
{
|
|
1114
|
-
kty: 'oct',
|
|
1115
|
-
kid,
|
|
1116
|
-
k: encodeBase64Url(utf8Encoder.encode(secret).buffer),
|
|
1117
|
-
use: 'sig',
|
|
1118
|
-
alg: 'HS256',
|
|
1119
|
-
},
|
|
1120
|
-
]
|
|
1121
|
-
|
|
1122
|
-
// HS256 is rejected before allowedAlgorithms check (symmetric algorithm rejection)
|
|
1123
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(
|
|
1124
|
-
JwtSymmetricAlgorithmNotAllowed
|
|
1125
|
-
)
|
|
1126
|
-
})
|
|
1127
|
-
|
|
1128
|
-
it('Should reject symmetric algorithm HS384', async () => {
|
|
1129
|
-
const payload = { message: 'hello world' }
|
|
1130
|
-
const secret = 'secret'
|
|
1131
|
-
const kid = 'dummy'
|
|
1132
|
-
|
|
1133
|
-
const header = { alg: 'HS384', typ: 'JWT', kid }
|
|
1134
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1135
|
-
const encodedHeader = encode(header)
|
|
1136
|
-
const encodedPayload = encode(payload)
|
|
1137
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1138
|
-
|
|
1139
|
-
const signatureBuffer = await signing(secret, 'HS384', utf8Encoder.encode(signingInput))
|
|
1140
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1141
|
-
|
|
1142
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1143
|
-
|
|
1144
|
-
const keys = [
|
|
1145
|
-
{
|
|
1146
|
-
kty: 'oct',
|
|
1147
|
-
kid,
|
|
1148
|
-
k: encodeBase64Url(utf8Encoder.encode(secret).buffer),
|
|
1149
|
-
use: 'sig',
|
|
1150
|
-
alg: 'HS384',
|
|
1151
|
-
},
|
|
1152
|
-
]
|
|
1153
|
-
|
|
1154
|
-
// HS384 is rejected before allowedAlgorithms check (symmetric algorithm rejection)
|
|
1155
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(
|
|
1156
|
-
JwtSymmetricAlgorithmNotAllowed
|
|
1157
|
-
)
|
|
1158
|
-
})
|
|
1159
|
-
|
|
1160
|
-
it('Should reject symmetric algorithm HS512', async () => {
|
|
1161
|
-
const payload = { message: 'hello world' }
|
|
1162
|
-
const secret = 'secret'
|
|
1163
|
-
const kid = 'dummy'
|
|
1164
|
-
|
|
1165
|
-
const header = { alg: 'HS512', typ: 'JWT', kid }
|
|
1166
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1167
|
-
const encodedHeader = encode(header)
|
|
1168
|
-
const encodedPayload = encode(payload)
|
|
1169
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1170
|
-
|
|
1171
|
-
const signatureBuffer = await signing(secret, 'HS512', utf8Encoder.encode(signingInput))
|
|
1172
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1173
|
-
|
|
1174
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1175
|
-
|
|
1176
|
-
const keys = [
|
|
1177
|
-
{
|
|
1178
|
-
kty: 'oct',
|
|
1179
|
-
kid,
|
|
1180
|
-
k: encodeBase64Url(utf8Encoder.encode(secret).buffer),
|
|
1181
|
-
use: 'sig',
|
|
1182
|
-
alg: 'HS512',
|
|
1183
|
-
},
|
|
1184
|
-
]
|
|
1185
|
-
|
|
1186
|
-
// HS512 is rejected before allowedAlgorithms check (symmetric algorithm rejection)
|
|
1187
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(
|
|
1188
|
-
JwtSymmetricAlgorithmNotAllowed
|
|
1189
|
-
)
|
|
1190
|
-
})
|
|
1191
|
-
|
|
1192
|
-
it('Should reject algorithm mismatch between JWK and JWT header', async () => {
|
|
1193
|
-
const payload = { message: 'hello world' }
|
|
1194
|
-
const kid = 'dummy'
|
|
1195
|
-
|
|
1196
|
-
// Generate RS256 key pair
|
|
1197
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1198
|
-
{
|
|
1199
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1200
|
-
modulusLength: 2048,
|
|
1201
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1202
|
-
hash: 'SHA-256',
|
|
1203
|
-
},
|
|
1204
|
-
true,
|
|
1205
|
-
['sign', 'verify']
|
|
1206
|
-
)
|
|
1207
|
-
|
|
1208
|
-
// Create JWT with RS384 in header (mismatch with RS256 key)
|
|
1209
|
-
const header = { alg: 'RS384', typ: 'JWT', kid }
|
|
1210
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1211
|
-
const encodedHeader = encode(header)
|
|
1212
|
-
const encodedPayload = encode(payload)
|
|
1213
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1214
|
-
|
|
1215
|
-
// Sign with RS256 key (but header says RS384)
|
|
1216
|
-
const signatureBuffer = await signing(
|
|
1217
|
-
keyPair.privateKey,
|
|
1218
|
-
'RS256',
|
|
1219
|
-
utf8Encoder.encode(signingInput)
|
|
1220
|
-
)
|
|
1221
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1222
|
-
|
|
1223
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1224
|
-
|
|
1225
|
-
// Export public key as JWK with RS256 alg
|
|
1226
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1227
|
-
const keyWithAlg = {
|
|
1228
|
-
...jwk,
|
|
1229
|
-
kid,
|
|
1230
|
-
use: 'sig',
|
|
1231
|
-
alg: 'RS256', // JWK says RS256, but header says RS384
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
const keys = [keyWithAlg]
|
|
1235
|
-
|
|
1236
|
-
// RS384 in header doesn't match RS256 in JWK alg field
|
|
1237
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS384'] })).rejects.toThrow(
|
|
1238
|
-
JwtAlgorithmMismatch
|
|
1239
|
-
)
|
|
1240
|
-
})
|
|
1241
|
-
|
|
1242
|
-
it('Should allow asymmetric algorithm RS256 with matching alg', async () => {
|
|
1243
|
-
const payload = { message: 'hello world' }
|
|
1244
|
-
const kid = 'dummy'
|
|
1245
|
-
|
|
1246
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1247
|
-
{
|
|
1248
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1249
|
-
modulusLength: 2048,
|
|
1250
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1251
|
-
hash: 'SHA-256',
|
|
1252
|
-
},
|
|
1253
|
-
true,
|
|
1254
|
-
['sign', 'verify']
|
|
1255
|
-
)
|
|
1256
|
-
|
|
1257
|
-
const header = { alg: 'RS256', typ: 'JWT', kid }
|
|
1258
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1259
|
-
const encodedHeader = encode(header)
|
|
1260
|
-
const encodedPayload = encode(payload)
|
|
1261
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1262
|
-
|
|
1263
|
-
const signatureBuffer = await signing(
|
|
1264
|
-
keyPair.privateKey,
|
|
1265
|
-
'RS256',
|
|
1266
|
-
utf8Encoder.encode(signingInput)
|
|
1267
|
-
)
|
|
1268
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1269
|
-
|
|
1270
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1271
|
-
|
|
1272
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1273
|
-
const keyWithAlg = {
|
|
1274
|
-
...jwk,
|
|
1275
|
-
kid,
|
|
1276
|
-
use: 'sig',
|
|
1277
|
-
alg: 'RS256',
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
const keys = [keyWithAlg]
|
|
1281
|
-
|
|
1282
|
-
const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })
|
|
1283
|
-
expect(result).toEqual(payload)
|
|
1284
|
-
})
|
|
1285
|
-
|
|
1286
|
-
it('Should reject algorithm confusion attack (HS256 with RSA public key)', async () => {
|
|
1287
|
-
// This test simulates the algorithm confusion attack where an attacker
|
|
1288
|
-
// tries to use HS256 with a public RSA key as the HMAC secret
|
|
1289
|
-
const payload = { message: 'hello world' }
|
|
1290
|
-
const kid = 'dummy'
|
|
1291
|
-
|
|
1292
|
-
// Generate RSA key pair (normally used for RS256)
|
|
1293
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1294
|
-
{
|
|
1295
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1296
|
-
modulusLength: 2048,
|
|
1297
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1298
|
-
hash: 'SHA-256',
|
|
1299
|
-
},
|
|
1300
|
-
true,
|
|
1301
|
-
['sign', 'verify']
|
|
1302
|
-
)
|
|
1303
|
-
|
|
1304
|
-
// Attacker creates a JWT with HS256 in header
|
|
1305
|
-
const header = { alg: 'HS256', typ: 'JWT', kid }
|
|
1306
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1307
|
-
const encodedHeader = encode(header)
|
|
1308
|
-
const encodedPayload = encode(payload)
|
|
1309
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1310
|
-
|
|
1311
|
-
// Export public key as JWK (which would be used in a real attack)
|
|
1312
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1313
|
-
|
|
1314
|
-
// Attacker would sign with the public key bytes as HMAC secret
|
|
1315
|
-
// We don't need to actually sign for this test - just verify rejection
|
|
1316
|
-
const signatureBuffer = await signing('fake-secret', 'HS256', utf8Encoder.encode(signingInput))
|
|
1317
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1318
|
-
|
|
1319
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1320
|
-
|
|
1321
|
-
// JWK has RS256 alg, but JWT header has HS256 (confusion attack)
|
|
1322
|
-
const keyWithAlg = {
|
|
1323
|
-
...jwk,
|
|
1324
|
-
kid,
|
|
1325
|
-
use: 'sig',
|
|
1326
|
-
alg: 'RS256',
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
const keys = [keyWithAlg]
|
|
1330
|
-
|
|
1331
|
-
// Should reject because HS256 is a symmetric algorithm (checked before allowedAlgorithms)
|
|
1332
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(
|
|
1333
|
-
JwtSymmetricAlgorithmNotAllowed
|
|
1334
|
-
)
|
|
1335
|
-
})
|
|
1336
|
-
})
|
|
1337
|
-
|
|
1338
|
-
describe('verifyWithJwks algorithm whitelist', () => {
|
|
1339
|
-
it('Should reject algorithm not in whitelist', async () => {
|
|
1340
|
-
const payload = { message: 'hello world' }
|
|
1341
|
-
const kid = 'dummy'
|
|
1342
|
-
|
|
1343
|
-
// Generate RS256 key pair
|
|
1344
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1345
|
-
{
|
|
1346
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1347
|
-
modulusLength: 2048,
|
|
1348
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1349
|
-
hash: 'SHA-256',
|
|
1350
|
-
},
|
|
1351
|
-
true,
|
|
1352
|
-
['sign', 'verify']
|
|
1353
|
-
)
|
|
1354
|
-
|
|
1355
|
-
// Create JWT with RS256
|
|
1356
|
-
const header = { alg: 'RS256', typ: 'JWT', kid }
|
|
1357
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1358
|
-
const encodedHeader = encode(header)
|
|
1359
|
-
const encodedPayload = encode(payload)
|
|
1360
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1361
|
-
|
|
1362
|
-
const signatureBuffer = await signing(
|
|
1363
|
-
keyPair.privateKey,
|
|
1364
|
-
'RS256',
|
|
1365
|
-
utf8Encoder.encode(signingInput)
|
|
1366
|
-
)
|
|
1367
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1368
|
-
|
|
1369
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1370
|
-
|
|
1371
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1372
|
-
const keyWithAlg = {
|
|
1373
|
-
...jwk,
|
|
1374
|
-
kid,
|
|
1375
|
-
use: 'sig',
|
|
1376
|
-
alg: 'RS256',
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
const keys = [keyWithAlg]
|
|
1380
|
-
|
|
1381
|
-
// RS256 is not in the whitelist (only ES256 is allowed)
|
|
1382
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['ES256'] })).rejects.toThrow(
|
|
1383
|
-
JwtAlgorithmNotAllowed
|
|
1384
|
-
)
|
|
1385
|
-
})
|
|
1386
|
-
|
|
1387
|
-
it('Should accept algorithm in whitelist', async () => {
|
|
1388
|
-
const payload = { message: 'hello world' }
|
|
1389
|
-
const kid = 'dummy'
|
|
1390
|
-
|
|
1391
|
-
// Generate RS256 key pair
|
|
1392
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1393
|
-
{
|
|
1394
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1395
|
-
modulusLength: 2048,
|
|
1396
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1397
|
-
hash: 'SHA-256',
|
|
1398
|
-
},
|
|
1399
|
-
true,
|
|
1400
|
-
['sign', 'verify']
|
|
1401
|
-
)
|
|
1402
|
-
|
|
1403
|
-
// Create JWT with RS256
|
|
1404
|
-
const header = { alg: 'RS256', typ: 'JWT', kid }
|
|
1405
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1406
|
-
const encodedHeader = encode(header)
|
|
1407
|
-
const encodedPayload = encode(payload)
|
|
1408
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1409
|
-
|
|
1410
|
-
const signatureBuffer = await signing(
|
|
1411
|
-
keyPair.privateKey,
|
|
1412
|
-
'RS256',
|
|
1413
|
-
utf8Encoder.encode(signingInput)
|
|
1414
|
-
)
|
|
1415
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1416
|
-
|
|
1417
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1418
|
-
|
|
1419
|
-
const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)
|
|
1420
|
-
const keyWithAlg = {
|
|
1421
|
-
...jwk,
|
|
1422
|
-
kid,
|
|
1423
|
-
use: 'sig',
|
|
1424
|
-
alg: 'RS256',
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
const keys = [keyWithAlg]
|
|
1428
|
-
|
|
1429
|
-
// RS256 is in the whitelist
|
|
1430
|
-
const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256', 'ES256'] })
|
|
1431
|
-
expect(result).toEqual(payload)
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
// Note: Tests for "whitelist not specified" and "empty whitelist" were removed
|
|
1435
|
-
// because allowedAlgorithms is now required (not optional).
|
|
1436
|
-
// This is a breaking change that enforces explicit algorithm specification for security.
|
|
1437
|
-
|
|
1438
|
-
it('Should reject symmetric algorithm (HS256) in JWT header', async () => {
|
|
1439
|
-
// This test verifies that symmetric algorithms are rejected even when
|
|
1440
|
-
// using verifyWithJwks with asymmetric algorithm whitelist.
|
|
1441
|
-
// Note: HS256 cannot be added to allowedAlgorithms due to type constraints (AsymmetricAlgorithm[])
|
|
1442
|
-
const payload = { message: 'hello world' }
|
|
1443
|
-
const secret = 'secret'
|
|
1444
|
-
const kid = 'dummy'
|
|
1445
|
-
|
|
1446
|
-
// Create JWT with HS256
|
|
1447
|
-
const header = { alg: 'HS256', typ: 'JWT', kid }
|
|
1448
|
-
const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)
|
|
1449
|
-
const encodedHeader = encode(header)
|
|
1450
|
-
const encodedPayload = encode(payload)
|
|
1451
|
-
const signingInput = `${encodedHeader}.${encodedPayload}`
|
|
1452
|
-
|
|
1453
|
-
const signatureBuffer = await signing(secret, 'HS256', utf8Encoder.encode(signingInput))
|
|
1454
|
-
const signature = encodeBase64Url(signatureBuffer)
|
|
1455
|
-
|
|
1456
|
-
const token = `${encodedHeader}.${encodedPayload}.${signature}`
|
|
1457
|
-
|
|
1458
|
-
const keys = [
|
|
1459
|
-
{
|
|
1460
|
-
kty: 'oct',
|
|
1461
|
-
kid,
|
|
1462
|
-
k: encodeBase64Url(utf8Encoder.encode(secret).buffer),
|
|
1463
|
-
use: 'sig',
|
|
1464
|
-
alg: 'HS256',
|
|
1465
|
-
},
|
|
1466
|
-
]
|
|
1467
|
-
|
|
1468
|
-
// HS256 in JWT header should be rejected as symmetric algorithm
|
|
1469
|
-
// (symmetric algorithm check happens before allowedAlgorithms check)
|
|
1470
|
-
await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(
|
|
1471
|
-
JwtSymmetricAlgorithmNotAllowed
|
|
1472
|
-
)
|
|
1473
|
-
})
|
|
1474
|
-
})
|
|
1475
|
-
|
|
1476
|
-
describe('verifyWithJwks key handling', () => {
|
|
1477
|
-
it('Should not mutate provided keys when JWKS is fetched repeatedly', async () => {
|
|
1478
|
-
const localKeys: AcrylJsonWebKey[] = [
|
|
1479
|
-
{
|
|
1480
|
-
kty: 'RSA',
|
|
1481
|
-
kid: 'local-key',
|
|
1482
|
-
alg: 'RS256',
|
|
1483
|
-
e: 'AQAB',
|
|
1484
|
-
n: 'sXchAZo4YqB7f1_g8U9RVcdpShUMHbOWcZHhGXLiCFYI8aAizI0s5momkMumZ5qX6Ch12yvDqOiiMHDLecxB2S7RMyCV2wAPOQgpdnXl16rDpD6PEw24kTx5cDIeEJD7BqXc9Ejo4kKDAdAm8YGtS-wGGyRyvE4s46HoPazTA7k',
|
|
1485
|
-
use: 'sig',
|
|
1486
|
-
},
|
|
1487
|
-
]
|
|
1488
|
-
|
|
1489
|
-
const originalKeys = structuredClone(localKeys)
|
|
1490
|
-
const originalFetch = globalThis.fetch
|
|
1491
|
-
const header = Buffer.from(
|
|
1492
|
-
JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: 'unknown-key' })
|
|
1493
|
-
).toString('base64url')
|
|
1494
|
-
const payload = Buffer.from(JSON.stringify({})).toString('base64url')
|
|
1495
|
-
const token = `${header}.${payload}.x`
|
|
1496
|
-
|
|
1497
|
-
try {
|
|
1498
|
-
globalThis.fetch = (async () => {
|
|
1499
|
-
return new Response(
|
|
1500
|
-
JSON.stringify({
|
|
1501
|
-
keys: [
|
|
1502
|
-
{ ...localKeys[0], kid: 'remote-key' },
|
|
1503
|
-
{ ...localKeys[0], kid: 'remote-key' },
|
|
1504
|
-
],
|
|
1505
|
-
}),
|
|
1506
|
-
{ status: 200, headers: { 'content-type': 'application/json' } }
|
|
1507
|
-
)
|
|
1508
|
-
}) as typeof globalThis.fetch
|
|
1509
|
-
|
|
1510
|
-
await expect(
|
|
1511
|
-
verifyWithJwks(token, {
|
|
1512
|
-
keys: localKeys,
|
|
1513
|
-
jwks_uri: 'https://example.invalid/.well-known/jwks.json',
|
|
1514
|
-
allowedAlgorithms: ['RS256'],
|
|
1515
|
-
})
|
|
1516
|
-
).rejects.toThrow(JwtTokenInvalid)
|
|
1517
|
-
|
|
1518
|
-
await expect(
|
|
1519
|
-
verifyWithJwks(token, {
|
|
1520
|
-
keys: localKeys,
|
|
1521
|
-
jwks_uri: 'https://example.invalid/.well-known/jwks.json',
|
|
1522
|
-
allowedAlgorithms: ['RS256'],
|
|
1523
|
-
})
|
|
1524
|
-
).rejects.toThrow(JwtTokenInvalid)
|
|
1525
|
-
} finally {
|
|
1526
|
-
globalThis.fetch = originalFetch
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
expect(localKeys).toEqual(originalKeys)
|
|
1530
|
-
})
|
|
1531
|
-
})
|
|
1532
|
-
|
|
1533
|
-
async function exportPEMPrivateKey(key: CryptoKey): Promise<string> {
|
|
1534
|
-
const exported = await crypto.subtle.exportKey('pkcs8', key)
|
|
1535
|
-
const pem = `-----BEGIN PRIVATE KEY-----\n${encodeBase64(exported)}\n-----END PRIVATE KEY-----`
|
|
1536
|
-
return pem
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
async function exportPEMPublicKey(key: CryptoKey): Promise<string> {
|
|
1540
|
-
const exported = await crypto.subtle.exportKey('spki', key)
|
|
1541
|
-
const pem = `-----BEGIN PUBLIC KEY-----\n${encodeBase64(exported)}\n-----END PUBLIC KEY-----`
|
|
1542
|
-
return pem
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
async function exportJWK(key: CryptoKey): Promise<JsonWebKey> {
|
|
1546
|
-
return await crypto.subtle.exportKey('jwk', key)
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
async function generateRSAKey(hash: string): Promise<CryptoKeyPair> {
|
|
1550
|
-
return await crypto.subtle.generateKey(
|
|
1551
|
-
{
|
|
1552
|
-
hash,
|
|
1553
|
-
modulusLength: 2048,
|
|
1554
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1555
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1556
|
-
},
|
|
1557
|
-
true,
|
|
1558
|
-
['sign', 'verify']
|
|
1559
|
-
)
|
|
1560
|
-
}
|
|
1561
|
-
|
|
1562
|
-
async function generateRSAPSSKey(hash: string): Promise<CryptoKeyPair> {
|
|
1563
|
-
return await crypto.subtle.generateKey(
|
|
1564
|
-
{
|
|
1565
|
-
hash,
|
|
1566
|
-
modulusLength: 2048,
|
|
1567
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1568
|
-
name: 'RSA-PSS',
|
|
1569
|
-
},
|
|
1570
|
-
true,
|
|
1571
|
-
['sign', 'verify']
|
|
1572
|
-
)
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
async function generateECDSAKey(namedCurve: string): Promise<CryptoKeyPair> {
|
|
1576
|
-
return await crypto.subtle.generateKey(
|
|
1577
|
-
{
|
|
1578
|
-
name: 'ECDSA',
|
|
1579
|
-
namedCurve,
|
|
1580
|
-
},
|
|
1581
|
-
true,
|
|
1582
|
-
['sign', 'verify']
|
|
1583
|
-
)
|
|
1584
|
-
}
|
|
1585
|
-
|
|
1586
|
-
async function generateEd25519Key(): Promise<CryptoKeyPair> {
|
|
1587
|
-
return await crypto.subtle.generateKey(
|
|
1588
|
-
{
|
|
1589
|
-
name: 'Ed25519',
|
|
1590
|
-
namedCurve: 'Ed25519',
|
|
1591
|
-
},
|
|
1592
|
-
true,
|
|
1593
|
-
['sign', 'verify']
|
|
1594
|
-
)
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
describe('Security: Algorithm Confusion Attack Prevention', () => {
|
|
1598
|
-
it('Should throw JwtAlgorithmRequired error when alg is not specified in verify()', async () => {
|
|
1599
|
-
const payload = { message: 'hello world' }
|
|
1600
|
-
const secret = 'a-secret'
|
|
1601
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
1602
|
-
|
|
1603
|
-
let err: JwtAlgorithmRequired | undefined
|
|
1604
|
-
let authorized
|
|
1605
|
-
try {
|
|
1606
|
-
// @ts-expect-error - intentionally testing without alg parameter
|
|
1607
|
-
authorized = await JWT.verify(tok, secret)
|
|
1608
|
-
} catch (e) {
|
|
1609
|
-
err = e as JwtAlgorithmRequired
|
|
1610
|
-
}
|
|
1611
|
-
expect(err).toBeInstanceOf(JwtAlgorithmRequired)
|
|
1612
|
-
expect(err?.message).toBe('JWT verification requires "alg" option to be specified')
|
|
1613
|
-
expect(authorized).toBeUndefined()
|
|
1614
|
-
})
|
|
1615
|
-
|
|
1616
|
-
it('Should throw JwtAlgorithmRequired error when alg is undefined in options object', async () => {
|
|
1617
|
-
const payload = { message: 'hello world' }
|
|
1618
|
-
const secret = 'a-secret'
|
|
1619
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
1620
|
-
|
|
1621
|
-
let err: JwtAlgorithmRequired | undefined
|
|
1622
|
-
let authorized
|
|
1623
|
-
try {
|
|
1624
|
-
// @ts-expect-error - intentionally testing with undefined alg
|
|
1625
|
-
authorized = await JWT.verify(tok, secret, { alg: undefined })
|
|
1626
|
-
} catch (e) {
|
|
1627
|
-
err = e as JwtAlgorithmRequired
|
|
1628
|
-
}
|
|
1629
|
-
expect(err).toBeInstanceOf(JwtAlgorithmRequired)
|
|
1630
|
-
expect(authorized).toBeUndefined()
|
|
1631
|
-
})
|
|
1632
|
-
|
|
1633
|
-
it('Should prevent algorithm confusion attack (RS256 token verified with HS256 using public key)', async () => {
|
|
1634
|
-
// Generate RSA key pair
|
|
1635
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1636
|
-
{
|
|
1637
|
-
name: 'RSASSA-PKCS1-v1_5',
|
|
1638
|
-
modulusLength: 2048,
|
|
1639
|
-
publicExponent: new Uint8Array([1, 0, 1]),
|
|
1640
|
-
hash: 'SHA-256',
|
|
1641
|
-
},
|
|
1642
|
-
true,
|
|
1643
|
-
['sign', 'verify']
|
|
1644
|
-
)
|
|
1645
|
-
|
|
1646
|
-
const payload = { message: 'hello world' }
|
|
1647
|
-
|
|
1648
|
-
// Sign token with RS256 using private key
|
|
1649
|
-
const tok = await JWT.sign(payload, keyPair.privateKey, AlgorithmTypes.RS256)
|
|
1650
|
-
|
|
1651
|
-
// Verify should succeed with RS256 and public key
|
|
1652
|
-
const verified = await JWT.verify(tok, keyPair.publicKey, AlgorithmTypes.RS256)
|
|
1653
|
-
expect(verified).toEqual(payload)
|
|
1654
|
-
|
|
1655
|
-
// Attempting to verify with HS256 using the public key should fail with algorithm mismatch
|
|
1656
|
-
// This simulates the algorithm confusion attack scenario
|
|
1657
|
-
let err: Error | undefined
|
|
1658
|
-
let maliciousResult
|
|
1659
|
-
try {
|
|
1660
|
-
maliciousResult = await JWT.verify(tok, keyPair.publicKey, AlgorithmTypes.HS256)
|
|
1661
|
-
} catch (e) {
|
|
1662
|
-
err = e as Error
|
|
1663
|
-
}
|
|
1664
|
-
// The verification should fail because the header.alg (RS256) doesn't match options.alg (HS256)
|
|
1665
|
-
expect(maliciousResult).toBeUndefined()
|
|
1666
|
-
expect(err).toBeInstanceOf(JwtAlgorithmMismatch)
|
|
1667
|
-
})
|
|
1668
|
-
|
|
1669
|
-
it('Should throw JwtAlgorithmMismatch when header.alg does not match options.alg', async () => {
|
|
1670
|
-
const payload = { message: 'hello world' }
|
|
1671
|
-
const secret = 'a-secret'
|
|
1672
|
-
|
|
1673
|
-
// Sign with HS512
|
|
1674
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)
|
|
1675
|
-
|
|
1676
|
-
// Try to verify with HS256 (wrong algorithm)
|
|
1677
|
-
let err: Error | undefined
|
|
1678
|
-
let result
|
|
1679
|
-
try {
|
|
1680
|
-
result = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
1681
|
-
} catch (e) {
|
|
1682
|
-
err = e as Error
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
expect(result).toBeUndefined()
|
|
1686
|
-
expect(err).toBeInstanceOf(JwtAlgorithmMismatch)
|
|
1687
|
-
})
|
|
1688
|
-
|
|
1689
|
-
it('Should require explicit alg specification to prevent default fallback attack', async () => {
|
|
1690
|
-
const payload = { message: 'hello world' }
|
|
1691
|
-
const secret = 'a-secret'
|
|
1692
|
-
|
|
1693
|
-
// Create a token with HS256
|
|
1694
|
-
const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)
|
|
1695
|
-
|
|
1696
|
-
// Verify with explicit HS256 should work
|
|
1697
|
-
const verified = await JWT.verify(tok, secret, AlgorithmTypes.HS256)
|
|
1698
|
-
expect(verified).toEqual(payload)
|
|
1699
|
-
|
|
1700
|
-
// Verify without alg should throw error (no default fallback)
|
|
1701
|
-
let err: Error | undefined
|
|
1702
|
-
try {
|
|
1703
|
-
// @ts-expect-error - intentionally testing without alg parameter
|
|
1704
|
-
await JWT.verify(tok, secret)
|
|
1705
|
-
} catch (e) {
|
|
1706
|
-
err = e as Error
|
|
1707
|
-
}
|
|
1708
|
-
expect(err).toBeInstanceOf(JwtAlgorithmRequired)
|
|
1709
|
-
})
|
|
1710
|
-
})
|
|
1711
|
-
|
|
1712
|
-
describe('JWT decode token format validation', () => {
|
|
1713
|
-
it('decode should throw JwtTokenInvalid for token with 2 parts', () => {
|
|
1714
|
-
const malformed = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8ifQ'
|
|
1715
|
-
expect(() => JWT.decode(malformed)).toThrow(JwtTokenInvalid)
|
|
1716
|
-
})
|
|
1717
|
-
|
|
1718
|
-
it('decode should throw JwtTokenInvalid for token with 1 part', () => {
|
|
1719
|
-
expect(() => JWT.decode('eyJhbGciOiJIUzI1NiJ9')).toThrow(JwtTokenInvalid)
|
|
1720
|
-
})
|
|
1721
|
-
|
|
1722
|
-
it('decode should throw JwtTokenInvalid for token with 4 parts', () => {
|
|
1723
|
-
const fourParts = 'a.b.c.d'
|
|
1724
|
-
expect(() => JWT.decode(fourParts)).toThrow(JwtTokenInvalid)
|
|
1725
|
-
})
|
|
1726
|
-
|
|
1727
|
-
it('decode should throw JwtTokenInvalid for empty string', () => {
|
|
1728
|
-
expect(() => JWT.decode('')).toThrow(JwtTokenInvalid)
|
|
1729
|
-
})
|
|
1730
|
-
|
|
1731
|
-
it('decodeHeader should throw JwtTokenInvalid for token with 2 parts', () => {
|
|
1732
|
-
const malformed = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8ifQ'
|
|
1733
|
-
expect(() => JWT.decodeHeader(malformed)).toThrow(JwtTokenInvalid)
|
|
1734
|
-
})
|
|
1735
|
-
|
|
1736
|
-
it('decodeHeader should throw JwtTokenInvalid for empty string', () => {
|
|
1737
|
-
expect(() => JWT.decodeHeader('')).toThrow(JwtTokenInvalid)
|
|
1738
|
-
})
|
|
1739
|
-
|
|
1740
|
-
it('decode should work for valid 3-part token', async () => {
|
|
1741
|
-
const secret = 'a-secret'
|
|
1742
|
-
const tok = await JWT.sign({ message: 'hello' }, secret, AlgorithmTypes.HS256)
|
|
1743
|
-
const decoded = JWT.decode(tok)
|
|
1744
|
-
expect(decoded.header.alg).toBe('HS256')
|
|
1745
|
-
expect(decoded.payload).toEqual({ message: 'hello' })
|
|
1746
|
-
})
|
|
1747
|
-
|
|
1748
|
-
it('decodeHeader should work for valid 3-part token', async () => {
|
|
1749
|
-
const secret = 'a-secret'
|
|
1750
|
-
const tok = await JWT.sign({ message: 'hello' }, secret, AlgorithmTypes.HS256)
|
|
1751
|
-
const header = JWT.decodeHeader(tok)
|
|
1752
|
-
expect(header.alg).toBe('HS256')
|
|
1753
|
-
expect(header.typ).toBe('JWT')
|
|
1754
|
-
})
|
|
1755
|
-
})
|
|
1756
|
-
|
|
1757
|
-
describe('PEM parsing', async () => {
|
|
1758
|
-
const keyPair = await crypto.subtle.generateKey(
|
|
1759
|
-
{
|
|
1760
|
-
name: 'Ed25519',
|
|
1761
|
-
},
|
|
1762
|
-
true,
|
|
1763
|
-
['sign', 'verify']
|
|
1764
|
-
)
|
|
1765
|
-
|
|
1766
|
-
const exported = await crypto.subtle.exportKey('pkcs8', keyPair.privateKey)
|
|
1767
|
-
const base64Key = Buffer.from(exported).toString('base64')
|
|
1768
|
-
const privateKey = `-----BEGIN PRIVATE KEY-----\n${base64Key}\n-----END PRIVATE KEY-----`
|
|
1769
|
-
|
|
1770
|
-
it('should sign & verify with a normal private PEM key', async () => {
|
|
1771
|
-
const payload = { sub: '123' }
|
|
1772
|
-
|
|
1773
|
-
const token = await JWT.sign(payload, privateKey, 'EdDSA')
|
|
1774
|
-
const verified = await JWT.verify(token, privateKey, 'EdDSA')
|
|
1775
|
-
|
|
1776
|
-
expect(verified).toEqual(payload)
|
|
1777
|
-
})
|
|
1778
|
-
|
|
1779
|
-
it('should sign & verify with a single-line private PEM key', async () => {
|
|
1780
|
-
const singleLinePrivateKey = privateKey.replace(/\n/g, '')
|
|
1781
|
-
const payload = { sub: '123' }
|
|
1782
|
-
|
|
1783
|
-
const token = await JWT.sign(payload, singleLinePrivateKey, 'EdDSA')
|
|
1784
|
-
const verified = await JWT.verify(token, privateKey, 'EdDSA')
|
|
1785
|
-
|
|
1786
|
-
expect(verified).toEqual(payload)
|
|
1787
|
-
})
|
|
1788
|
-
})
|