@authticon/client 0.0.0-beta1
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/Auth.d.ts +19 -0
- package/dist/Auth.d.ts.map +1 -0
- package/dist/Auth.js +24 -0
- package/dist/Auth.js.map +1 -0
- package/dist/Authticon.d.ts +21 -0
- package/dist/Authticon.d.ts.map +1 -0
- package/dist/Authticon.js +30 -0
- package/dist/Authticon.js.map +1 -0
- package/dist/BrowserCookieAdapter.d.ts +13 -0
- package/dist/BrowserCookieAdapter.d.ts.map +1 -0
- package/dist/BrowserCookieAdapter.js +18 -0
- package/dist/BrowserCookieAdapter.js.map +1 -0
- package/dist/BrowserCookieAdapter.test.d.ts +2 -0
- package/dist/BrowserCookieAdapter.test.d.ts.map +1 -0
- package/dist/BrowserCookieAdapter.test.js +145 -0
- package/dist/BrowserCookieAdapter.test.js.map +1 -0
- package/dist/RemixCookieAdapter.d.ts +12 -0
- package/dist/RemixCookieAdapter.d.ts.map +1 -0
- package/dist/RemixCookieAdapter.js +25 -0
- package/dist/RemixCookieAdapter.js.map +1 -0
- package/dist/RemixCookieAdapter.test.d.ts +2 -0
- package/dist/RemixCookieAdapter.test.d.ts.map +1 -0
- package/dist/RemixCookieAdapter.test.js +136 -0
- package/dist/RemixCookieAdapter.test.js.map +1 -0
- package/dist/TokenManager.d.ts +22 -0
- package/dist/TokenManager.d.ts.map +1 -0
- package/dist/TokenManager.js +45 -0
- package/dist/TokenManager.js.map +1 -0
- package/dist/TokenManager.test.d.ts +2 -0
- package/dist/TokenManager.test.d.ts.map +1 -0
- package/dist/TokenManager.test.js +118 -0
- package/dist/TokenManager.test.js.map +1 -0
- package/dist/TokenStorage.d.ts +17 -0
- package/dist/TokenStorage.d.ts.map +1 -0
- package/dist/TokenStorage.js +68 -0
- package/dist/TokenStorage.js.map +1 -0
- package/dist/TokenStorage.test.d.ts +2 -0
- package/dist/TokenStorage.test.d.ts.map +1 -0
- package/dist/TokenStorage.test.js +179 -0
- package/dist/TokenStorage.test.js.map +1 -0
- package/dist/TokenVerifier.d.ts +13 -0
- package/dist/TokenVerifier.d.ts.map +1 -0
- package/dist/TokenVerifier.js +61 -0
- package/dist/TokenVerifier.js.map +1 -0
- package/dist/TokenVerifier.test.d.ts +2 -0
- package/dist/TokenVerifier.test.d.ts.map +1 -0
- package/dist/TokenVerifier.test.js +117 -0
- package/dist/TokenVerifier.test.js.map +1 -0
- package/dist/Users.d.ts +57 -0
- package/dist/Users.d.ts.map +1 -0
- package/dist/Users.js +54 -0
- package/dist/Users.js.map +1 -0
- package/dist/cookie-utils.d.ts +5 -0
- package/dist/cookie-utils.d.ts.map +1 -0
- package/dist/cookie-utils.js +33 -0
- package/dist/cookie-utils.js.map +1 -0
- package/dist/generated/client/client.gen.d.ts +3 -0
- package/dist/generated/client/client.gen.d.ts.map +1 -0
- package/dist/generated/client/client.gen.js +135 -0
- package/dist/generated/client/client.gen.js.map +1 -0
- package/dist/generated/client/index.d.ts +9 -0
- package/dist/generated/client/index.d.ts.map +1 -0
- package/dist/generated/client/index.js +7 -0
- package/dist/generated/client/index.js.map +1 -0
- package/dist/generated/client/types.gen.d.ts +89 -0
- package/dist/generated/client/types.gen.d.ts.map +1 -0
- package/dist/generated/client/types.gen.js +3 -0
- package/dist/generated/client/types.gen.js.map +1 -0
- package/dist/generated/client/utils.gen.d.ts +15 -0
- package/dist/generated/client/utils.gen.d.ts.map +1 -0
- package/dist/generated/client/utils.gen.js +169 -0
- package/dist/generated/client/utils.gen.js.map +1 -0
- package/dist/generated/client.gen.d.ts +13 -0
- package/dist/generated/client.gen.d.ts.map +1 -0
- package/dist/generated/client.gen.js +4 -0
- package/dist/generated/client.gen.js.map +1 -0
- package/dist/generated/core/auth.gen.d.ts +19 -0
- package/dist/generated/core/auth.gen.d.ts.map +1 -0
- package/dist/generated/core/auth.gen.js +15 -0
- package/dist/generated/core/auth.gen.js.map +1 -0
- package/dist/generated/core/bodySerializer.gen.d.ts +26 -0
- package/dist/generated/core/bodySerializer.gen.d.ts.map +1 -0
- package/dist/generated/core/bodySerializer.gen.js +58 -0
- package/dist/generated/core/bodySerializer.gen.js.map +1 -0
- package/dist/generated/core/params.gen.d.ts +44 -0
- package/dist/generated/core/params.gen.d.ts.map +1 -0
- package/dist/generated/core/params.gen.js +101 -0
- package/dist/generated/core/params.gen.js.map +1 -0
- package/dist/generated/core/pathSerializer.gen.d.ts +34 -0
- package/dist/generated/core/pathSerializer.gen.d.ts.map +1 -0
- package/dist/generated/core/pathSerializer.gen.js +107 -0
- package/dist/generated/core/pathSerializer.gen.js.map +1 -0
- package/dist/generated/core/queryKeySerializer.gen.d.ts +19 -0
- package/dist/generated/core/queryKeySerializer.gen.d.ts.map +1 -0
- package/dist/generated/core/queryKeySerializer.gen.js +93 -0
- package/dist/generated/core/queryKeySerializer.gen.js.map +1 -0
- package/dist/generated/core/serverSentEvents.gen.d.ts +72 -0
- package/dist/generated/core/serverSentEvents.gen.d.ts.map +1 -0
- package/dist/generated/core/serverSentEvents.gen.js +134 -0
- package/dist/generated/core/serverSentEvents.gen.js.map +1 -0
- package/dist/generated/core/types.gen.d.ts +79 -0
- package/dist/generated/core/types.gen.d.ts.map +1 -0
- package/dist/generated/core/types.gen.js +3 -0
- package/dist/generated/core/types.gen.js.map +1 -0
- package/dist/generated/core/utils.gen.d.ts +20 -0
- package/dist/generated/core/utils.gen.d.ts.map +1 -0
- package/dist/generated/core/utils.gen.js +88 -0
- package/dist/generated/core/utils.gen.js.map +1 -0
- package/dist/generated/index.d.ts +3 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +3 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/generated/sdk.gen.d.ts +67 -0
- package/dist/generated/sdk.gen.d.ts.map +1 -0
- package/dist/generated/sdk.gen.js +402 -0
- package/dist/generated/sdk.gen.js.map +1 -0
- package/dist/generated/types.gen.d.ts +1672 -0
- package/dist/generated/types.gen.d.ts.map +1 -0
- package/dist/generated/types.gen.js +3 -0
- package/dist/generated/types.gen.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { TokenManager } from "./TokenManager.js";
|
|
3
|
+
vi.mock("./TokenVerifier.js", () => ({
|
|
4
|
+
TokenVerifier: class {
|
|
5
|
+
verifyToken = vi.fn().mockResolvedValue({ sub: "user-1", exp: 9999999999 });
|
|
6
|
+
clearKeyCache = vi.fn();
|
|
7
|
+
},
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("./generated/index.js", () => ({
|
|
10
|
+
postApiV1AuthTokenRefresh: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
const createMockClient = () => ({});
|
|
13
|
+
const createMockAdapter = () => ({
|
|
14
|
+
get: vi.fn().mockReturnValue(null),
|
|
15
|
+
set: vi.fn(),
|
|
16
|
+
remove: vi.fn(),
|
|
17
|
+
});
|
|
18
|
+
describe("TokenManager", () => {
|
|
19
|
+
describe("without storage (no CookieAdapter)", () => {
|
|
20
|
+
let manager;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
manager = new TokenManager(createMockClient(), {
|
|
23
|
+
jwksUrl: "https://example.com/.well-known/jwks.json",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it("should verify a token", async () => {
|
|
27
|
+
const payload = await manager.verifyToken("some-jwt");
|
|
28
|
+
expect(payload).toEqual({ sub: "user-1", exp: 9999999999 });
|
|
29
|
+
});
|
|
30
|
+
it("should clear key cache", () => {
|
|
31
|
+
expect(() => manager.clearKeyCache()).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
it("should report hasStorage as false", () => {
|
|
34
|
+
expect(manager.hasStorage()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it("should throw on save without adapter", () => {
|
|
37
|
+
expect(() => manager.save({ accessToken: "a", refreshToken: "r", deviceId: "d" }))
|
|
38
|
+
.toThrow("TokenStorage is not configured");
|
|
39
|
+
});
|
|
40
|
+
it("should throw on getAccessToken without adapter", () => {
|
|
41
|
+
expect(() => manager.getAccessToken())
|
|
42
|
+
.toThrow("TokenStorage is not configured");
|
|
43
|
+
});
|
|
44
|
+
it("should throw on getRefreshToken without adapter", () => {
|
|
45
|
+
expect(() => manager.getRefreshToken())
|
|
46
|
+
.toThrow("TokenStorage is not configured");
|
|
47
|
+
});
|
|
48
|
+
it("should throw on getAll without adapter", () => {
|
|
49
|
+
expect(() => manager.getAll())
|
|
50
|
+
.toThrow("TokenStorage is not configured");
|
|
51
|
+
});
|
|
52
|
+
it("should throw on clear without adapter", () => {
|
|
53
|
+
expect(() => manager.clear())
|
|
54
|
+
.toThrow("TokenStorage is not configured");
|
|
55
|
+
});
|
|
56
|
+
it("should throw on verifyStoredAccessToken without adapter", async () => {
|
|
57
|
+
await expect(manager.verifyStoredAccessToken())
|
|
58
|
+
.rejects.toThrow("TokenStorage is not configured");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("with storage (CookieAdapter provided)", () => {
|
|
62
|
+
let manager;
|
|
63
|
+
let adapter;
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
adapter = createMockAdapter();
|
|
66
|
+
manager = new TokenManager(createMockClient(), {
|
|
67
|
+
jwksUrl: "https://example.com/.well-known/jwks.json",
|
|
68
|
+
cookies: adapter,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
it("should report hasStorage as true", () => {
|
|
72
|
+
expect(manager.hasStorage()).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
it("should save tokens via adapter", () => {
|
|
75
|
+
manager.save({ accessToken: "abc", refreshToken: "xyz", deviceId: "dev-1" });
|
|
76
|
+
expect(adapter.set).toHaveBeenCalledTimes(3);
|
|
77
|
+
expect(adapter.set).toHaveBeenCalledWith("access_token", "abc", expect.any(Object));
|
|
78
|
+
expect(adapter.set).toHaveBeenCalledWith("refresh_token", "xyz", expect.any(Object));
|
|
79
|
+
});
|
|
80
|
+
it("should get access token via adapter", () => {
|
|
81
|
+
vi.mocked(adapter.get).mockReturnValue("stored-access");
|
|
82
|
+
expect(manager.getAccessToken()).toBe("stored-access");
|
|
83
|
+
expect(adapter.get).toHaveBeenCalledWith("access_token");
|
|
84
|
+
});
|
|
85
|
+
it("should get refresh token via adapter", () => {
|
|
86
|
+
vi.mocked(adapter.get).mockReturnValue("stored-refresh");
|
|
87
|
+
expect(manager.getRefreshToken()).toBe("stored-refresh");
|
|
88
|
+
});
|
|
89
|
+
it("should get all tokens", () => {
|
|
90
|
+
vi.mocked(adapter.get)
|
|
91
|
+
.mockReturnValueOnce("access-val")
|
|
92
|
+
.mockReturnValueOnce("refresh-val")
|
|
93
|
+
.mockReturnValueOnce("device-val");
|
|
94
|
+
expect(manager.getAll()).toEqual({
|
|
95
|
+
accessToken: "access-val",
|
|
96
|
+
refreshToken: "refresh-val",
|
|
97
|
+
deviceId: "device-val",
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it("should return null from getAll when tokens are missing", () => {
|
|
101
|
+
expect(manager.getAll()).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
it("should clear tokens via adapter", () => {
|
|
104
|
+
manager.clear();
|
|
105
|
+
expect(adapter.remove).toHaveBeenCalledTimes(2);
|
|
106
|
+
});
|
|
107
|
+
it("should verify stored access token", async () => {
|
|
108
|
+
vi.mocked(adapter.get).mockReturnValue("stored-jwt");
|
|
109
|
+
const payload = await manager.verifyStoredAccessToken();
|
|
110
|
+
expect(payload).toEqual({ sub: "user-1", exp: 9999999999 });
|
|
111
|
+
});
|
|
112
|
+
it("should return null from verifyStoredAccessToken when no token stored", async () => {
|
|
113
|
+
const payload = await manager.verifyStoredAccessToken();
|
|
114
|
+
expect(payload).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=TokenManager.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenManager.test.js","sourceRoot":"","sources":["../src/TokenManager.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CookieAdapter, TokenPair, TokenStorageOptions } from "./types.js";
|
|
2
|
+
export declare class TokenStorage {
|
|
3
|
+
private readonly cookies;
|
|
4
|
+
private readonly accessName;
|
|
5
|
+
private readonly refreshName;
|
|
6
|
+
private readonly deviceIdName;
|
|
7
|
+
private readonly setOptions;
|
|
8
|
+
private readonly removeOptions;
|
|
9
|
+
constructor(cookies: CookieAdapter, options?: TokenStorageOptions);
|
|
10
|
+
readonly save: (tokens: TokenPair) => void;
|
|
11
|
+
readonly getAccessToken: () => string | null;
|
|
12
|
+
readonly getRefreshToken: () => string | null;
|
|
13
|
+
readonly getDeviceId: () => string | null;
|
|
14
|
+
readonly getAll: () => TokenPair | null;
|
|
15
|
+
readonly clear: () => void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=TokenStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenStorage.d.ts","sourceRoot":"","sources":["../src/TokenStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAGb,SAAS,EACT,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAQpB,qBAAa,YAAY;IAYrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAX1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAIzB;IACF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;gBAGjC,OAAO,EAAE,aAAa,EACvC,OAAO,GAAE,mBAAwB;IAsCnC,QAAQ,CAAC,IAAI,GAAI,QAAQ,SAAS,KAAG,IAAI,CAIvC;IAEF,QAAQ,CAAC,cAAc,QAAO,MAAM,GAAG,IAAI,CACP;IAEpC,QAAQ,CAAC,eAAe,QAAO,MAAM,GAAG,IAAI,CACP;IAErC,QAAQ,CAAC,WAAW,QAAO,MAAM,GAAG,IAAI,CACF;IAEtC,QAAQ,CAAC,MAAM,QAAO,SAAS,GAAG,IAAI,CAMpC;IAEF,QAAQ,CAAC,KAAK,QAAO,IAAI,CAGvB;CACH"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const DEFAULT_ACCESS_TOKEN_NAME = "access_token";
|
|
2
|
+
const DEFAULT_REFRESH_TOKEN_NAME = "refresh_token";
|
|
3
|
+
const DEFAULT_DEVICE_ID_NAME = "device_id";
|
|
4
|
+
const DEFAULT_ACCESS_TOKEN_MAX_AGE = 900;
|
|
5
|
+
const DEFAULT_REFRESH_TOKEN_MAX_AGE = 2_592_000;
|
|
6
|
+
export class TokenStorage {
|
|
7
|
+
cookies;
|
|
8
|
+
accessName;
|
|
9
|
+
refreshName;
|
|
10
|
+
deviceIdName;
|
|
11
|
+
setOptions;
|
|
12
|
+
removeOptions;
|
|
13
|
+
constructor(cookies, options = {}) {
|
|
14
|
+
this.cookies = cookies;
|
|
15
|
+
this.accessName = options.accessTokenName ?? DEFAULT_ACCESS_TOKEN_NAME;
|
|
16
|
+
this.refreshName = options.refreshTokenName ?? DEFAULT_REFRESH_TOKEN_NAME;
|
|
17
|
+
this.deviceIdName = options.deviceIdName ?? DEFAULT_DEVICE_ID_NAME;
|
|
18
|
+
const path = options.path ?? "/";
|
|
19
|
+
const domain = options.domain;
|
|
20
|
+
const secure = options.secure ?? true;
|
|
21
|
+
const sameSite = options.sameSite ?? "Lax";
|
|
22
|
+
this.setOptions = {
|
|
23
|
+
access: {
|
|
24
|
+
path,
|
|
25
|
+
domain,
|
|
26
|
+
secure,
|
|
27
|
+
sameSite,
|
|
28
|
+
maxAge: options.accessTokenMaxAge ?? DEFAULT_ACCESS_TOKEN_MAX_AGE,
|
|
29
|
+
},
|
|
30
|
+
refresh: {
|
|
31
|
+
path,
|
|
32
|
+
domain,
|
|
33
|
+
secure,
|
|
34
|
+
sameSite,
|
|
35
|
+
maxAge: options.refreshTokenMaxAge ?? DEFAULT_REFRESH_TOKEN_MAX_AGE,
|
|
36
|
+
},
|
|
37
|
+
deviceId: {
|
|
38
|
+
path,
|
|
39
|
+
domain,
|
|
40
|
+
secure,
|
|
41
|
+
sameSite,
|
|
42
|
+
maxAge: options.refreshTokenMaxAge ?? DEFAULT_REFRESH_TOKEN_MAX_AGE,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
this.removeOptions = { path, domain };
|
|
46
|
+
}
|
|
47
|
+
save = (tokens) => {
|
|
48
|
+
this.cookies.set(this.accessName, tokens.accessToken, this.setOptions.access);
|
|
49
|
+
this.cookies.set(this.refreshName, tokens.refreshToken, this.setOptions.refresh);
|
|
50
|
+
this.cookies.set(this.deviceIdName, tokens.deviceId, this.setOptions.deviceId);
|
|
51
|
+
};
|
|
52
|
+
getAccessToken = () => this.cookies.get(this.accessName);
|
|
53
|
+
getRefreshToken = () => this.cookies.get(this.refreshName);
|
|
54
|
+
getDeviceId = () => this.cookies.get(this.deviceIdName);
|
|
55
|
+
getAll = () => {
|
|
56
|
+
const accessToken = this.getAccessToken();
|
|
57
|
+
const refreshToken = this.getRefreshToken();
|
|
58
|
+
const deviceId = this.getDeviceId();
|
|
59
|
+
if (accessToken === null || refreshToken === null || deviceId === null)
|
|
60
|
+
return null;
|
|
61
|
+
return { accessToken, refreshToken, deviceId };
|
|
62
|
+
};
|
|
63
|
+
clear = () => {
|
|
64
|
+
this.cookies.remove(this.accessName, this.removeOptions);
|
|
65
|
+
this.cookies.remove(this.refreshName, this.removeOptions);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=TokenStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenStorage.js","sourceRoot":"","sources":["../src/TokenStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenStorage.test.d.ts","sourceRoot":"","sources":["../src/TokenStorage.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { TokenStorage } from "./TokenStorage.js";
|
|
3
|
+
const createMockAdapter = () => ({
|
|
4
|
+
get: vi.fn().mockReturnValue(null),
|
|
5
|
+
set: vi.fn(),
|
|
6
|
+
remove: vi.fn(),
|
|
7
|
+
});
|
|
8
|
+
describe("TokenStorage", () => {
|
|
9
|
+
let adapter;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
adapter = createMockAdapter();
|
|
12
|
+
});
|
|
13
|
+
describe("save", () => {
|
|
14
|
+
it("should set both tokens via adapter with correct options", () => {
|
|
15
|
+
const storage = new TokenStorage(adapter);
|
|
16
|
+
storage.save({ accessToken: "abc", refreshToken: "xyz", deviceId: "dev-1" });
|
|
17
|
+
expect(adapter.set).toHaveBeenCalledWith("access_token", "abc", {
|
|
18
|
+
path: "/",
|
|
19
|
+
domain: undefined,
|
|
20
|
+
secure: true,
|
|
21
|
+
sameSite: "Lax",
|
|
22
|
+
maxAge: 900,
|
|
23
|
+
});
|
|
24
|
+
expect(adapter.set).toHaveBeenCalledWith("refresh_token", "xyz", {
|
|
25
|
+
path: "/",
|
|
26
|
+
domain: undefined,
|
|
27
|
+
secure: true,
|
|
28
|
+
sameSite: "Lax",
|
|
29
|
+
maxAge: 2_592_000,
|
|
30
|
+
});
|
|
31
|
+
expect(adapter.set).toHaveBeenCalledWith("device_id", "dev-1", {
|
|
32
|
+
path: "/",
|
|
33
|
+
domain: undefined,
|
|
34
|
+
secure: true,
|
|
35
|
+
sameSite: "Lax",
|
|
36
|
+
maxAge: 2_592_000,
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it("should use custom cookie names", () => {
|
|
40
|
+
const storage = new TokenStorage(adapter, {
|
|
41
|
+
accessTokenName: "at",
|
|
42
|
+
refreshTokenName: "rt",
|
|
43
|
+
});
|
|
44
|
+
storage.save({ accessToken: "a1", refreshToken: "r1", deviceId: "dev-1" });
|
|
45
|
+
expect(adapter.set).toHaveBeenCalledWith("at", "a1", expect.any(Object));
|
|
46
|
+
expect(adapter.set).toHaveBeenCalledWith("rt", "r1", expect.any(Object));
|
|
47
|
+
});
|
|
48
|
+
it("should pass custom cookie options", () => {
|
|
49
|
+
const storage = new TokenStorage(adapter, {
|
|
50
|
+
path: "/app",
|
|
51
|
+
domain: ".example.com",
|
|
52
|
+
secure: false,
|
|
53
|
+
sameSite: "Strict",
|
|
54
|
+
accessTokenMaxAge: 60,
|
|
55
|
+
refreshTokenMaxAge: 120,
|
|
56
|
+
});
|
|
57
|
+
storage.save({ accessToken: "a", refreshToken: "r", deviceId: "dev-1" });
|
|
58
|
+
expect(adapter.set).toHaveBeenCalledWith("access_token", "a", {
|
|
59
|
+
path: "/app",
|
|
60
|
+
domain: ".example.com",
|
|
61
|
+
secure: false,
|
|
62
|
+
sameSite: "Strict",
|
|
63
|
+
maxAge: 60,
|
|
64
|
+
});
|
|
65
|
+
expect(adapter.set).toHaveBeenCalledWith("refresh_token", "r", {
|
|
66
|
+
path: "/app",
|
|
67
|
+
domain: ".example.com",
|
|
68
|
+
secure: false,
|
|
69
|
+
sameSite: "Strict",
|
|
70
|
+
maxAge: 120,
|
|
71
|
+
});
|
|
72
|
+
expect(adapter.set).toHaveBeenCalledWith("device_id", "dev-1", {
|
|
73
|
+
path: "/app",
|
|
74
|
+
domain: ".example.com",
|
|
75
|
+
secure: false,
|
|
76
|
+
sameSite: "Strict",
|
|
77
|
+
maxAge: 120,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("getAccessToken", () => {
|
|
82
|
+
it("should delegate to adapter.get with access token name", () => {
|
|
83
|
+
vi.mocked(adapter.get).mockReturnValue("token123");
|
|
84
|
+
const storage = new TokenStorage(adapter);
|
|
85
|
+
expect(storage.getAccessToken()).toBe("token123");
|
|
86
|
+
expect(adapter.get).toHaveBeenCalledWith("access_token");
|
|
87
|
+
});
|
|
88
|
+
it("should return null when adapter returns null", () => {
|
|
89
|
+
const storage = new TokenStorage(adapter);
|
|
90
|
+
expect(storage.getAccessToken()).toBeNull();
|
|
91
|
+
});
|
|
92
|
+
it("should use custom access token name", () => {
|
|
93
|
+
const storage = new TokenStorage(adapter, {
|
|
94
|
+
accessTokenName: "my_at",
|
|
95
|
+
});
|
|
96
|
+
storage.getAccessToken();
|
|
97
|
+
expect(adapter.get).toHaveBeenCalledWith("my_at");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe("getRefreshToken", () => {
|
|
101
|
+
it("should delegate to adapter.get with refresh token name", () => {
|
|
102
|
+
vi.mocked(adapter.get).mockReturnValue("refresh123");
|
|
103
|
+
const storage = new TokenStorage(adapter);
|
|
104
|
+
expect(storage.getRefreshToken()).toBe("refresh123");
|
|
105
|
+
expect(adapter.get).toHaveBeenCalledWith("refresh_token");
|
|
106
|
+
});
|
|
107
|
+
it("should return null when adapter returns null", () => {
|
|
108
|
+
const storage = new TokenStorage(adapter);
|
|
109
|
+
expect(storage.getRefreshToken()).toBeNull();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("getAll", () => {
|
|
113
|
+
it("should return all tokens when present", () => {
|
|
114
|
+
vi.mocked(adapter.get)
|
|
115
|
+
.mockReturnValueOnce("access-val")
|
|
116
|
+
.mockReturnValueOnce("refresh-val")
|
|
117
|
+
.mockReturnValueOnce("device-val");
|
|
118
|
+
const storage = new TokenStorage(adapter);
|
|
119
|
+
expect(storage.getAll()).toEqual({
|
|
120
|
+
accessToken: "access-val",
|
|
121
|
+
refreshToken: "refresh-val",
|
|
122
|
+
deviceId: "device-val",
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
it("should return null when access token is missing", () => {
|
|
126
|
+
vi.mocked(adapter.get)
|
|
127
|
+
.mockReturnValueOnce(null)
|
|
128
|
+
.mockReturnValueOnce("refresh-val")
|
|
129
|
+
.mockReturnValueOnce("device-val");
|
|
130
|
+
const storage = new TokenStorage(adapter);
|
|
131
|
+
expect(storage.getAll()).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
it("should return null when refresh token is missing", () => {
|
|
134
|
+
vi.mocked(adapter.get)
|
|
135
|
+
.mockReturnValueOnce("access-val")
|
|
136
|
+
.mockReturnValueOnce(null)
|
|
137
|
+
.mockReturnValueOnce("device-val");
|
|
138
|
+
const storage = new TokenStorage(adapter);
|
|
139
|
+
expect(storage.getAll()).toBeNull();
|
|
140
|
+
});
|
|
141
|
+
it("should return null when device id is missing", () => {
|
|
142
|
+
vi.mocked(adapter.get)
|
|
143
|
+
.mockReturnValueOnce("access-val")
|
|
144
|
+
.mockReturnValueOnce("refresh-val")
|
|
145
|
+
.mockReturnValueOnce(null);
|
|
146
|
+
const storage = new TokenStorage(adapter);
|
|
147
|
+
expect(storage.getAll()).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
it("should return null when all tokens are missing", () => {
|
|
150
|
+
const storage = new TokenStorage(adapter);
|
|
151
|
+
expect(storage.getAll()).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe("clear", () => {
|
|
155
|
+
it("should remove both tokens via adapter", () => {
|
|
156
|
+
const storage = new TokenStorage(adapter);
|
|
157
|
+
storage.clear();
|
|
158
|
+
expect(adapter.remove).toHaveBeenCalledWith("access_token", {
|
|
159
|
+
path: "/",
|
|
160
|
+
domain: undefined,
|
|
161
|
+
});
|
|
162
|
+
expect(adapter.remove).toHaveBeenCalledWith("refresh_token", {
|
|
163
|
+
path: "/",
|
|
164
|
+
domain: undefined,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it("should pass domain to remove options", () => {
|
|
168
|
+
const storage = new TokenStorage(adapter, {
|
|
169
|
+
domain: ".example.com",
|
|
170
|
+
});
|
|
171
|
+
storage.clear();
|
|
172
|
+
expect(adapter.remove).toHaveBeenCalledWith("access_token", {
|
|
173
|
+
path: "/",
|
|
174
|
+
domain: ".example.com",
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=TokenStorage.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenStorage.test.js","sourceRoot":"","sources":["../src/TokenStorage.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type JWTPayload } from "jose";
|
|
2
|
+
export declare class TokenVerifier {
|
|
3
|
+
private readonly jwksUrl;
|
|
4
|
+
private readonly cacheTtlMs;
|
|
5
|
+
private cached;
|
|
6
|
+
constructor(jwksUrl: string, cacheTtlMs?: number);
|
|
7
|
+
private readonly fetchJwks;
|
|
8
|
+
private readonly getCachedOrFetch;
|
|
9
|
+
private readonly resolveKey;
|
|
10
|
+
readonly verifyToken: (token: string) => Promise<JWTPayload>;
|
|
11
|
+
readonly clearKeyCache: () => void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=TokenVerifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenVerifier.d.ts","sourceRoot":"","sources":["../src/TokenVerifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,UAAU,EAGhB,MAAM,MAAM,CAAC;AAmCd,qBAAa,aAAa;IAItB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAJ7B,OAAO,CAAC,MAAM,CAA2B;gBAGtB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,MAA6B;IAG5D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAOxB;IAEF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAO/B;IAEF,OAAO,CAAC,QAAQ,CAAC,UAAU,CAczB;IAEF,QAAQ,CAAC,WAAW,GAAU,OAAO,MAAM,KAAG,OAAO,CAAC,UAAU,CAAC,CAS/D;IAEF,QAAQ,CAAC,aAAa,QAAO,IAAI,CAE/B;CACH"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jwtVerify, importJWK, decodeProtectedHeader, } from "jose";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
const DEFAULT_CACHE_TTL_MS = 3_600_000;
|
|
4
|
+
const importKeysFromJwks = async (jwks) => {
|
|
5
|
+
const keysWithKid = jwks.filter((jwk) => typeof jwk.kid === "string");
|
|
6
|
+
const entries = await Promise.all(keysWithKid.map(async (jwk) => {
|
|
7
|
+
const imported = await importJWK(jwk);
|
|
8
|
+
if (imported instanceof Uint8Array) {
|
|
9
|
+
throw new Error(`Symmetric key (kid: ${jwk.kid}) is not supported`);
|
|
10
|
+
}
|
|
11
|
+
return [jwk.kid, imported];
|
|
12
|
+
}));
|
|
13
|
+
return new Map(entries);
|
|
14
|
+
};
|
|
15
|
+
export class TokenVerifier {
|
|
16
|
+
jwksUrl;
|
|
17
|
+
cacheTtlMs;
|
|
18
|
+
cached = null;
|
|
19
|
+
constructor(jwksUrl, cacheTtlMs = DEFAULT_CACHE_TTL_MS) {
|
|
20
|
+
this.jwksUrl = jwksUrl;
|
|
21
|
+
this.cacheTtlMs = cacheTtlMs;
|
|
22
|
+
}
|
|
23
|
+
fetchJwks = async () => {
|
|
24
|
+
const { data } = await axios.get(this.jwksUrl);
|
|
25
|
+
const keys = await importKeysFromJwks(data.keys);
|
|
26
|
+
this.cached = { keys, fetchedAt: Date.now() };
|
|
27
|
+
return keys;
|
|
28
|
+
};
|
|
29
|
+
getCachedOrFetch = async () => {
|
|
30
|
+
if (this.cached && Date.now() - this.cached.fetchedAt < this.cacheTtlMs) {
|
|
31
|
+
return this.cached.keys;
|
|
32
|
+
}
|
|
33
|
+
return this.fetchJwks();
|
|
34
|
+
};
|
|
35
|
+
resolveKey = async (kid) => {
|
|
36
|
+
const keys = await this.getCachedOrFetch();
|
|
37
|
+
const key = keys.get(kid);
|
|
38
|
+
if (key)
|
|
39
|
+
return key;
|
|
40
|
+
this.cached = null;
|
|
41
|
+
const freshKeys = await this.fetchJwks();
|
|
42
|
+
const freshKey = freshKeys.get(kid);
|
|
43
|
+
if (!freshKey) {
|
|
44
|
+
throw new Error(`Key with kid "${kid}" not found in JWKS`);
|
|
45
|
+
}
|
|
46
|
+
return freshKey;
|
|
47
|
+
};
|
|
48
|
+
verifyToken = async (token) => {
|
|
49
|
+
const header = decodeProtectedHeader(token);
|
|
50
|
+
if (!header.kid) {
|
|
51
|
+
throw new Error("Token header is missing 'kid' claim");
|
|
52
|
+
}
|
|
53
|
+
const key = await this.resolveKey(header.kid);
|
|
54
|
+
const { payload } = await jwtVerify(token, key);
|
|
55
|
+
return payload;
|
|
56
|
+
};
|
|
57
|
+
clearKeyCache = () => {
|
|
58
|
+
this.cached = null;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=TokenVerifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenVerifier.js","sourceRoot":"","sources":["../src/TokenVerifier.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EACT,qBAAqB,GAItB,MAAM,MAAM,CAAC;AACd,OAAO,KAAK,MAAM,OAAO,CAAC;AAW1B,MAAM,oBAAoB,GAAG,SAAS,CAAC;AAEvC,MAAM,kBAAkB,GAAG,KAAK,EAC9B,IAAoB,EACyB,EAAE;IAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAC7B,CAAC,GAAG,EAAyC,EAAE,CAC7C,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,CAC9B,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QACtC,IAAI,QAAQ,YAAY,UAAU,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,GAAG,oBAAoB,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAU,CAAC;IACtC,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEF,MAAM,OAAO,aAAa;IAIL;IACA;IAJX,MAAM,GAAsB,IAAI,CAAC;IAEzC,YACmB,OAAe,EACf,aAAqB,oBAAoB;QADzC,YAAO,GAAP,OAAO,CAAQ;QACf,eAAU,GAAV,UAAU,CAA+B;IACzD,CAAC;IAEa,SAAS,GAAG,KAAK,IAEhC,EAAE;QACF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAAe,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEe,gBAAgB,GAAG,KAAK,IAEvC,EAAE;QACF,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACxE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;QAC1B,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC;IAC1B,CAAC,CAAC;IAEe,UAAU,GAAG,KAAK,EACjC,GAAW,EACa,EAAE;QAC1B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QAEpB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,qBAAqB,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;IAEO,WAAW,GAAG,KAAK,EAAE,KAAa,EAAuB,EAAE;QAClE,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEO,aAAa,GAAG,GAAS,EAAE;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC,CAAC;CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TokenVerifier.test.d.ts","sourceRoot":"","sources":["../src/TokenVerifier.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import { TokenVerifier } from "./TokenVerifier.js";
|
|
3
|
+
import { generateKeyPair, exportJWK, SignJWT, } from "jose";
|
|
4
|
+
import axios from "axios";
|
|
5
|
+
vi.mock("axios", async (importOriginal) => {
|
|
6
|
+
const actual = await importOriginal();
|
|
7
|
+
return {
|
|
8
|
+
...actual,
|
|
9
|
+
default: {
|
|
10
|
+
...actual.default,
|
|
11
|
+
get: vi.fn(),
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
const JWKS_URL = "https://authticon.com/.well-known/jwks.json";
|
|
16
|
+
const createTestKeyPair = async (kid) => {
|
|
17
|
+
const { publicKey, privateKey } = await generateKeyPair("RS256");
|
|
18
|
+
const jwk = await exportJWK(publicKey);
|
|
19
|
+
jwk.kid = kid;
|
|
20
|
+
jwk.use = "sig";
|
|
21
|
+
jwk.alg = "RS256";
|
|
22
|
+
return { publicKey, privateKey, jwk };
|
|
23
|
+
};
|
|
24
|
+
const signToken = (privateKey, kid, claims = {}) => new SignJWT(claims)
|
|
25
|
+
.setProtectedHeader({ alg: "RS256", kid })
|
|
26
|
+
.setIssuedAt()
|
|
27
|
+
.setExpirationTime("1h")
|
|
28
|
+
.sign(privateKey);
|
|
29
|
+
describe("TokenVerifier", () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
vi.useRealTimers();
|
|
33
|
+
});
|
|
34
|
+
it("should verify a valid token", async () => {
|
|
35
|
+
const { privateKey, jwk } = await createTestKeyPair("key-1");
|
|
36
|
+
vi.mocked(axios.get).mockResolvedValue({ data: { keys: [jwk] } });
|
|
37
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
38
|
+
const token = await signToken(privateKey, "key-1", { sub: "user-123" });
|
|
39
|
+
const payload = await verifier.verifyToken(token);
|
|
40
|
+
expect(payload.sub).toBe("user-123");
|
|
41
|
+
expect(axios.get).toHaveBeenCalledWith(JWKS_URL);
|
|
42
|
+
});
|
|
43
|
+
it("should cache JWKS keys across multiple verify calls", async () => {
|
|
44
|
+
const { privateKey, jwk } = await createTestKeyPair("key-1");
|
|
45
|
+
vi.mocked(axios.get).mockResolvedValue({ data: { keys: [jwk] } });
|
|
46
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
47
|
+
const token1 = await signToken(privateKey, "key-1", { sub: "user-1" });
|
|
48
|
+
const token2 = await signToken(privateKey, "key-1", { sub: "user-2" });
|
|
49
|
+
await verifier.verifyToken(token1);
|
|
50
|
+
await verifier.verifyToken(token2);
|
|
51
|
+
expect(axios.get).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
it("should refetch JWKS when kid is not found (key rotation)", async () => {
|
|
54
|
+
const key1 = await createTestKeyPair("key-1");
|
|
55
|
+
const key2 = await createTestKeyPair("key-2");
|
|
56
|
+
vi.mocked(axios.get)
|
|
57
|
+
.mockResolvedValueOnce({ data: { keys: [key1.jwk] } })
|
|
58
|
+
.mockResolvedValueOnce({ data: { keys: [key1.jwk, key2.jwk] } });
|
|
59
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
60
|
+
const token1 = await signToken(key1.privateKey, "key-1");
|
|
61
|
+
await verifier.verifyToken(token1);
|
|
62
|
+
const token2 = await signToken(key2.privateKey, "key-2");
|
|
63
|
+
await verifier.verifyToken(token2);
|
|
64
|
+
expect(axios.get).toHaveBeenCalledTimes(2);
|
|
65
|
+
});
|
|
66
|
+
it("should throw when kid is missing from token header", async () => {
|
|
67
|
+
const { privateKey } = await generateKeyPair("RS256");
|
|
68
|
+
const token = await new SignJWT({ sub: "user-123" })
|
|
69
|
+
.setProtectedHeader({ alg: "RS256" })
|
|
70
|
+
.setIssuedAt()
|
|
71
|
+
.setExpirationTime("1h")
|
|
72
|
+
.sign(privateKey);
|
|
73
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
74
|
+
await expect(verifier.verifyToken(token)).rejects.toThrow("kid");
|
|
75
|
+
});
|
|
76
|
+
it("should throw when key is not found after refetch", async () => {
|
|
77
|
+
const { jwk } = await createTestKeyPair("key-1");
|
|
78
|
+
vi.mocked(axios.get).mockResolvedValue({ data: { keys: [jwk] } });
|
|
79
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
80
|
+
const { privateKey: otherKey } = await generateKeyPair("RS256");
|
|
81
|
+
const token = await signToken(otherKey, "unknown-kid");
|
|
82
|
+
await expect(verifier.verifyToken(token)).rejects.toThrow("not found in JWKS");
|
|
83
|
+
});
|
|
84
|
+
it("should clear cache on clearKeyCache()", async () => {
|
|
85
|
+
const { privateKey, jwk } = await createTestKeyPair("key-1");
|
|
86
|
+
vi.mocked(axios.get).mockResolvedValue({ data: { keys: [jwk] } });
|
|
87
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
88
|
+
const token = await signToken(privateKey, "key-1");
|
|
89
|
+
await verifier.verifyToken(token);
|
|
90
|
+
verifier.clearKeyCache();
|
|
91
|
+
await verifier.verifyToken(token);
|
|
92
|
+
expect(axios.get).toHaveBeenCalledTimes(2);
|
|
93
|
+
});
|
|
94
|
+
it("should refetch JWKS when cache TTL expires", async () => {
|
|
95
|
+
vi.useFakeTimers();
|
|
96
|
+
const { privateKey, jwk } = await createTestKeyPair("key-1");
|
|
97
|
+
vi.mocked(axios.get).mockResolvedValue({ data: { keys: [jwk] } });
|
|
98
|
+
const verifier = new TokenVerifier(JWKS_URL, 1000);
|
|
99
|
+
const token = await signToken(privateKey, "key-1");
|
|
100
|
+
await verifier.verifyToken(token);
|
|
101
|
+
vi.advanceTimersByTime(1001);
|
|
102
|
+
await verifier.verifyToken(token);
|
|
103
|
+
expect(axios.get).toHaveBeenCalledTimes(2);
|
|
104
|
+
});
|
|
105
|
+
it("should skip JWK entries without kid", async () => {
|
|
106
|
+
const { privateKey } = await generateKeyPair("RS256");
|
|
107
|
+
const jwkWithoutKid = await exportJWK((await generateKeyPair("RS256")).publicKey);
|
|
108
|
+
const { jwk: jwkWithKid } = await createTestKeyPair("key-1");
|
|
109
|
+
vi.mocked(axios.get).mockResolvedValue({
|
|
110
|
+
data: { keys: [jwkWithoutKid, jwkWithKid] },
|
|
111
|
+
});
|
|
112
|
+
const verifier = new TokenVerifier(JWKS_URL);
|
|
113
|
+
const token = await signToken(privateKey, "key-no-match");
|
|
114
|
+
await expect(verifier.verifyToken(token)).rejects.toThrow("not found in JWKS");
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
//# sourceMappingURL=TokenVerifier.test.js.map
|