@better-auth/core 1.5.7-beta.1 → 1.6.0-beta.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/dist/api/index.d.mts +1 -2
- package/dist/api/index.mjs +0 -3
- package/dist/async_hooks/index.d.mts +1 -2
- package/dist/async_hooks/index.mjs +0 -2
- package/dist/async_hooks/pure.index.d.mts +1 -2
- package/dist/async_hooks/pure.index.mjs +0 -2
- package/dist/context/endpoint-context.d.mts +1 -2
- package/dist/context/endpoint-context.mjs +0 -2
- package/dist/context/global.d.mts +1 -2
- package/dist/context/global.mjs +1 -3
- package/dist/context/request-state.d.mts +1 -2
- package/dist/context/request-state.mjs +0 -2
- package/dist/context/transaction.d.mts +1 -2
- package/dist/context/transaction.mjs +0 -2
- package/dist/db/adapter/factory.d.mts +1 -2
- package/dist/db/adapter/factory.mjs +3 -6
- package/dist/db/adapter/get-default-field-name.d.mts +1 -2
- package/dist/db/adapter/get-default-field-name.mjs +0 -2
- package/dist/db/adapter/get-default-model-name.d.mts +1 -2
- package/dist/db/adapter/get-default-model-name.mjs +0 -2
- package/dist/db/adapter/get-field-attributes.d.mts +1 -2
- package/dist/db/adapter/get-field-attributes.mjs +0 -2
- package/dist/db/adapter/get-field-name.d.mts +1 -2
- package/dist/db/adapter/get-field-name.mjs +0 -2
- package/dist/db/adapter/get-id-field.d.mts +1 -2
- package/dist/db/adapter/get-id-field.mjs +0 -3
- package/dist/db/adapter/get-model-name.d.mts +1 -2
- package/dist/db/adapter/get-model-name.mjs +0 -2
- package/dist/db/adapter/index.d.mts +9 -2
- package/dist/db/adapter/index.mjs +0 -2
- package/dist/db/adapter/types.d.mts +1 -2
- package/dist/db/adapter/utils.d.mts +1 -2
- package/dist/db/adapter/utils.mjs +0 -2
- package/dist/db/get-tables.d.mts +1 -2
- package/dist/db/get-tables.mjs +0 -2
- package/dist/db/plugin.d.mts +1 -2
- package/dist/db/schema/account.d.mts +1 -2
- package/dist/db/schema/account.mjs +0 -2
- package/dist/db/schema/rate-limit.d.mts +1 -2
- package/dist/db/schema/rate-limit.mjs +0 -2
- package/dist/db/schema/session.d.mts +1 -2
- package/dist/db/schema/session.mjs +0 -2
- package/dist/db/schema/shared.d.mts +1 -2
- package/dist/db/schema/shared.mjs +0 -2
- package/dist/db/schema/user.d.mts +1 -2
- package/dist/db/schema/user.mjs +0 -2
- package/dist/db/schema/verification.d.mts +1 -2
- package/dist/db/schema/verification.mjs +0 -2
- package/dist/db/type.d.mts +1 -2
- package/dist/env/color-depth.d.mts +1 -2
- package/dist/env/color-depth.mjs +0 -2
- package/dist/env/env-impl.d.mts +1 -2
- package/dist/env/env-impl.mjs +0 -2
- package/dist/env/logger.d.mts +1 -2
- package/dist/env/logger.mjs +0 -2
- package/dist/error/codes.d.mts +1 -2
- package/dist/error/codes.mjs +0 -2
- package/dist/error/index.d.mts +1 -2
- package/dist/error/index.mjs +0 -2
- package/dist/instrumentation/attributes.d.mts +1 -2
- package/dist/instrumentation/attributes.mjs +0 -2
- package/dist/instrumentation/tracer.d.mts +1 -2
- package/dist/instrumentation/tracer.mjs +29 -15
- package/dist/oauth2/client-credentials-token.d.mts +1 -2
- package/dist/oauth2/client-credentials-token.mjs +0 -2
- package/dist/oauth2/create-authorization-url.d.mts +1 -2
- package/dist/oauth2/create-authorization-url.mjs +0 -2
- package/dist/oauth2/oauth-provider.d.mts +1 -2
- package/dist/oauth2/refresh-access-token.d.mts +1 -2
- package/dist/oauth2/refresh-access-token.mjs +0 -2
- package/dist/oauth2/utils.d.mts +1 -2
- package/dist/oauth2/utils.mjs +0 -2
- package/dist/oauth2/validate-authorization-code.d.mts +1 -2
- package/dist/oauth2/validate-authorization-code.mjs +0 -3
- package/dist/oauth2/verify.d.mts +1 -2
- package/dist/oauth2/verify.mjs +0 -3
- package/dist/social-providers/apple.d.mts +1 -2
- package/dist/social-providers/apple.mjs +0 -3
- package/dist/social-providers/atlassian.d.mts +1 -2
- package/dist/social-providers/atlassian.mjs +0 -4
- package/dist/social-providers/cognito.d.mts +1 -2
- package/dist/social-providers/cognito.mjs +0 -4
- package/dist/social-providers/discord.d.mts +1 -2
- package/dist/social-providers/discord.mjs +0 -3
- package/dist/social-providers/dropbox.d.mts +1 -2
- package/dist/social-providers/dropbox.mjs +0 -3
- package/dist/social-providers/facebook.d.mts +1 -2
- package/dist/social-providers/facebook.mjs +0 -3
- package/dist/social-providers/figma.d.mts +1 -2
- package/dist/social-providers/figma.mjs +0 -4
- package/dist/social-providers/github.d.mts +1 -2
- package/dist/social-providers/github.mjs +0 -4
- package/dist/social-providers/gitlab.d.mts +1 -2
- package/dist/social-providers/gitlab.mjs +0 -3
- package/dist/social-providers/google.d.mts +1 -2
- package/dist/social-providers/google.mjs +0 -4
- package/dist/social-providers/huggingface.d.mts +1 -2
- package/dist/social-providers/huggingface.mjs +0 -3
- package/dist/social-providers/index.d.mts +1 -2
- package/dist/social-providers/index.mjs +0 -2
- package/dist/social-providers/kakao.d.mts +1 -2
- package/dist/social-providers/kakao.mjs +0 -3
- package/dist/social-providers/kick.d.mts +1 -2
- package/dist/social-providers/kick.mjs +0 -3
- package/dist/social-providers/line.d.mts +1 -2
- package/dist/social-providers/line.mjs +0 -3
- package/dist/social-providers/linear.d.mts +1 -2
- package/dist/social-providers/linear.mjs +0 -3
- package/dist/social-providers/linkedin.d.mts +1 -2
- package/dist/social-providers/linkedin.mjs +0 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +1 -2
- package/dist/social-providers/microsoft-entra-id.mjs +0 -4
- package/dist/social-providers/naver.d.mts +1 -2
- package/dist/social-providers/naver.mjs +0 -3
- package/dist/social-providers/notion.d.mts +1 -2
- package/dist/social-providers/notion.mjs +0 -3
- package/dist/social-providers/paybin.d.mts +1 -2
- package/dist/social-providers/paybin.mjs +0 -4
- package/dist/social-providers/paypal.d.mts +1 -2
- package/dist/social-providers/paypal.mjs +0 -4
- package/dist/social-providers/polar.d.mts +1 -2
- package/dist/social-providers/polar.mjs +0 -3
- package/dist/social-providers/railway.d.mts +1 -2
- package/dist/social-providers/railway.mjs +0 -3
- package/dist/social-providers/reddit.d.mts +1 -2
- package/dist/social-providers/reddit.mjs +0 -3
- package/dist/social-providers/roblox.d.mts +1 -2
- package/dist/social-providers/roblox.mjs +0 -3
- package/dist/social-providers/salesforce.d.mts +1 -2
- package/dist/social-providers/salesforce.mjs +0 -4
- package/dist/social-providers/slack.d.mts +1 -2
- package/dist/social-providers/slack.mjs +0 -3
- package/dist/social-providers/spotify.d.mts +1 -2
- package/dist/social-providers/spotify.mjs +0 -3
- package/dist/social-providers/tiktok.d.mts +1 -2
- package/dist/social-providers/tiktok.mjs +0 -3
- package/dist/social-providers/twitch.d.mts +1 -2
- package/dist/social-providers/twitch.mjs +0 -4
- package/dist/social-providers/twitter.d.mts +1 -2
- package/dist/social-providers/twitter.mjs +0 -3
- package/dist/social-providers/vercel.d.mts +1 -2
- package/dist/social-providers/vercel.mjs +0 -3
- package/dist/social-providers/vk.d.mts +1 -2
- package/dist/social-providers/vk.mjs +0 -3
- package/dist/social-providers/wechat.d.mts +1 -2
- package/dist/social-providers/wechat.mjs +0 -2
- package/dist/social-providers/zoom.d.mts +1 -2
- package/dist/social-providers/zoom.mjs +0 -3
- package/dist/types/context.d.mts +2 -2
- package/dist/types/cookie.d.mts +1 -2
- package/dist/types/helper.d.mts +1 -2
- package/dist/types/init-options.d.mts +14 -6
- package/dist/types/plugin-client.d.mts +2 -2
- package/dist/types/plugin.d.mts +2 -2
- package/dist/types/secret.d.mts +1 -2
- package/dist/utils/db.d.mts +1 -2
- package/dist/utils/db.mjs +0 -2
- package/dist/utils/deprecate.d.mts +1 -2
- package/dist/utils/deprecate.mjs +0 -2
- package/dist/utils/error-codes.d.mts +1 -2
- package/dist/utils/error-codes.mjs +0 -2
- package/dist/utils/fetch-metadata.d.mts +1 -2
- package/dist/utils/fetch-metadata.mjs +0 -2
- package/dist/utils/id.d.mts +1 -2
- package/dist/utils/id.mjs +0 -2
- package/dist/utils/ip.d.mts +1 -2
- package/dist/utils/ip.mjs +0 -2
- package/dist/utils/json.d.mts +1 -2
- package/dist/utils/json.mjs +0 -3
- package/dist/utils/string.d.mts +1 -2
- package/dist/utils/string.mjs +0 -2
- package/dist/utils/url.d.mts +1 -2
- package/dist/utils/url.mjs +0 -2
- package/package.json +9 -7
- package/src/db/adapter/factory.ts +2 -0
- package/src/db/adapter/index.ts +8 -0
- package/src/instrumentation/tracer.ts +40 -12
- package/src/social-providers/index.ts +0 -2
- package/src/types/context.ts +1 -0
- package/src/types/init-options.ts +13 -4
- package/src/types/plugin-client.ts +1 -0
- package/src/types/plugin.ts +1 -0
- package/dist/api/index.mjs.map +0 -1
- package/dist/async_hooks/index.mjs.map +0 -1
- package/dist/async_hooks/pure.index.mjs.map +0 -1
- package/dist/context/endpoint-context.mjs.map +0 -1
- package/dist/context/global.mjs.map +0 -1
- package/dist/context/request-state.mjs.map +0 -1
- package/dist/context/transaction.mjs.map +0 -1
- package/dist/db/adapter/factory.mjs.map +0 -1
- package/dist/db/adapter/get-default-field-name.mjs.map +0 -1
- package/dist/db/adapter/get-default-model-name.mjs.map +0 -1
- package/dist/db/adapter/get-field-attributes.mjs.map +0 -1
- package/dist/db/adapter/get-field-name.mjs.map +0 -1
- package/dist/db/adapter/get-id-field.mjs.map +0 -1
- package/dist/db/adapter/get-model-name.mjs.map +0 -1
- package/dist/db/adapter/index.mjs.map +0 -1
- package/dist/db/adapter/utils.mjs.map +0 -1
- package/dist/db/get-tables.mjs.map +0 -1
- package/dist/db/schema/account.mjs.map +0 -1
- package/dist/db/schema/rate-limit.mjs.map +0 -1
- package/dist/db/schema/session.mjs.map +0 -1
- package/dist/db/schema/shared.mjs.map +0 -1
- package/dist/db/schema/user.mjs.map +0 -1
- package/dist/db/schema/verification.mjs.map +0 -1
- package/dist/env/color-depth.mjs.map +0 -1
- package/dist/env/env-impl.mjs.map +0 -1
- package/dist/env/logger.mjs.map +0 -1
- package/dist/error/codes.mjs.map +0 -1
- package/dist/error/index.mjs.map +0 -1
- package/dist/instrumentation/attributes.mjs.map +0 -1
- package/dist/instrumentation/tracer.mjs.map +0 -1
- package/dist/oauth2/client-credentials-token.mjs.map +0 -1
- package/dist/oauth2/create-authorization-url.mjs.map +0 -1
- package/dist/oauth2/refresh-access-token.mjs.map +0 -1
- package/dist/oauth2/utils.mjs.map +0 -1
- package/dist/oauth2/validate-authorization-code.mjs.map +0 -1
- package/dist/oauth2/verify.mjs.map +0 -1
- package/dist/social-providers/apple.mjs.map +0 -1
- package/dist/social-providers/atlassian.mjs.map +0 -1
- package/dist/social-providers/cognito.mjs.map +0 -1
- package/dist/social-providers/discord.mjs.map +0 -1
- package/dist/social-providers/dropbox.mjs.map +0 -1
- package/dist/social-providers/facebook.mjs.map +0 -1
- package/dist/social-providers/figma.mjs.map +0 -1
- package/dist/social-providers/github.mjs.map +0 -1
- package/dist/social-providers/gitlab.mjs.map +0 -1
- package/dist/social-providers/google.mjs.map +0 -1
- package/dist/social-providers/huggingface.mjs.map +0 -1
- package/dist/social-providers/index.mjs.map +0 -1
- package/dist/social-providers/kakao.mjs.map +0 -1
- package/dist/social-providers/kick.mjs.map +0 -1
- package/dist/social-providers/line.mjs.map +0 -1
- package/dist/social-providers/linear.mjs.map +0 -1
- package/dist/social-providers/linkedin.mjs.map +0 -1
- package/dist/social-providers/microsoft-entra-id.mjs.map +0 -1
- package/dist/social-providers/naver.mjs.map +0 -1
- package/dist/social-providers/notion.mjs.map +0 -1
- package/dist/social-providers/paybin.mjs.map +0 -1
- package/dist/social-providers/paypal.mjs.map +0 -1
- package/dist/social-providers/polar.mjs.map +0 -1
- package/dist/social-providers/railway.mjs.map +0 -1
- package/dist/social-providers/reddit.mjs.map +0 -1
- package/dist/social-providers/roblox.mjs.map +0 -1
- package/dist/social-providers/salesforce.mjs.map +0 -1
- package/dist/social-providers/slack.mjs.map +0 -1
- package/dist/social-providers/spotify.mjs.map +0 -1
- package/dist/social-providers/tiktok.mjs.map +0 -1
- package/dist/social-providers/twitch.mjs.map +0 -1
- package/dist/social-providers/twitter.mjs.map +0 -1
- package/dist/social-providers/vercel.mjs.map +0 -1
- package/dist/social-providers/vk.mjs.map +0 -1
- package/dist/social-providers/wechat.mjs.map +0 -1
- package/dist/social-providers/zoom.mjs.map +0 -1
- package/dist/utils/db.mjs.map +0 -1
- package/dist/utils/deprecate.mjs.map +0 -1
- package/dist/utils/error-codes.mjs.map +0 -1
- package/dist/utils/fetch-metadata.mjs.map +0 -1
- package/dist/utils/id.mjs.map +0 -1
- package/dist/utils/ip.mjs.map +0 -1
- package/dist/utils/json.mjs.map +0 -1
- package/dist/utils/string.mjs.map +0 -1
- package/dist/utils/url.mjs.map +0 -1
- package/src/context/request-state.test.ts +0 -94
- package/src/db/adapter/get-id-field.test.ts +0 -222
- package/src/db/test/get-tables.test.ts +0 -116
- package/src/env/logger.test.ts +0 -34
- package/src/instrumentation/instrumentation.test.ts +0 -139
- package/src/oauth2/refresh-access-token.test.ts +0 -90
- package/src/oauth2/validate-token.test.ts +0 -229
- package/src/utils/deprecate.test.ts +0 -71
- package/src/utils/fetch-metadata.test.ts +0 -28
- package/src/utils/ip.test.ts +0 -255
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import type { JWK } from "jose";
|
|
2
|
-
import { exportJWK, generateKeyPair, SignJWT } from "jose";
|
|
3
|
-
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
4
|
-
import { validateToken } from "./validate-authorization-code";
|
|
5
|
-
|
|
6
|
-
describe("validateToken", () => {
|
|
7
|
-
const originalFetch = globalThis.fetch;
|
|
8
|
-
const mockedFetch = vi.fn() as unknown as typeof fetch &
|
|
9
|
-
ReturnType<typeof vi.fn>;
|
|
10
|
-
|
|
11
|
-
beforeAll(() => {
|
|
12
|
-
globalThis.fetch = mockedFetch;
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterAll(() => {
|
|
16
|
-
globalThis.fetch = originalFetch;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
async function createTestJWKS(alg: string, crv?: string) {
|
|
20
|
-
const { publicKey, privateKey } = await generateKeyPair(alg, {
|
|
21
|
-
crv,
|
|
22
|
-
extractable: true,
|
|
23
|
-
});
|
|
24
|
-
const publicJWK = await exportJWK(publicKey);
|
|
25
|
-
const privateJWK = await exportJWK(privateKey);
|
|
26
|
-
const kid = `test-key-${Date.now()}`;
|
|
27
|
-
publicJWK.kid = kid;
|
|
28
|
-
publicJWK.alg = alg;
|
|
29
|
-
privateJWK.kid = kid;
|
|
30
|
-
privateJWK.alg = alg;
|
|
31
|
-
return { publicJWK, privateJWK, kid, publicKey, privateKey };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function createSignedToken(
|
|
35
|
-
privateKey: CryptoKey,
|
|
36
|
-
alg: string,
|
|
37
|
-
kid: string,
|
|
38
|
-
payload: Record<string, unknown> = {},
|
|
39
|
-
) {
|
|
40
|
-
return await new SignJWT({
|
|
41
|
-
sub: "user-123",
|
|
42
|
-
email: "test@example.com",
|
|
43
|
-
iss: "https://example.com",
|
|
44
|
-
aud: "test-client",
|
|
45
|
-
...payload,
|
|
46
|
-
})
|
|
47
|
-
.setProtectedHeader({ alg, kid })
|
|
48
|
-
.setIssuedAt()
|
|
49
|
-
.setExpirationTime("1h")
|
|
50
|
-
.sign(privateKey);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function mockJWKSResponse(...publicJWKs: JWK[]) {
|
|
54
|
-
mockedFetch.mockResolvedValueOnce(
|
|
55
|
-
new Response(JSON.stringify({ keys: publicJWKs }), {
|
|
56
|
-
status: 200,
|
|
57
|
-
headers: { "content-type": "application/json" },
|
|
58
|
-
}),
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
it("should verify RS256 signed token", async () => {
|
|
63
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
64
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
65
|
-
mockJWKSResponse(publicJWK);
|
|
66
|
-
|
|
67
|
-
const result = await validateToken(
|
|
68
|
-
token,
|
|
69
|
-
"https://example.com/.well-known/jwks",
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
expect(result).toBeDefined();
|
|
73
|
-
expect(result.payload.sub).toBe("user-123");
|
|
74
|
-
expect(result.payload.email).toBe("test@example.com");
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("should verify ES256 signed token", async () => {
|
|
78
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("ES256");
|
|
79
|
-
const token = await createSignedToken(privateKey, "ES256", kid);
|
|
80
|
-
mockJWKSResponse(publicJWK);
|
|
81
|
-
|
|
82
|
-
const result = await validateToken(
|
|
83
|
-
token,
|
|
84
|
-
"https://example.com/.well-known/jwks",
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
expect(result).toBeDefined();
|
|
88
|
-
expect(result.payload.sub).toBe("user-123");
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it("should verify EdDSA (Ed25519) signed token", async () => {
|
|
92
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS(
|
|
93
|
-
"EdDSA",
|
|
94
|
-
"Ed25519",
|
|
95
|
-
);
|
|
96
|
-
const token = await createSignedToken(privateKey, "EdDSA", kid);
|
|
97
|
-
mockJWKSResponse(publicJWK);
|
|
98
|
-
|
|
99
|
-
const result = await validateToken(
|
|
100
|
-
token,
|
|
101
|
-
"https://example.com/.well-known/jwks",
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
expect(result).toBeDefined();
|
|
105
|
-
expect(result.payload.sub).toBe("user-123");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("should throw when kid doesn't match any key", async () => {
|
|
109
|
-
const { publicJWK, privateKey } = await createTestJWKS("RS256");
|
|
110
|
-
publicJWK.kid = "different-kid";
|
|
111
|
-
const token = await createSignedToken(privateKey, "RS256", "original-kid");
|
|
112
|
-
mockJWKSResponse(publicJWK);
|
|
113
|
-
|
|
114
|
-
await expect(
|
|
115
|
-
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
116
|
-
).rejects.toThrow();
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("should find correct key when multiple keys exist", async () => {
|
|
120
|
-
const key1 = await createTestJWKS("RS256");
|
|
121
|
-
const key2 = await createTestJWKS("RS256");
|
|
122
|
-
const key3 = await createTestJWKS("ES256");
|
|
123
|
-
const token = await createSignedToken(key2.privateKey, "RS256", key2.kid);
|
|
124
|
-
mockJWKSResponse(key1.publicJWK, key2.publicJWK, key3.publicJWK);
|
|
125
|
-
|
|
126
|
-
const result = await validateToken(
|
|
127
|
-
token,
|
|
128
|
-
"https://example.com/.well-known/jwks",
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
expect(result).toBeDefined();
|
|
132
|
-
expect(result.payload.sub).toBe("user-123");
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("should throw when JWKS returns empty keys array", async () => {
|
|
136
|
-
const { privateKey, kid } = await createTestJWKS("RS256");
|
|
137
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
138
|
-
mockJWKSResponse();
|
|
139
|
-
|
|
140
|
-
await expect(
|
|
141
|
-
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
142
|
-
).rejects.toThrow();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("should throw when JWKS fetch fails", async () => {
|
|
146
|
-
const { privateKey, kid } = await createTestJWKS("RS256");
|
|
147
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
148
|
-
mockedFetch.mockResolvedValueOnce(
|
|
149
|
-
new Response("Internal Server Error", { status: 500 }),
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
await expect(
|
|
153
|
-
validateToken(token, "https://example.com/.well-known/jwks"),
|
|
154
|
-
).rejects.toBeDefined();
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("should verify token with matching audience", async () => {
|
|
158
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
159
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
160
|
-
mockJWKSResponse(publicJWK);
|
|
161
|
-
|
|
162
|
-
const result = await validateToken(
|
|
163
|
-
token,
|
|
164
|
-
"https://example.com/.well-known/jwks",
|
|
165
|
-
{ audience: "test-client" },
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
expect(result).toBeDefined();
|
|
169
|
-
expect(result.payload.aud).toBe("test-client");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("should reject token with mismatched audience", async () => {
|
|
173
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
174
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
175
|
-
mockJWKSResponse(publicJWK);
|
|
176
|
-
|
|
177
|
-
await expect(
|
|
178
|
-
validateToken(token, "https://example.com/.well-known/jwks", {
|
|
179
|
-
audience: "wrong-client",
|
|
180
|
-
}),
|
|
181
|
-
).rejects.toThrow();
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("should verify token with matching issuer", async () => {
|
|
185
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
186
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
187
|
-
mockJWKSResponse(publicJWK);
|
|
188
|
-
|
|
189
|
-
const result = await validateToken(
|
|
190
|
-
token,
|
|
191
|
-
"https://example.com/.well-known/jwks",
|
|
192
|
-
{ issuer: "https://example.com" },
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
expect(result).toBeDefined();
|
|
196
|
-
expect(result.payload.iss).toBe("https://example.com");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("should reject token with mismatched issuer", async () => {
|
|
200
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
201
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
202
|
-
mockJWKSResponse(publicJWK);
|
|
203
|
-
|
|
204
|
-
await expect(
|
|
205
|
-
validateToken(token, "https://example.com/.well-known/jwks", {
|
|
206
|
-
issuer: "https://wrong-issuer.com",
|
|
207
|
-
}),
|
|
208
|
-
).rejects.toThrow();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it("should verify token with both audience and issuer", async () => {
|
|
212
|
-
const { publicJWK, privateKey, kid } = await createTestJWKS("RS256");
|
|
213
|
-
const token = await createSignedToken(privateKey, "RS256", kid);
|
|
214
|
-
mockJWKSResponse(publicJWK);
|
|
215
|
-
|
|
216
|
-
const result = await validateToken(
|
|
217
|
-
token,
|
|
218
|
-
"https://example.com/.well-known/jwks",
|
|
219
|
-
{
|
|
220
|
-
audience: "test-client",
|
|
221
|
-
issuer: "https://example.com",
|
|
222
|
-
},
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
expect(result).toBeDefined();
|
|
226
|
-
expect(result.payload.aud).toBe("test-client");
|
|
227
|
-
expect(result.payload.iss).toBe("https://example.com");
|
|
228
|
-
});
|
|
229
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { deprecate } from "./deprecate";
|
|
3
|
-
|
|
4
|
-
describe("deprecate", () => {
|
|
5
|
-
it("should warn once when called multiple times", () => {
|
|
6
|
-
const warn = vi.fn();
|
|
7
|
-
const logger = { warn } as any;
|
|
8
|
-
const fn = vi.fn();
|
|
9
|
-
const deprecatedFn = deprecate(fn, "test message", logger);
|
|
10
|
-
|
|
11
|
-
deprecatedFn();
|
|
12
|
-
deprecatedFn();
|
|
13
|
-
deprecatedFn();
|
|
14
|
-
|
|
15
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
16
|
-
expect(warn).toHaveBeenCalledWith("[Deprecation] test message");
|
|
17
|
-
expect(fn).toHaveBeenCalledTimes(3);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should use provided logger if available", () => {
|
|
21
|
-
const warn = vi.fn();
|
|
22
|
-
const logger = { warn } as any;
|
|
23
|
-
const fn = vi.fn();
|
|
24
|
-
const deprecatedFn = deprecate(fn, "test message", logger);
|
|
25
|
-
|
|
26
|
-
deprecatedFn();
|
|
27
|
-
|
|
28
|
-
expect(warn).toHaveBeenCalledWith("[Deprecation] test message");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("should fall back to console.warn if no logger provided", () => {
|
|
32
|
-
const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
33
|
-
const fn = vi.fn();
|
|
34
|
-
const deprecatedFn = deprecate(fn, "test message");
|
|
35
|
-
|
|
36
|
-
deprecatedFn();
|
|
37
|
-
|
|
38
|
-
expect(consoleWarn).toHaveBeenCalledWith("[Deprecation] test message");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("should pass arguments and return value correctly", () => {
|
|
42
|
-
const fn = vi.fn((a: number, b: number) => a + b);
|
|
43
|
-
const deprecatedFn = deprecate(fn, "test message", {
|
|
44
|
-
warn: vi.fn(),
|
|
45
|
-
} as any);
|
|
46
|
-
|
|
47
|
-
const result = deprecatedFn(1, 2);
|
|
48
|
-
|
|
49
|
-
expect(result).toBe(3);
|
|
50
|
-
expect(fn).toHaveBeenCalledWith(1, 2);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("should preserve this context", () => {
|
|
54
|
-
class TestClass {
|
|
55
|
-
value = 10;
|
|
56
|
-
method(a: number) {
|
|
57
|
-
return this.value + a;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const instance = new TestClass();
|
|
62
|
-
const originalMethod = instance.method;
|
|
63
|
-
instance.method = deprecate(originalMethod, "test message", {
|
|
64
|
-
warn: vi.fn(),
|
|
65
|
-
} as any);
|
|
66
|
-
|
|
67
|
-
const result = instance.method(5);
|
|
68
|
-
|
|
69
|
-
expect(result).toBe(15);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { isBrowserFetchRequest } from "./fetch-metadata";
|
|
3
|
-
|
|
4
|
-
describe("isBrowserFetchRequest", () => {
|
|
5
|
-
it("returns true for browser fetch requests", () => {
|
|
6
|
-
expect(
|
|
7
|
-
isBrowserFetchRequest(
|
|
8
|
-
new Headers({
|
|
9
|
-
"Sec-Fetch-Mode": "cors",
|
|
10
|
-
}),
|
|
11
|
-
),
|
|
12
|
-
).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("returns false for navigation requests", () => {
|
|
16
|
-
expect(
|
|
17
|
-
isBrowserFetchRequest(
|
|
18
|
-
new Headers({
|
|
19
|
-
"Sec-Fetch-Mode": "navigate",
|
|
20
|
-
}),
|
|
21
|
-
),
|
|
22
|
-
).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("returns false without fetch metadata", () => {
|
|
26
|
-
expect(isBrowserFetchRequest()).toBe(false);
|
|
27
|
-
});
|
|
28
|
-
});
|
package/src/utils/ip.test.ts
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { createRateLimitKey, isValidIP, normalizeIP } from "./ip";
|
|
3
|
-
|
|
4
|
-
describe("IP Normalization", () => {
|
|
5
|
-
describe("isValidIP", () => {
|
|
6
|
-
it("should validate IPv4 addresses", () => {
|
|
7
|
-
expect(isValidIP("192.168.1.1")).toBe(true);
|
|
8
|
-
expect(isValidIP("127.0.0.1")).toBe(true);
|
|
9
|
-
expect(isValidIP("0.0.0.0")).toBe(true);
|
|
10
|
-
expect(isValidIP("255.255.255.255")).toBe(true);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("should validate IPv6 addresses", () => {
|
|
14
|
-
expect(isValidIP("2001:db8::1")).toBe(true);
|
|
15
|
-
expect(isValidIP("::1")).toBe(true);
|
|
16
|
-
expect(isValidIP("::")).toBe(true);
|
|
17
|
-
expect(isValidIP("2001:0db8:0000:0000:0000:0000:0000:0001")).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it("should reject invalid IPs", () => {
|
|
21
|
-
expect(isValidIP("not-an-ip")).toBe(false);
|
|
22
|
-
expect(isValidIP("999.999.999.999")).toBe(false);
|
|
23
|
-
expect(isValidIP("gggg::1")).toBe(false);
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
describe("IPv4 Normalization", () => {
|
|
28
|
-
it("should return IPv4 addresses unchanged", () => {
|
|
29
|
-
expect(normalizeIP("192.168.1.1")).toBe("192.168.1.1");
|
|
30
|
-
expect(normalizeIP("127.0.0.1")).toBe("127.0.0.1");
|
|
31
|
-
expect(normalizeIP("10.0.0.1")).toBe("10.0.0.1");
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("IPv6 Normalization", () => {
|
|
36
|
-
it("should normalize compressed IPv6 to full form", () => {
|
|
37
|
-
expect(normalizeIP("2001:db8::1", { ipv6Subnet: 128 })).toBe(
|
|
38
|
-
"2001:0db8:0000:0000:0000:0000:0000:0001",
|
|
39
|
-
);
|
|
40
|
-
expect(normalizeIP("::1", { ipv6Subnet: 128 })).toBe(
|
|
41
|
-
"0000:0000:0000:0000:0000:0000:0000:0001",
|
|
42
|
-
);
|
|
43
|
-
expect(normalizeIP("::", { ipv6Subnet: 128 })).toBe(
|
|
44
|
-
"0000:0000:0000:0000:0000:0000:0000:0000",
|
|
45
|
-
);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should normalize uppercase to lowercase", () => {
|
|
49
|
-
expect(normalizeIP("2001:DB8::1", { ipv6Subnet: 128 })).toBe(
|
|
50
|
-
"2001:0db8:0000:0000:0000:0000:0000:0001",
|
|
51
|
-
);
|
|
52
|
-
expect(normalizeIP("2001:0DB8:ABCD:EF00::1", { ipv6Subnet: 128 })).toBe(
|
|
53
|
-
"2001:0db8:abcd:ef00:0000:0000:0000:0001",
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("should handle various IPv6 formats consistently", () => {
|
|
58
|
-
// All these represent the same address
|
|
59
|
-
const normalized = "2001:0db8:0000:0000:0000:0000:0000:0001";
|
|
60
|
-
expect(normalizeIP("2001:db8::1", { ipv6Subnet: 128 })).toBe(normalized);
|
|
61
|
-
expect(normalizeIP("2001:0db8:0:0:0:0:0:1", { ipv6Subnet: 128 })).toBe(
|
|
62
|
-
normalized,
|
|
63
|
-
);
|
|
64
|
-
expect(normalizeIP("2001:db8:0::1", { ipv6Subnet: 128 })).toBe(
|
|
65
|
-
normalized,
|
|
66
|
-
);
|
|
67
|
-
expect(normalizeIP("2001:0db8::0:0:0:1", { ipv6Subnet: 128 })).toBe(
|
|
68
|
-
normalized,
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("should handle IPv6 with :: at different positions", () => {
|
|
73
|
-
expect(
|
|
74
|
-
normalizeIP("2001:db8:85a3::8a2e:370:7334", { ipv6Subnet: 128 }),
|
|
75
|
-
).toBe("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
|
|
76
|
-
expect(normalizeIP("::ffff:192.0.2.1")).not.toContain("::");
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe("IPv4-mapped IPv6 Conversion", () => {
|
|
81
|
-
it("should convert IPv4-mapped IPv6 to IPv4", () => {
|
|
82
|
-
expect(normalizeIP("::ffff:192.0.2.1")).toBe("192.0.2.1");
|
|
83
|
-
expect(normalizeIP("::ffff:127.0.0.1")).toBe("127.0.0.1");
|
|
84
|
-
expect(normalizeIP("::FFFF:10.0.0.1")).toBe("10.0.0.1");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it("should handle hex-encoded IPv4 in mapped addresses", () => {
|
|
88
|
-
// ::ffff:c000:0201 = ::ffff:192.0.2.1 = 192.0.2.1
|
|
89
|
-
expect(normalizeIP("::ffff:c000:0201")).toBe("192.0.2.1");
|
|
90
|
-
// ::ffff:7f00:0001 = ::ffff:127.0.0.1 = 127.0.0.1
|
|
91
|
-
expect(normalizeIP("::ffff:7f00:0001")).toBe("127.0.0.1");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("should handle full form IPv4-mapped IPv6", () => {
|
|
95
|
-
expect(normalizeIP("0:0:0:0:0:ffff:192.0.2.1")).toBe("192.0.2.1");
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("IPv6 Subnet Support", () => {
|
|
100
|
-
it("should extract /64 subnet", () => {
|
|
101
|
-
/* cspell:disable-next-line */
|
|
102
|
-
const ip1 = normalizeIP("2001:db8:0:0:1234:5678:90ab:cdef", {
|
|
103
|
-
ipv6Subnet: 64,
|
|
104
|
-
});
|
|
105
|
-
const ip2 = normalizeIP("2001:db8:0:0:ffff:ffff:ffff:ffff", {
|
|
106
|
-
ipv6Subnet: 64,
|
|
107
|
-
});
|
|
108
|
-
// Both should have same /64 prefix
|
|
109
|
-
expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
|
|
110
|
-
expect(ip2).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
|
|
111
|
-
expect(ip1).toBe(ip2);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("should extract /48 subnet", () => {
|
|
115
|
-
/* cspell:disable-next-line */
|
|
116
|
-
const ip1 = normalizeIP("2001:db8:1234:5678:90ab:cdef:1234:5678", {
|
|
117
|
-
ipv6Subnet: 48,
|
|
118
|
-
});
|
|
119
|
-
const ip2 = normalizeIP("2001:db8:1234:ffff:ffff:ffff:ffff:ffff", {
|
|
120
|
-
ipv6Subnet: 48,
|
|
121
|
-
});
|
|
122
|
-
// Both should have same /48 prefix
|
|
123
|
-
expect(ip1).toBe("2001:0db8:1234:0000:0000:0000:0000:0000");
|
|
124
|
-
expect(ip2).toBe("2001:0db8:1234:0000:0000:0000:0000:0000");
|
|
125
|
-
expect(ip1).toBe(ip2);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("should extract /32 subnet", () => {
|
|
129
|
-
/* cspell:disable-next-line */
|
|
130
|
-
const ip1 = normalizeIP("2001:db8:1234:5678:90ab:cdef:1234:5678", {
|
|
131
|
-
ipv6Subnet: 32,
|
|
132
|
-
});
|
|
133
|
-
const ip2 = normalizeIP("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff", {
|
|
134
|
-
ipv6Subnet: 32,
|
|
135
|
-
});
|
|
136
|
-
// Both should have same /32 prefix
|
|
137
|
-
expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
|
|
138
|
-
expect(ip2).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
|
|
139
|
-
expect(ip1).toBe(ip2);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it("should handle /64 subnet by default", () => {
|
|
143
|
-
const ip1 = normalizeIP("2001:db8::1");
|
|
144
|
-
const ip2 = normalizeIP("2001:db8::1", { ipv6Subnet: 64 });
|
|
145
|
-
expect(ip1).toBe(ip2);
|
|
146
|
-
expect(ip1).toBe("2001:0db8:0000:0000:0000:0000:0000:0000");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("should not affect IPv4 addresses when ipv6Subnet is set", () => {
|
|
150
|
-
expect(normalizeIP("192.168.1.1", { ipv6Subnet: 64 })).toBe(
|
|
151
|
-
"192.168.1.1",
|
|
152
|
-
);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe("Rate Limit Key Creation", () => {
|
|
157
|
-
it("should create keys with separator", () => {
|
|
158
|
-
expect(createRateLimitKey("192.168.1.1", "/sign-in")).toBe(
|
|
159
|
-
"192.168.1.1|/sign-in",
|
|
160
|
-
);
|
|
161
|
-
expect(createRateLimitKey("2001:db8::1", "/api/auth")).toBe(
|
|
162
|
-
"2001:db8::1|/api/auth",
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("should prevent collision attacks", () => {
|
|
167
|
-
// Without separator: "192.0.2.1" + "/sign-in" = "192.0.2.1/sign-in"
|
|
168
|
-
// "192.0.2" + ".1/sign-in" = "192.0.2.1/sign-in"
|
|
169
|
-
// With separator: they're different
|
|
170
|
-
const key1 = createRateLimitKey("192.0.2.1", "/sign-in");
|
|
171
|
-
const key2 = createRateLimitKey("192.0.2", ".1/sign-in");
|
|
172
|
-
expect(key1).not.toBe(key2);
|
|
173
|
-
expect(key1).toBe("192.0.2.1|/sign-in");
|
|
174
|
-
expect(key2).toBe("192.0.2|.1/sign-in");
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
describe("Security: Bypass Prevention", () => {
|
|
179
|
-
it("should prevent IPv6 representation bypass", () => {
|
|
180
|
-
// Attacker tries different representations of same address
|
|
181
|
-
const representations = [
|
|
182
|
-
"2001:db8::1",
|
|
183
|
-
"2001:DB8::1",
|
|
184
|
-
"2001:0db8::1",
|
|
185
|
-
"2001:db8:0::1",
|
|
186
|
-
"2001:0db8:0:0:0:0:0:1",
|
|
187
|
-
"2001:db8::0:1",
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
const normalized = representations.map((ip) =>
|
|
191
|
-
normalizeIP(ip, { ipv6Subnet: 128 }),
|
|
192
|
-
);
|
|
193
|
-
// All should normalize to the same value
|
|
194
|
-
const uniqueValues = new Set(normalized);
|
|
195
|
-
expect(uniqueValues.size).toBe(1);
|
|
196
|
-
expect(normalized[0]).toBe("2001:0db8:0000:0000:0000:0000:0000:0001");
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("should prevent IPv4-mapped bypass", () => {
|
|
200
|
-
// Attacker switches between IPv4 and IPv4-mapped IPv6
|
|
201
|
-
const ip1 = normalizeIP("192.0.2.1");
|
|
202
|
-
const ip2 = normalizeIP("::ffff:192.0.2.1");
|
|
203
|
-
const ip3 = normalizeIP("::FFFF:192.0.2.1");
|
|
204
|
-
const ip4 = normalizeIP("::ffff:c000:0201");
|
|
205
|
-
|
|
206
|
-
// All should normalize to the same IPv4
|
|
207
|
-
expect(ip1).toBe("192.0.2.1");
|
|
208
|
-
expect(ip2).toBe("192.0.2.1");
|
|
209
|
-
expect(ip3).toBe("192.0.2.1");
|
|
210
|
-
expect(ip4).toBe("192.0.2.1");
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
it("should group IPv6 subnet attacks", () => {
|
|
214
|
-
// Attacker rotates through addresses in their /64 allocation
|
|
215
|
-
const attackIPs = [
|
|
216
|
-
"2001:db8:abcd:1234:0000:0000:0000:0001",
|
|
217
|
-
"2001:db8:abcd:1234:1111:2222:3333:4444",
|
|
218
|
-
"2001:db8:abcd:1234:ffff:ffff:ffff:ffff",
|
|
219
|
-
"2001:db8:abcd:1234:aaaa:bbbb:cccc:dddd",
|
|
220
|
-
];
|
|
221
|
-
|
|
222
|
-
const normalized = attackIPs.map((ip) =>
|
|
223
|
-
normalizeIP(ip, { ipv6Subnet: 64 }),
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
// All should map to same /64 subnet
|
|
227
|
-
const uniqueValues = new Set(normalized);
|
|
228
|
-
expect(uniqueValues.size).toBe(1);
|
|
229
|
-
expect(normalized[0]).toBe("2001:0db8:abcd:1234:0000:0000:0000:0000");
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe("Edge Cases", () => {
|
|
234
|
-
it("should handle localhost addresses", () => {
|
|
235
|
-
expect(normalizeIP("127.0.0.1")).toBe("127.0.0.1");
|
|
236
|
-
expect(normalizeIP("::1", { ipv6Subnet: 128 })).toBe(
|
|
237
|
-
"0000:0000:0000:0000:0000:0000:0000:0001",
|
|
238
|
-
);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it("should handle all-zeros address", () => {
|
|
242
|
-
expect(normalizeIP("0.0.0.0")).toBe("0.0.0.0");
|
|
243
|
-
expect(normalizeIP("::", { ipv6Subnet: 128 })).toBe(
|
|
244
|
-
"0000:0000:0000:0000:0000:0000:0000:0000",
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it("should handle link-local addresses", () => {
|
|
249
|
-
expect(normalizeIP("169.254.0.1")).toBe("169.254.0.1");
|
|
250
|
-
expect(normalizeIP("fe80::1", { ipv6Subnet: 128 })).toBe(
|
|
251
|
-
"fe80:0000:0000:0000:0000:0000:0000:0001",
|
|
252
|
-
);
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
});
|