@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.
- package/dist/src/interfaces/server-config.interface.d.ts +47 -1
- package/dist/src/interfaces/server-config.interface.d.ts.map +1 -1
- package/dist/src/middleware/error-handler.d.ts +9 -0
- package/dist/src/middleware/error-handler.d.ts.map +1 -1
- package/dist/src/middleware/error-handler.js +49 -3
- package/dist/src/middleware/jwt-auth.d.ts +54 -0
- package/dist/src/middleware/jwt-auth.d.ts.map +1 -0
- package/dist/src/middleware/jwt-auth.js +145 -0
- package/dist/src/o-server.d.ts.map +1 -1
- package/dist/src/o-server.js +53 -23
- package/dist/src/validation/address-validator.d.ts +13 -0
- package/dist/src/validation/address-validator.d.ts.map +1 -0
- package/dist/src/validation/address-validator.js +40 -0
- package/dist/src/validation/index.d.ts +17 -0
- package/dist/src/validation/index.d.ts.map +1 -0
- package/dist/src/validation/index.js +16 -0
- package/dist/src/validation/method-validator.d.ts +13 -0
- package/dist/src/validation/method-validator.d.ts.map +1 -0
- package/dist/src/validation/method-validator.js +36 -0
- package/dist/src/validation/params-sanitizer.d.ts +15 -0
- package/dist/src/validation/params-sanitizer.d.ts.map +1 -0
- package/dist/src/validation/params-sanitizer.js +51 -0
- package/dist/src/validation/request-validator.d.ts +53 -0
- package/dist/src/validation/request-validator.d.ts.map +1 -0
- package/dist/src/validation/request-validator.js +53 -0
- package/dist/src/validation/validation-error.d.ts +11 -0
- package/dist/src/validation/validation-error.d.ts.map +1 -0
- package/dist/src/validation/validation-error.js +12 -0
- package/dist/test/ai.spec.d.ts +0 -1
- package/dist/test/ai.spec.js +20 -13
- package/dist/test/error-security.spec.d.ts +2 -0
- package/dist/test/error-security.spec.d.ts.map +1 -0
- package/dist/test/error-security.spec.js +134 -0
- package/dist/test/input-validation.spec.d.ts +14 -0
- package/dist/test/input-validation.spec.d.ts.map +1 -0
- package/dist/test/input-validation.spec.js +487 -0
- package/dist/test/jwt-auth.spec.d.ts +2 -0
- package/dist/test/jwt-auth.spec.d.ts.map +1 -0
- package/dist/test/jwt-auth.spec.js +397 -0
- 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.
|
|
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.
|
|
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.
|
|
68
|
+
"express": "^4.21.2",
|
|
69
|
+
"jsonwebtoken": "^9.0.3",
|
|
70
|
+
"zod": "^3.25.76"
|
|
65
71
|
},
|
|
66
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "9e35c874d849d051bcffe483fd2a8c2b3ecf68cc"
|
|
67
73
|
}
|