@olane/o-server 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/src/interfaces/server-config.interface.d.ts +47 -1
  2. package/dist/src/interfaces/server-config.interface.d.ts.map +1 -1
  3. package/dist/src/middleware/error-handler.d.ts +9 -0
  4. package/dist/src/middleware/error-handler.d.ts.map +1 -1
  5. package/dist/src/middleware/error-handler.js +49 -3
  6. package/dist/src/middleware/jwt-auth.d.ts +54 -0
  7. package/dist/src/middleware/jwt-auth.d.ts.map +1 -0
  8. package/dist/src/middleware/jwt-auth.js +145 -0
  9. package/dist/src/o-server.d.ts.map +1 -1
  10. package/dist/src/o-server.js +53 -23
  11. package/dist/src/validation/address-validator.d.ts +13 -0
  12. package/dist/src/validation/address-validator.d.ts.map +1 -0
  13. package/dist/src/validation/address-validator.js +40 -0
  14. package/dist/src/validation/index.d.ts +17 -0
  15. package/dist/src/validation/index.d.ts.map +1 -0
  16. package/dist/src/validation/index.js +16 -0
  17. package/dist/src/validation/method-validator.d.ts +13 -0
  18. package/dist/src/validation/method-validator.d.ts.map +1 -0
  19. package/dist/src/validation/method-validator.js +36 -0
  20. package/dist/src/validation/params-sanitizer.d.ts +15 -0
  21. package/dist/src/validation/params-sanitizer.d.ts.map +1 -0
  22. package/dist/src/validation/params-sanitizer.js +51 -0
  23. package/dist/src/validation/request-validator.d.ts +53 -0
  24. package/dist/src/validation/request-validator.d.ts.map +1 -0
  25. package/dist/src/validation/request-validator.js +53 -0
  26. package/dist/src/validation/validation-error.d.ts +11 -0
  27. package/dist/src/validation/validation-error.d.ts.map +1 -0
  28. package/dist/src/validation/validation-error.js +12 -0
  29. package/dist/test/ai.spec.d.ts +0 -1
  30. package/dist/test/ai.spec.js +20 -13
  31. package/dist/test/error-security.spec.d.ts +2 -0
  32. package/dist/test/error-security.spec.d.ts.map +1 -0
  33. package/dist/test/error-security.spec.js +134 -0
  34. package/dist/test/input-validation.spec.d.ts +14 -0
  35. package/dist/test/input-validation.spec.d.ts.map +1 -0
  36. package/dist/test/input-validation.spec.js +487 -0
  37. package/dist/test/jwt-auth.spec.d.ts +2 -0
  38. package/dist/test/jwt-auth.spec.d.ts.map +1 -0
  39. package/dist/test/jwt-auth.spec.js +397 -0
  40. package/package.json +10 -4
