@xivdyetools/auth 1.0.1 → 1.0.3
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 +171 -158
- package/dist/hmac.d.ts.map +1 -1
- package/dist/hmac.js +10 -7
- package/dist/hmac.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +13 -12
- package/dist/jwt.js.map +1 -1
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +4 -2
- package/dist/timing.js.map +1 -1
- package/package.json +75 -71
- package/src/discord.test.ts +243 -243
- package/src/discord.ts +143 -143
- package/src/hmac.test.ts +325 -325
- package/src/hmac.ts +223 -213
- package/src/index.ts +54 -54
- package/src/jwt.test.ts +337 -337
- package/src/jwt.ts +265 -267
- package/src/timing.test.ts +114 -117
- package/src/timing.ts +86 -84
package/src/hmac.test.ts
CHANGED
|
@@ -1,325 +1,325 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for HMAC Signing Utilities
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
createHmacKey,
|
|
7
|
-
hmacSign,
|
|
8
|
-
hmacSignHex,
|
|
9
|
-
hmacVerify,
|
|
10
|
-
hmacVerifyHex,
|
|
11
|
-
verifyBotSignature,
|
|
12
|
-
} from './hmac.js';
|
|
13
|
-
|
|
14
|
-
describe('hmac.ts', () => {
|
|
15
|
-
describe('createHmacKey', () => {
|
|
16
|
-
it('should create a CryptoKey for signing', async () => {
|
|
17
|
-
const key = await createHmacKey('test-secret', 'sign');
|
|
18
|
-
expect(key).toBeDefined();
|
|
19
|
-
expect(key.algorithm.name).toBe('HMAC');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should create a CryptoKey for verification', async () => {
|
|
23
|
-
const key = await createHmacKey('test-secret', 'verify');
|
|
24
|
-
expect(key).toBeDefined();
|
|
25
|
-
expect(key.algorithm.name).toBe('HMAC');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should create a CryptoKey for both operations', async () => {
|
|
29
|
-
const key = await createHmacKey('test-secret', 'both');
|
|
30
|
-
expect(key).toBeDefined();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should default to both operations', async () => {
|
|
34
|
-
const key = await createHmacKey('test-secret');
|
|
35
|
-
expect(key).toBeDefined();
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('hmacSign', () => {
|
|
40
|
-
it('should return a base64url-encoded signature', async () => {
|
|
41
|
-
const signature = await hmacSign('test-data', 'test-secret');
|
|
42
|
-
expect(signature).toBeDefined();
|
|
43
|
-
expect(typeof signature).toBe('string');
|
|
44
|
-
// Base64URL should not contain + or /
|
|
45
|
-
expect(signature).not.toMatch(/[+/]/);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should produce consistent signatures for same input', async () => {
|
|
49
|
-
const sig1 = await hmacSign('test-data', 'test-secret');
|
|
50
|
-
const sig2 = await hmacSign('test-data', 'test-secret');
|
|
51
|
-
expect(sig1).toBe(sig2);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should produce different signatures for different data', async () => {
|
|
55
|
-
const sig1 = await hmacSign('data1', 'test-secret');
|
|
56
|
-
const sig2 = await hmacSign('data2', 'test-secret');
|
|
57
|
-
expect(sig1).not.toBe(sig2);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('should produce different signatures for different secrets', async () => {
|
|
61
|
-
const sig1 = await hmacSign('test-data', 'secret1');
|
|
62
|
-
const sig2 = await hmacSign('test-data', 'secret2');
|
|
63
|
-
expect(sig1).not.toBe(sig2);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe('hmacSignHex', () => {
|
|
68
|
-
it('should return a hex-encoded signature', async () => {
|
|
69
|
-
const signature = await hmacSignHex('test-data', 'test-secret');
|
|
70
|
-
expect(signature).toBeDefined();
|
|
71
|
-
expect(typeof signature).toBe('string');
|
|
72
|
-
// Should only contain hex characters
|
|
73
|
-
expect(signature).toMatch(/^[0-9a-f]+$/);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('should produce consistent signatures', async () => {
|
|
77
|
-
const sig1 = await hmacSignHex('test-data', 'test-secret');
|
|
78
|
-
const sig2 = await hmacSignHex('test-data', 'test-secret');
|
|
79
|
-
expect(sig1).toBe(sig2);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('hmacVerify', () => {
|
|
84
|
-
it('should return true for valid signature', async () => {
|
|
85
|
-
const data = 'test-data';
|
|
86
|
-
const secret = 'test-secret';
|
|
87
|
-
const signature = await hmacSign(data, secret);
|
|
88
|
-
const isValid = await hmacVerify(data, signature, secret);
|
|
89
|
-
expect(isValid).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should return false for invalid signature', async () => {
|
|
93
|
-
const isValid = await hmacVerify('test-data', 'invalid-signature', 'test-secret');
|
|
94
|
-
expect(isValid).toBe(false);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('should return false for wrong secret', async () => {
|
|
98
|
-
const data = 'test-data';
|
|
99
|
-
const signature = await hmacSign(data, 'secret1');
|
|
100
|
-
const isValid = await hmacVerify(data, signature, 'secret2');
|
|
101
|
-
expect(isValid).toBe(false);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should return false for tampered data', async () => {
|
|
105
|
-
const signature = await hmacSign('original-data', 'test-secret');
|
|
106
|
-
const isValid = await hmacVerify('tampered-data', signature, 'test-secret');
|
|
107
|
-
expect(isValid).toBe(false);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('hmacVerifyHex', () => {
|
|
112
|
-
it('should return true for valid hex signature', async () => {
|
|
113
|
-
const data = 'test-data';
|
|
114
|
-
const secret = 'test-secret';
|
|
115
|
-
const signature = await hmacSignHex(data, secret);
|
|
116
|
-
const isValid = await hmacVerifyHex(data, signature, secret);
|
|
117
|
-
expect(isValid).toBe(true);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should return false for invalid hex signature', async () => {
|
|
121
|
-
const isValid = await hmacVerifyHex('test-data', 'deadbeef', 'test-secret');
|
|
122
|
-
expect(isValid).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it('should return false for malformed hex', async () => {
|
|
126
|
-
const isValid = await hmacVerifyHex('test-data', 'not-hex!', 'test-secret');
|
|
127
|
-
expect(isValid).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
describe('verifyBotSignature', () => {
|
|
132
|
-
const secret = 'bot-signing-secret';
|
|
133
|
-
|
|
134
|
-
beforeEach(() => {
|
|
135
|
-
vi.useFakeTimers();
|
|
136
|
-
vi.setSystemTime(new Date('2024-01-15T12:00:00Z'));
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
afterEach(() => {
|
|
140
|
-
vi.useRealTimers();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should return true for valid signature', async () => {
|
|
144
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
145
|
-
const userId = '123456789';
|
|
146
|
-
const userName = 'testuser';
|
|
147
|
-
const message = `${timestamp}:${userId}:${userName}`;
|
|
148
|
-
const signature = await hmacSignHex(message, secret);
|
|
149
|
-
|
|
150
|
-
const isValid = await verifyBotSignature(
|
|
151
|
-
signature,
|
|
152
|
-
timestamp,
|
|
153
|
-
userId,
|
|
154
|
-
userName,
|
|
155
|
-
secret
|
|
156
|
-
);
|
|
157
|
-
expect(isValid).toBe(true);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('should return false for missing signature', async () => {
|
|
161
|
-
const isValid = await verifyBotSignature(
|
|
162
|
-
undefined,
|
|
163
|
-
'1234567890',
|
|
164
|
-
'123456789',
|
|
165
|
-
'testuser',
|
|
166
|
-
secret
|
|
167
|
-
);
|
|
168
|
-
expect(isValid).toBe(false);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should return false for missing timestamp', async () => {
|
|
172
|
-
const isValid = await verifyBotSignature(
|
|
173
|
-
'somesignature',
|
|
174
|
-
undefined,
|
|
175
|
-
'123456789',
|
|
176
|
-
'testuser',
|
|
177
|
-
secret
|
|
178
|
-
);
|
|
179
|
-
expect(isValid).toBe(false);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
it('should return false for missing userId', async () => {
|
|
183
|
-
const isValid = await verifyBotSignature(
|
|
184
|
-
'somesignature',
|
|
185
|
-
'1234567890',
|
|
186
|
-
undefined,
|
|
187
|
-
'testuser',
|
|
188
|
-
secret
|
|
189
|
-
);
|
|
190
|
-
expect(isValid).toBe(false);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('should return false for missing userName', async () => {
|
|
194
|
-
const isValid = await verifyBotSignature(
|
|
195
|
-
'somesignature',
|
|
196
|
-
'1234567890',
|
|
197
|
-
'123456789',
|
|
198
|
-
undefined,
|
|
199
|
-
secret
|
|
200
|
-
);
|
|
201
|
-
expect(isValid).toBe(false);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should return false for expired signature', async () => {
|
|
205
|
-
// Create signature from 10 minutes ago (default max age is 5 minutes)
|
|
206
|
-
const oldTimestamp = Math.floor(Date.now() / 1000) - 600;
|
|
207
|
-
const userId = '123456789';
|
|
208
|
-
const userName = 'testuser';
|
|
209
|
-
const message = `${oldTimestamp}:${userId}:${userName}`;
|
|
210
|
-
const signature = await hmacSignHex(message, secret);
|
|
211
|
-
|
|
212
|
-
const isValid = await verifyBotSignature(
|
|
213
|
-
signature,
|
|
214
|
-
oldTimestamp.toString(),
|
|
215
|
-
userId,
|
|
216
|
-
userName,
|
|
217
|
-
secret
|
|
218
|
-
);
|
|
219
|
-
expect(isValid).toBe(false);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('should return false for future timestamp beyond clock skew', async () => {
|
|
223
|
-
// Create signature 2 minutes in the future (default clock skew is 1 minute)
|
|
224
|
-
const futureTimestamp = Math.floor(Date.now() / 1000) + 120;
|
|
225
|
-
const userId = '123456789';
|
|
226
|
-
const userName = 'testuser';
|
|
227
|
-
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
228
|
-
const signature = await hmacSignHex(message, secret);
|
|
229
|
-
|
|
230
|
-
const isValid = await verifyBotSignature(
|
|
231
|
-
signature,
|
|
232
|
-
futureTimestamp.toString(),
|
|
233
|
-
userId,
|
|
234
|
-
userName,
|
|
235
|
-
secret
|
|
236
|
-
);
|
|
237
|
-
expect(isValid).toBe(false);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should accept signature within clock skew tolerance', async () => {
|
|
241
|
-
// Create signature 30 seconds in the future (within default 1 minute clock skew)
|
|
242
|
-
const futureTimestamp = Math.floor(Date.now() / 1000) + 30;
|
|
243
|
-
const userId = '123456789';
|
|
244
|
-
const userName = 'testuser';
|
|
245
|
-
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
246
|
-
const signature = await hmacSignHex(message, secret);
|
|
247
|
-
|
|
248
|
-
const isValid = await verifyBotSignature(
|
|
249
|
-
signature,
|
|
250
|
-
futureTimestamp.toString(),
|
|
251
|
-
userId,
|
|
252
|
-
userName,
|
|
253
|
-
secret
|
|
254
|
-
);
|
|
255
|
-
expect(isValid).toBe(true);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it('should return false for invalid timestamp format', async () => {
|
|
259
|
-
const isValid = await verifyBotSignature(
|
|
260
|
-
'somesignature',
|
|
261
|
-
'not-a-number',
|
|
262
|
-
'123456789',
|
|
263
|
-
'testuser',
|
|
264
|
-
secret
|
|
265
|
-
);
|
|
266
|
-
expect(isValid).toBe(false);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it('should return false for wrong signature', async () => {
|
|
270
|
-
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
271
|
-
const userId = '123456789';
|
|
272
|
-
const userName = 'testuser';
|
|
273
|
-
|
|
274
|
-
const isValid = await verifyBotSignature(
|
|
275
|
-
'wrongsignature',
|
|
276
|
-
timestamp,
|
|
277
|
-
userId,
|
|
278
|
-
userName,
|
|
279
|
-
secret
|
|
280
|
-
);
|
|
281
|
-
expect(isValid).toBe(false);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('should respect custom maxAgeMs option', async () => {
|
|
285
|
-
// Create signature from 2 minutes ago
|
|
286
|
-
const oldTimestamp = Math.floor(Date.now() / 1000) - 120;
|
|
287
|
-
const userId = '123456789';
|
|
288
|
-
const userName = 'testuser';
|
|
289
|
-
const message = `${oldTimestamp}:${userId}:${userName}`;
|
|
290
|
-
const signature = await hmacSignHex(message, secret);
|
|
291
|
-
|
|
292
|
-
// Should fail with default 5 minute max age... wait, 2 minutes is within 5 minutes
|
|
293
|
-
// Let's use 1 minute max age instead
|
|
294
|
-
const isValid = await verifyBotSignature(
|
|
295
|
-
signature,
|
|
296
|
-
oldTimestamp.toString(),
|
|
297
|
-
userId,
|
|
298
|
-
userName,
|
|
299
|
-
secret,
|
|
300
|
-
{ maxAgeMs: 60 * 1000 } // 1 minute
|
|
301
|
-
);
|
|
302
|
-
expect(isValid).toBe(false);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('should respect custom clockSkewMs option', async () => {
|
|
306
|
-
// Create signature 30 seconds in the future
|
|
307
|
-
const futureTimestamp = Math.floor(Date.now() / 1000) + 30;
|
|
308
|
-
const userId = '123456789';
|
|
309
|
-
const userName = 'testuser';
|
|
310
|
-
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
311
|
-
const signature = await hmacSignHex(message, secret);
|
|
312
|
-
|
|
313
|
-
// Should fail with 10 second clock skew tolerance
|
|
314
|
-
const isValid = await verifyBotSignature(
|
|
315
|
-
signature,
|
|
316
|
-
futureTimestamp.toString(),
|
|
317
|
-
userId,
|
|
318
|
-
userName,
|
|
319
|
-
secret,
|
|
320
|
-
{ clockSkewMs: 10 * 1000 } // 10 seconds
|
|
321
|
-
);
|
|
322
|
-
expect(isValid).toBe(false);
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Tests for HMAC Signing Utilities
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
createHmacKey,
|
|
7
|
+
hmacSign,
|
|
8
|
+
hmacSignHex,
|
|
9
|
+
hmacVerify,
|
|
10
|
+
hmacVerifyHex,
|
|
11
|
+
verifyBotSignature,
|
|
12
|
+
} from './hmac.js';
|
|
13
|
+
|
|
14
|
+
describe('hmac.ts', () => {
|
|
15
|
+
describe('createHmacKey', () => {
|
|
16
|
+
it('should create a CryptoKey for signing', async () => {
|
|
17
|
+
const key = await createHmacKey('test-secret', 'sign');
|
|
18
|
+
expect(key).toBeDefined();
|
|
19
|
+
expect(key.algorithm.name).toBe('HMAC');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should create a CryptoKey for verification', async () => {
|
|
23
|
+
const key = await createHmacKey('test-secret', 'verify');
|
|
24
|
+
expect(key).toBeDefined();
|
|
25
|
+
expect(key.algorithm.name).toBe('HMAC');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should create a CryptoKey for both operations', async () => {
|
|
29
|
+
const key = await createHmacKey('test-secret', 'both');
|
|
30
|
+
expect(key).toBeDefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should default to both operations', async () => {
|
|
34
|
+
const key = await createHmacKey('test-secret');
|
|
35
|
+
expect(key).toBeDefined();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('hmacSign', () => {
|
|
40
|
+
it('should return a base64url-encoded signature', async () => {
|
|
41
|
+
const signature = await hmacSign('test-data', 'test-secret');
|
|
42
|
+
expect(signature).toBeDefined();
|
|
43
|
+
expect(typeof signature).toBe('string');
|
|
44
|
+
// Base64URL should not contain + or /
|
|
45
|
+
expect(signature).not.toMatch(/[+/]/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should produce consistent signatures for same input', async () => {
|
|
49
|
+
const sig1 = await hmacSign('test-data', 'test-secret');
|
|
50
|
+
const sig2 = await hmacSign('test-data', 'test-secret');
|
|
51
|
+
expect(sig1).toBe(sig2);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should produce different signatures for different data', async () => {
|
|
55
|
+
const sig1 = await hmacSign('data1', 'test-secret');
|
|
56
|
+
const sig2 = await hmacSign('data2', 'test-secret');
|
|
57
|
+
expect(sig1).not.toBe(sig2);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should produce different signatures for different secrets', async () => {
|
|
61
|
+
const sig1 = await hmacSign('test-data', 'secret1');
|
|
62
|
+
const sig2 = await hmacSign('test-data', 'secret2');
|
|
63
|
+
expect(sig1).not.toBe(sig2);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('hmacSignHex', () => {
|
|
68
|
+
it('should return a hex-encoded signature', async () => {
|
|
69
|
+
const signature = await hmacSignHex('test-data', 'test-secret');
|
|
70
|
+
expect(signature).toBeDefined();
|
|
71
|
+
expect(typeof signature).toBe('string');
|
|
72
|
+
// Should only contain hex characters
|
|
73
|
+
expect(signature).toMatch(/^[0-9a-f]+$/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should produce consistent signatures', async () => {
|
|
77
|
+
const sig1 = await hmacSignHex('test-data', 'test-secret');
|
|
78
|
+
const sig2 = await hmacSignHex('test-data', 'test-secret');
|
|
79
|
+
expect(sig1).toBe(sig2);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('hmacVerify', () => {
|
|
84
|
+
it('should return true for valid signature', async () => {
|
|
85
|
+
const data = 'test-data';
|
|
86
|
+
const secret = 'test-secret';
|
|
87
|
+
const signature = await hmacSign(data, secret);
|
|
88
|
+
const isValid = await hmacVerify(data, signature, secret);
|
|
89
|
+
expect(isValid).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should return false for invalid signature', async () => {
|
|
93
|
+
const isValid = await hmacVerify('test-data', 'invalid-signature', 'test-secret');
|
|
94
|
+
expect(isValid).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return false for wrong secret', async () => {
|
|
98
|
+
const data = 'test-data';
|
|
99
|
+
const signature = await hmacSign(data, 'secret1');
|
|
100
|
+
const isValid = await hmacVerify(data, signature, 'secret2');
|
|
101
|
+
expect(isValid).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return false for tampered data', async () => {
|
|
105
|
+
const signature = await hmacSign('original-data', 'test-secret');
|
|
106
|
+
const isValid = await hmacVerify('tampered-data', signature, 'test-secret');
|
|
107
|
+
expect(isValid).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('hmacVerifyHex', () => {
|
|
112
|
+
it('should return true for valid hex signature', async () => {
|
|
113
|
+
const data = 'test-data';
|
|
114
|
+
const secret = 'test-secret';
|
|
115
|
+
const signature = await hmacSignHex(data, secret);
|
|
116
|
+
const isValid = await hmacVerifyHex(data, signature, secret);
|
|
117
|
+
expect(isValid).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should return false for invalid hex signature', async () => {
|
|
121
|
+
const isValid = await hmacVerifyHex('test-data', 'deadbeef', 'test-secret');
|
|
122
|
+
expect(isValid).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return false for malformed hex', async () => {
|
|
126
|
+
const isValid = await hmacVerifyHex('test-data', 'not-hex!', 'test-secret');
|
|
127
|
+
expect(isValid).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('verifyBotSignature', () => {
|
|
132
|
+
const secret = 'bot-signing-secret';
|
|
133
|
+
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
vi.useFakeTimers();
|
|
136
|
+
vi.setSystemTime(new Date('2024-01-15T12:00:00Z'));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
vi.useRealTimers();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should return true for valid signature', async () => {
|
|
144
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
145
|
+
const userId = '123456789';
|
|
146
|
+
const userName = 'testuser';
|
|
147
|
+
const message = `${timestamp}:${userId}:${userName}`;
|
|
148
|
+
const signature = await hmacSignHex(message, secret);
|
|
149
|
+
|
|
150
|
+
const isValid = await verifyBotSignature(
|
|
151
|
+
signature,
|
|
152
|
+
timestamp,
|
|
153
|
+
userId,
|
|
154
|
+
userName,
|
|
155
|
+
secret
|
|
156
|
+
);
|
|
157
|
+
expect(isValid).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should return false for missing signature', async () => {
|
|
161
|
+
const isValid = await verifyBotSignature(
|
|
162
|
+
undefined,
|
|
163
|
+
'1234567890',
|
|
164
|
+
'123456789',
|
|
165
|
+
'testuser',
|
|
166
|
+
secret
|
|
167
|
+
);
|
|
168
|
+
expect(isValid).toBe(false);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should return false for missing timestamp', async () => {
|
|
172
|
+
const isValid = await verifyBotSignature(
|
|
173
|
+
'somesignature',
|
|
174
|
+
undefined,
|
|
175
|
+
'123456789',
|
|
176
|
+
'testuser',
|
|
177
|
+
secret
|
|
178
|
+
);
|
|
179
|
+
expect(isValid).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should return false for missing userId', async () => {
|
|
183
|
+
const isValid = await verifyBotSignature(
|
|
184
|
+
'somesignature',
|
|
185
|
+
'1234567890',
|
|
186
|
+
undefined,
|
|
187
|
+
'testuser',
|
|
188
|
+
secret
|
|
189
|
+
);
|
|
190
|
+
expect(isValid).toBe(false);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should return false for missing userName', async () => {
|
|
194
|
+
const isValid = await verifyBotSignature(
|
|
195
|
+
'somesignature',
|
|
196
|
+
'1234567890',
|
|
197
|
+
'123456789',
|
|
198
|
+
undefined,
|
|
199
|
+
secret
|
|
200
|
+
);
|
|
201
|
+
expect(isValid).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should return false for expired signature', async () => {
|
|
205
|
+
// Create signature from 10 minutes ago (default max age is 5 minutes)
|
|
206
|
+
const oldTimestamp = Math.floor(Date.now() / 1000) - 600;
|
|
207
|
+
const userId = '123456789';
|
|
208
|
+
const userName = 'testuser';
|
|
209
|
+
const message = `${oldTimestamp}:${userId}:${userName}`;
|
|
210
|
+
const signature = await hmacSignHex(message, secret);
|
|
211
|
+
|
|
212
|
+
const isValid = await verifyBotSignature(
|
|
213
|
+
signature,
|
|
214
|
+
oldTimestamp.toString(),
|
|
215
|
+
userId,
|
|
216
|
+
userName,
|
|
217
|
+
secret
|
|
218
|
+
);
|
|
219
|
+
expect(isValid).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return false for future timestamp beyond clock skew', async () => {
|
|
223
|
+
// Create signature 2 minutes in the future (default clock skew is 1 minute)
|
|
224
|
+
const futureTimestamp = Math.floor(Date.now() / 1000) + 120;
|
|
225
|
+
const userId = '123456789';
|
|
226
|
+
const userName = 'testuser';
|
|
227
|
+
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
228
|
+
const signature = await hmacSignHex(message, secret);
|
|
229
|
+
|
|
230
|
+
const isValid = await verifyBotSignature(
|
|
231
|
+
signature,
|
|
232
|
+
futureTimestamp.toString(),
|
|
233
|
+
userId,
|
|
234
|
+
userName,
|
|
235
|
+
secret
|
|
236
|
+
);
|
|
237
|
+
expect(isValid).toBe(false);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should accept signature within clock skew tolerance', async () => {
|
|
241
|
+
// Create signature 30 seconds in the future (within default 1 minute clock skew)
|
|
242
|
+
const futureTimestamp = Math.floor(Date.now() / 1000) + 30;
|
|
243
|
+
const userId = '123456789';
|
|
244
|
+
const userName = 'testuser';
|
|
245
|
+
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
246
|
+
const signature = await hmacSignHex(message, secret);
|
|
247
|
+
|
|
248
|
+
const isValid = await verifyBotSignature(
|
|
249
|
+
signature,
|
|
250
|
+
futureTimestamp.toString(),
|
|
251
|
+
userId,
|
|
252
|
+
userName,
|
|
253
|
+
secret
|
|
254
|
+
);
|
|
255
|
+
expect(isValid).toBe(true);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should return false for invalid timestamp format', async () => {
|
|
259
|
+
const isValid = await verifyBotSignature(
|
|
260
|
+
'somesignature',
|
|
261
|
+
'not-a-number',
|
|
262
|
+
'123456789',
|
|
263
|
+
'testuser',
|
|
264
|
+
secret
|
|
265
|
+
);
|
|
266
|
+
expect(isValid).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should return false for wrong signature', async () => {
|
|
270
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
271
|
+
const userId = '123456789';
|
|
272
|
+
const userName = 'testuser';
|
|
273
|
+
|
|
274
|
+
const isValid = await verifyBotSignature(
|
|
275
|
+
'wrongsignature',
|
|
276
|
+
timestamp,
|
|
277
|
+
userId,
|
|
278
|
+
userName,
|
|
279
|
+
secret
|
|
280
|
+
);
|
|
281
|
+
expect(isValid).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should respect custom maxAgeMs option', async () => {
|
|
285
|
+
// Create signature from 2 minutes ago
|
|
286
|
+
const oldTimestamp = Math.floor(Date.now() / 1000) - 120;
|
|
287
|
+
const userId = '123456789';
|
|
288
|
+
const userName = 'testuser';
|
|
289
|
+
const message = `${oldTimestamp}:${userId}:${userName}`;
|
|
290
|
+
const signature = await hmacSignHex(message, secret);
|
|
291
|
+
|
|
292
|
+
// Should fail with default 5 minute max age... wait, 2 minutes is within 5 minutes
|
|
293
|
+
// Let's use 1 minute max age instead
|
|
294
|
+
const isValid = await verifyBotSignature(
|
|
295
|
+
signature,
|
|
296
|
+
oldTimestamp.toString(),
|
|
297
|
+
userId,
|
|
298
|
+
userName,
|
|
299
|
+
secret,
|
|
300
|
+
{ maxAgeMs: 60 * 1000 } // 1 minute
|
|
301
|
+
);
|
|
302
|
+
expect(isValid).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should respect custom clockSkewMs option', async () => {
|
|
306
|
+
// Create signature 30 seconds in the future
|
|
307
|
+
const futureTimestamp = Math.floor(Date.now() / 1000) + 30;
|
|
308
|
+
const userId = '123456789';
|
|
309
|
+
const userName = 'testuser';
|
|
310
|
+
const message = `${futureTimestamp}:${userId}:${userName}`;
|
|
311
|
+
const signature = await hmacSignHex(message, secret);
|
|
312
|
+
|
|
313
|
+
// Should fail with 10 second clock skew tolerance
|
|
314
|
+
const isValid = await verifyBotSignature(
|
|
315
|
+
signature,
|
|
316
|
+
futureTimestamp.toString(),
|
|
317
|
+
userId,
|
|
318
|
+
userName,
|
|
319
|
+
secret,
|
|
320
|
+
{ clockSkewMs: 10 * 1000 } // 10 seconds
|
|
321
|
+
);
|
|
322
|
+
expect(isValid).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
});
|