@@ -0,0 +1,397 @@
1
+ import { createJwtMiddleware } from '../src/middleware/jwt-auth.js';
2
+ import { sanitizeErrorMessage } from '../src/middleware/error-handler.js';
3
+ import { expect } from 'aegir/chai';
4
+ import jwt from 'jsonwebtoken';
5
+ import { writeFileSync, unlinkSync, mkdirSync } from 'fs';
6
+ import { join } from 'path';
7
+ import { tmpdir } from 'os';
8
+ describe('JWT Authentication', () => {
9
+ // Test keys
10
+ const testSecret = 'test-secret-key-for-hs256';
11
+ // Generate RSA key pair for testing
12
+ const rsaPrivateKey = `-----BEGIN RSA PRIVATE KEY-----
13
+ MIIEpAIBAAKCAQEA0Z3VS5JJcds3xfn/vLMOT+2FnPB3xCLnRvCmFJQFmXwJvHFM
14
+ qUBMQFmG+hCDaS3o6JdBk0xKyLpXCzRjv1hpT+0tFQjZWVDZDUVnAJBBKFxhDMQi
15
+ yPnCpVj6x5C6vYmXlN6WVdKaZG3hqjjyXRWqJxB2tIfPPHrNnRpqWvfJJFVH3WJ3
16
+ VvkNLWcPqJ6w3LpHPKvqkJCUGLpLYLQHDHqRbWlh2M8OPzKPdLQHDqTlLXqJLHBY
17
+ j+aKxQP8l7GzKC8LJNlQFEsKHLwNXM+vL9QJXlQxBVKP9m8IJQVJKH3m8xPqYLmQ
18
+ z9aQG7PkLxHvKF7RqnLQJLYQF8L9QxGxYH3QDwIDAQABAoIBAFDXkqLxjJQFZwp4
19
+ zLl2KJvXd1YqqCJDd1qXxmLxPQFMBd8xPXJYLCUQR0l2Ek7Fx6l0Y8L+QYXm8cAq
20
+ gLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQRql2Ek7Fx6l0
21
+ Y8L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQ
22
+ R0l2Ek7Fx6l0Y8L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFM
23
+ Bd8xP3JYLCUQR0l2Ek7Fx6l0Y8L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8Cq
24
+ LJPKqLXdECgYEA8qZLV3PqJ9CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6l0Y8
25
+ L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0
26
+ l2Ek7Fx6ECgYEA3QHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6l0Y8L+QY
27
+ Xm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek
28
+ 7Fx6l0CgYAYQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6l0Y8L+QYXm8c
29
+ AqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6
30
+ l0Y8L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLC
31
+ UQECCL+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JY
32
+ LCUQECgYEAwQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6l0Y8L+QYXm8c
33
+ AqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLCUQR0l2Ek7Fx6
34
+ l0Y8L+QYXm8cAqgLqLXdPqJgGNmKLFXqV2YQHqG8CqLJPKqLXdPQFMBd8xP3JYLC
35
+ UQE=
36
+ -----END RSA PRIVATE KEY-----`;
37
+ const rsaPublicKey = `-----BEGIN PUBLIC KEY-----
38
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Z3VS5JJcds3xfn/vLMO
39
+ T+2FnPB3xCLnRvCmFJQFmXwJvHFMqUBMQFmG+hCDaS3o6JdBk0xKyLpXCzRjv1hp
40
+ T+0tFQjZWVDZDUVnAJBBKFxhDMQiyPnCpVj6x5C6vYmXlN6WVdKaZG3hqjjyXRWq
41
+ JxB2tIfPPHrNnRpqWvfJJFVH3WJ3VvkNLWcPqJ6w3LpHPKvqkJCUGLpLYLQHDHqR
42
+ bWlh2M8OPzKPdLQHDqTlLXqJLHBYj+aKxQP8l7GzKC8LJNlQFEsKHLwNXM+vL9QJ
43
+ XlQxBVKP9m8IJQVJKH3m8xPqYLmQz9aQG7PkLxHvKF7RqnLQJLYQF8L9QxGxYH3Q
44
+ DwIDAQAB
45
+ -----END PUBLIC KEY-----`;
46
+ let tmpDir;
47
+ let publicKeyPath;
48
+ before(() => {
49
+ // Create temp directory for test files
50
+ tmpDir = join(tmpdir(), 'o-server-jwt-test-' + Date.now());
51
+ mkdirSync(tmpDir, { recursive: true });
52
+ // Write public key to file
53
+ publicKeyPath = join(tmpDir, 'public-key.pem');
54
+ writeFileSync(publicKeyPath, rsaPublicKey);
55
+ });
56
+ after(() => {
57
+ // Cleanup temp files
58
+ try {
59
+ unlinkSync(publicKeyPath);
60
+ }
61
+ catch (e) {
62
+ // Ignore cleanup errors
63
+ }
64
+ });
65
+ // Helper to create mock Express request/response/next
66
+ function createMockReq(authHeader) {
67
+ return {
68
+ headers: authHeader ? { authorization: authHeader } : {},
69
+ path: '/api/v1/use',
70
+ };
71
+ }
72
+ function createMockRes() {
73
+ return {};
74
+ }
75
+ function createMockNext() {
76
+ const mock = {
77
+ next: (err) => {
78
+ if (err) {
79
+ mock.error = err;
80
+ }
81
+ },
82
+ error: undefined,
83
+ };
84
+ return mock;
85
+ }
86
+ describe('Valid Token Tests', () => {
87
+ it('should accept valid RS256 token', async () => {
88
+ const token = jwt.sign({
89
+ sub: 'user123',
90
+ iss: 'test-issuer',
91
+ aud: 'test-audience',
92
+ }, rsaPrivateKey, { algorithm: 'RS256', expiresIn: '1h' });
93
+ const config = {
94
+ method: 'publicKey',
95
+ publicKeyPath,
96
+ issuer: 'test-issuer',
97
+ audience: 'test-audience',
98
+ };
99
+ const middleware = createJwtMiddleware(config);
100
+ const req = createMockReq(`Bearer ${token}`);
101
+ const res = createMockRes();
102
+ const { next, error } = createMockNext();
103
+ await middleware(req, res, next);
104
+ expect(error).to.be.undefined;
105
+ expect(req.jwt).to.exist;
106
+ expect(req.jwt.sub).to.equal('user123');
107
+ });
108
+ it('should accept valid HS256 token', async () => {
109
+ const token = jwt.sign({
110
+ sub: 'user456',
111
+ iss: 'test-issuer',
112
+ aud: 'test-audience',
113
+ }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
114
+ const config = {
115
+ method: 'secret',
116
+ secret: testSecret,
117
+ issuer: 'test-issuer',
118
+ audience: 'test-audience',
119
+ };
120
+ const middleware = createJwtMiddleware(config);
121
+ const req = createMockReq(`Bearer ${token}`);
122
+ const res = createMockRes();
123
+ const { next, error } = createMockNext();
124
+ await middleware(req, res, next);
125
+ expect(error).to.be.undefined;
126
+ expect(req.jwt).to.exist;
127
+ expect(req.jwt.sub).to.equal('user456');
128
+ });
129
+ it('should validate all claims correctly', async () => {
130
+ const token = jwt.sign({
131
+ sub: 'user789',
132
+ iss: 'test-issuer',
133
+ aud: 'test-audience',
134
+ customClaim: 'customValue',
135
+ }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
136
+ const config = {
137
+ method: 'secret',
138
+ secret: testSecret,
139
+ issuer: 'test-issuer',
140
+ audience: 'test-audience',
141
+ };
142
+ const middleware = createJwtMiddleware(config);
143
+ const req = createMockReq(`Bearer ${token}`);
144
+ const res = createMockRes();
145
+ const { next, error } = createMockNext();
146
+ await middleware(req, res, next);
147
+ expect(error).to.be.undefined;
148
+ expect(req.jwt).to.exist;
149
+ expect(req.jwt.sub).to.equal('user789');
150
+ expect(req.jwt.iss).to.equal('test-issuer');
151
+ expect(req.jwt.aud).to.equal('test-audience');
152
+ expect(req.jwt.customClaim).to.equal('customValue');
153
+ });
154
+ it('should make token payload accessible in req.user', async () => {
155
+ const token = jwt.sign({
156
+ sub: 'user999',
157
+ name: 'Test User',
158
+ }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
159
+ const config = {
160
+ method: 'secret',
161
+ secret: testSecret,
162
+ };
163
+ const middleware = createJwtMiddleware(config);
164
+ const req = createMockReq(`Bearer ${token}`);
165
+ const res = createMockRes();
166
+ const { next, error } = createMockNext();
167
+ await middleware(req, res, next);
168
+ expect(error).to.be.undefined;
169
+ expect(req.user).to.exist;
170
+ expect(req.user.userId).to.equal('user999');
171
+ expect(req.user.sub).to.equal('user999');
172
+ expect(req.user.name).to.equal('Test User');
173
+ });
174
+ });
175
+ describe('Invalid Token Tests', () => {
176
+ it('should reject request with missing Authorization header', async () => {
177
+ const config = {
178
+ method: 'secret',
179
+ secret: testSecret,
180
+ };
181
+ const middleware = createJwtMiddleware(config);
182
+ const req = createMockReq(); // No auth header
183
+ const res = createMockRes();
184
+ const { next, error } = createMockNext();
185
+ await middleware(req, res, next);
186
+ expect(error).to.exist;
187
+ expect(error.code).to.equal('MISSING_TOKEN');
188
+ expect(error.status).to.equal(401);
189
+ expect(error.message).to.contain('No authorization token');
190
+ });
191
+ it('should reject malformed header (no Bearer prefix)', async () => {
192
+ const token = jwt.sign({ sub: 'user123' }, testSecret, { expiresIn: '1h' });
193
+ const config = {
194
+ method: 'secret',
195
+ secret: testSecret,
196
+ };
197
+ const middleware = createJwtMiddleware(config);
198
+ const req = createMockReq(token); // No "Bearer " prefix
199
+ const res = createMockRes();
200
+ const { next, error } = createMockNext();
201
+ await middleware(req, res, next);
202
+ expect(error).to.exist;
203
+ expect(error.code).to.equal('INVALID_TOKEN_FORMAT');
204
+ expect(error.status).to.equal(401);
205
+ expect(error.message).to.contain('Invalid authorization format');
206
+ });
207
+ it('should reject expired token', async () => {
208
+ const token = jwt.sign({ sub: 'user123' }, testSecret, { algorithm: 'HS256', expiresIn: '-1h' } // Already expired
209
+ );
210
+ const config = {
211
+ method: 'secret',
212
+ secret: testSecret,
213
+ };
214
+ const middleware = createJwtMiddleware(config);
215
+ const req = createMockReq(`Bearer ${token}`);
216
+ const res = createMockRes();
217
+ const { next, error } = createMockNext();
218
+ await middleware(req, res, next);
219
+ expect(error).to.exist;
220
+ expect(error.code).to.equal('TOKEN_EXPIRED');
221
+ expect(error.status).to.equal(401);
222
+ expect(error.message).to.contain('expired');
223
+ });
224
+ it('should reject token not yet valid (nbf)', async () => {
225
+ const futureTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour in future
226
+ const token = jwt.sign({
227
+ sub: 'user123',
228
+ nbf: futureTime,
229
+ }, testSecret, { algorithm: 'HS256', expiresIn: '2h' });
230
+ const config = {
231
+ method: 'secret',
232
+ secret: testSecret,
233
+ };
234
+ const middleware = createJwtMiddleware(config);
235
+ const req = createMockReq(`Bearer ${token}`);
236
+ const res = createMockRes();
237
+ const { next, error } = createMockNext();
238
+ await middleware(req, res, next);
239
+ expect(error).to.exist;
240
+ expect(error.code).to.equal('TOKEN_NOT_ACTIVE');
241
+ expect(error.status).to.equal(401);
242
+ expect(error.message).to.contain('not yet valid');
243
+ });
244
+ it('should reject token with invalid signature', async () => {
245
+ const token = jwt.sign({ sub: 'user123' }, 'wrong-secret', { expiresIn: '1h' });
246
+ const config = {
247
+ method: 'secret',
248
+ secret: testSecret,
249
+ };
250
+ const middleware = createJwtMiddleware(config);
251
+ const req = createMockReq(`Bearer ${token}`);
252
+ const res = createMockRes();
253
+ const { next, error } = createMockNext();
254
+ await middleware(req, res, next);
255
+ expect(error).to.exist;
256
+ expect(error.code).to.equal('INVALID_TOKEN');
257
+ expect(error.status).to.equal(401);
258
+ });
259
+ it('should reject token with wrong issuer', async () => {
260
+ const token = jwt.sign({
261
+ sub: 'user123',
262
+ iss: 'wrong-issuer',
263
+ }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
264
+ const config = {
265
+ method: 'secret',
266
+ secret: testSecret,
267
+ issuer: 'expected-issuer',
268
+ };
269
+ const middleware = createJwtMiddleware(config);
270
+ const req = createMockReq(`Bearer ${token}`);
271
+ const res = createMockRes();
272
+ const { next, error } = createMockNext();
273
+ await middleware(req, res, next);
274
+ expect(error).to.exist;
275
+ expect(error.code).to.equal('INVALID_TOKEN');
276
+ expect(error.status).to.equal(401);
277
+ });
278
+ it('should reject token with wrong audience', async () => {
279
+ const token = jwt.sign({
280
+ sub: 'user123',
281
+ aud: 'wrong-audience',
282
+ }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
283
+ const config = {
284
+ method: 'secret',
285
+ secret: testSecret,
286
+ audience: 'expected-audience',
287
+ };
288
+ const middleware = createJwtMiddleware(config);
289
+ const req = createMockReq(`Bearer ${token}`);
290
+ const res = createMockRes();
291
+ const { next, error } = createMockNext();
292
+ await middleware(req, res, next);
293
+ expect(error).to.exist;
294
+ expect(error.code).to.equal('INVALID_TOKEN');
295
+ expect(error.status).to.equal(401);
296
+ });
297
+ it('should reject token with unsupported algorithm', async () => {
298
+ const token = jwt.sign({ sub: 'user123' }, testSecret, { algorithm: 'HS512', expiresIn: '1h' });
299
+ const config = {
300
+ method: 'secret',
301
+ secret: testSecret,
302
+ algorithms: ['HS256'], // Only HS256 allowed
303
+ };
304
+ const middleware = createJwtMiddleware(config);
305
+ const req = createMockReq(`Bearer ${token}`);
306
+ const res = createMockRes();
307
+ const { next, error } = createMockNext();
308
+ await middleware(req, res, next);
309
+ expect(error).to.exist;
310
+ expect(error.code).to.equal('INVALID_TOKEN');
311
+ expect(error.status).to.equal(401);
312
+ });
313
+ });
314
+ describe('Configuration Tests', () => {
315
+ it('should work with RS256 and public key file', async () => {
316
+ const token = jwt.sign({ sub: 'user123' }, rsaPrivateKey, { algorithm: 'RS256', expiresIn: '1h' });
317
+ const config = {
318
+ method: 'publicKey',
319
+ publicKeyPath,
320
+ };
321
+ const middleware = createJwtMiddleware(config);
322
+ const req = createMockReq(`Bearer ${token}`);
323
+ const res = createMockRes();
324
+ const { next, error } = createMockNext();
325
+ await middleware(req, res, next);
326
+ expect(error).to.be.undefined;
327
+ expect(req.jwt).to.exist;
328
+ });
329
+ it('should work with HS256 and secret', async () => {
330
+ const token = jwt.sign({ sub: 'user123' }, testSecret, { algorithm: 'HS256', expiresIn: '1h' });
331
+ const config = {
332
+ method: 'secret',
333
+ secret: testSecret,
334
+ };
335
+ const middleware = createJwtMiddleware(config);
336
+ const req = createMockReq(`Bearer ${token}`);
337
+ const res = createMockRes();
338
+ const { next, error } = createMockNext();
339
+ await middleware(req, res, next);
340
+ expect(error).to.be.undefined;
341
+ expect(req.jwt).to.exist;
342
+ });
343
+ it('should respect clock tolerance for exp/nbf', async () => {
344
+ // Token expires in 3 seconds
345
+ const token = jwt.sign({ sub: 'user123' }, testSecret, { algorithm: 'HS256', expiresIn: '3s' });
346
+ // Wait for token to expire
347
+ await new Promise(resolve => setTimeout(resolve, 4000));
348
+ const config = {
349
+ method: 'secret',
350
+ secret: testSecret,
351
+ clockTolerance: 10, // 10 second tolerance
352
+ };
353
+ const middleware = createJwtMiddleware(config);
354
+ const req = createMockReq(`Bearer ${token}`);
355
+ const res = createMockRes();
356
+ const { next, error } = createMockNext();
357
+ await middleware(req, res, next);
358
+ // Should pass because of clock tolerance
359
+ expect(error).to.be.undefined;
360
+ expect(req.jwt).to.exist;
361
+ });
362
+ });
363
+ describe('Error Message Sanitization', () => {
364
+ let originalNodeEnv;
365
+ beforeEach(() => {
366
+ originalNodeEnv = process.env.NODE_ENV;
367
+ });
368
+ afterEach(() => {
369
+ process.env.NODE_ENV = originalNodeEnv;
370
+ });
371
+ it('should sanitize MISSING_TOKEN in production', () => {
372
+ process.env.NODE_ENV = 'production';
373
+ const result = sanitizeErrorMessage('MISSING_TOKEN', 'No authorization token provided');
374
+ expect(result).to.equal('Authentication required');
375
+ });
376
+ it('should sanitize INVALID_TOKEN_FORMAT in production', () => {
377
+ process.env.NODE_ENV = 'production';
378
+ const result = sanitizeErrorMessage('INVALID_TOKEN_FORMAT', 'Invalid authorization format');
379
+ expect(result).to.equal('Invalid authentication format');
380
+ });
381
+ it('should sanitize TOKEN_EXPIRED in production', () => {
382
+ process.env.NODE_ENV = 'production';
383
+ const result = sanitizeErrorMessage('TOKEN_EXPIRED', 'Token has expired');
384
+ expect(result).to.equal('Authentication token has expired');
385
+ });
386
+ it('should sanitize TOKEN_NOT_ACTIVE in production', () => {
387
+ process.env.NODE_ENV = 'production';
388
+ const result = sanitizeErrorMessage('TOKEN_NOT_ACTIVE', 'Token is not yet valid');
389
+ expect(result).to.equal('Authentication token not yet valid');
390
+ });
391
+ it('should sanitize INVALID_TOKEN in production', () => {
392
+ process.env.NODE_ENV = 'production';
393
+ const result = sanitizeErrorMessage('INVALID_TOKEN', 'Token verification failed');
394
+ expect(result).to.equal('Invalid authentication token');
395
+ });
396
+ });
397
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olane/o-server",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -40,7 +40,10 @@
40
40
  "@types/cors": "^2.8.17",
41
41
  "@types/express": "^4.17.21",
42
42
  "@types/jest": "^30.0.0",
43
+ "@types/jsonwebtoken": "^9.0.10",
44
+ "@types/mocha": "^10.0.10",
43
45
  "@types/node": "^20.14.0",
46
+ "@types/supertest": "^6.0.3",
44
47
  "@typescript-eslint/eslint-plugin": "^8.34.1",
45
48
  "@typescript-eslint/parser": "^8.34.1",
46
49
  "aegir": "^47.0.21",
@@ -50,6 +53,7 @@
50
53
  "globals": "^16.2.0",
51
54
  "jest": "^30.0.0",
52
55
  "prettier": "^3.5.3",
56
+ "supertest": "^7.2.2",
53
57
  "ts-jest": "^29.4.0",
54
58
  "ts-node": "^10.9.2",
55
59
  "tsconfig-paths": "^4.2.0",
@@ -57,11 +61,13 @@
57
61
  "typescript": "5.4.5"
58
62
  },
59
63
  "dependencies": {
60
- "@olane/o-core": "0.8.0",
64
+ "@olane/o-core": "0.8.2",
61
65
  "cors": "^2.8.5",
62
66
  "debug": "^4.4.1",
63
67
  "dotenv": "^16.5.0",
64
- "express": "^4.19.2"
68
+ "express": "^4.21.2",
69
+ "jsonwebtoken": "^9.0.3",
70
+ "zod": "^3.25.76"
65
71
  },
66
- "gitHead": "314bb7d07b7af13705bb3069a90da4120bf16eb7"
72
+ "gitHead": "9e35c874d849d051bcffe483fd2a8c2b3ecf68cc"
67
73
  }