impulse-api 3.0.3 → 3.0.5

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/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "test": "nyc --reporter=lcov --reporter=text-summary mocha",
13
13
  "test-server": "node ./test/integration/test-server.js"
14
14
  },
15
- "version": "3.0.3",
15
+ "version": "3.0.5",
16
16
  "engines": {
17
17
  "node": ">=22"
18
18
  },
package/readme.md CHANGED
@@ -95,16 +95,94 @@ Impulse-Api comes with [JSON web token](https://www.npmjs.com/package/jsonwebtok
95
95
 
96
96
  Additionally, any variables that are encoded in the token are accessible once the token has been decoded via `inputs.decoded`.
97
97
 
98
- Token generation is handled via `Impulse.Auth` which again utilizes the [JSON web token](https://www.npmjs.com/package/jsonwebtoken) package.
98
+ #### Token Generation
99
+
100
+ Token generation is handled via the `Auth` class. You must create an `Auth` instance with your `secretKey`, then you can generate and verify tokens without passing the secret key on every call.
101
+
102
+ ```js
103
+ const Impulse = require('impulse-api');
104
+ const Auth = Impulse.Auth;
105
+
106
+ // Create an Auth instance with your secret key
107
+ const auth = new Auth('your-secret-key');
108
+
109
+ // Generate a token (no need to pass secretKey again)
110
+ const token = auth.generateToken({
111
+ userId: '123',
112
+ username: 'john.doe',
113
+ role: 'admin'
114
+ });
115
+
116
+ // Verify a token (no need to pass secretKey again)
117
+ const decoded = auth.verifyToken(token);
118
+ console.log(decoded.userId); // '123'
119
+ console.log(decoded.username); // 'john.doe'
120
+ ```
121
+
122
+ **Important:** The `Auth` class requires a `secretKey` in the constructor. If you don't provide one, it will throw an error.
123
+
124
+ ```js
125
+ // This will throw an error
126
+ const auth = new Auth(); // Error: Auth instance must be initialized with secretKey
127
+
128
+ // This is correct
129
+ const auth = new Auth('your-secret-key');
130
+ ```
131
+
132
+ When using the server with `secretKey` in the config, the server automatically creates an `Auth` instance internally. You can also create your own `Auth` instances for token generation in your application code (e.g., in login routes).
133
+
134
+ **Example: Login Route**
135
+
136
+ Here's a complete example of a login route that generates tokens:
99
137
 
100
138
  ```js
101
- const { Auth } = require('impulse-api');
102
- { decode: [Function],
103
- verify: [Function],
104
- sign: [Function],
105
- JsonWebTokenError: [Function: JsonWebTokenError],
106
- NotBeforeError: [Function: NotBeforeError],
107
- TokenExpiredError: [Function: TokenExpiredError] }
139
+ // routes/auth.js
140
+ const Impulse = require('impulse-api');
141
+ const Auth = Impulse.Auth;
142
+
143
+ // Create Auth instance with your secret key (same as server config)
144
+ const auth = new Auth(process.env.SECRET_KEY || 'your-secret-key');
145
+
146
+ exports.login = {
147
+ name: 'login',
148
+ description: 'User login endpoint',
149
+ method: 'post',
150
+ endpoint: '/api/login',
151
+ version: 'v1',
152
+ inputs: {
153
+ email: {
154
+ required: true,
155
+ validate: (val) => typeof val === 'string' && val.includes('@')
156
+ },
157
+ password: {
158
+ required: true
159
+ }
160
+ },
161
+ run: (services, inputs, next) => {
162
+ // Authenticate user (check database, etc.)
163
+ const user = services.userService.authenticate(inputs.email, inputs.password);
164
+
165
+ if (!user) {
166
+ return next(401, { error: 'Invalid credentials' });
167
+ }
168
+
169
+ // Generate token with user data
170
+ const token = auth.generateToken({
171
+ userId: user.id,
172
+ email: user.email,
173
+ role: user.role
174
+ });
175
+
176
+ next(200, {
177
+ token,
178
+ user: {
179
+ id: user.id,
180
+ email: user.email,
181
+ role: user.role
182
+ }
183
+ });
184
+ }
185
+ };
108
186
  ```
109
187
 
110
188
  #### Custom JWT Validation
package/src/api.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const Server = require('./server');
2
2
  const Errors = require('./errors');
3
- const JSONWebToken = require('jsonwebtoken');
3
+ const Auth = require('./auth');
4
4
 
5
5
  class Api {
6
6
  constructor(config) {
@@ -10,6 +10,6 @@ class Api {
10
10
  }
11
11
 
12
12
  Api.Errors = Errors;
13
- Api.Auth = JSONWebToken;
13
+ Api.Auth = Auth;
14
14
 
15
15
  module.exports = Api;
package/src/auth.js CHANGED
@@ -1,11 +1,21 @@
1
1
  const jwt = require('jsonwebtoken');
2
- const auth = {
3
- generateToken(params, config) {
4
- return(jwt.sign(params, config.secretKey));
5
- },
6
- verifyToken(token, secretKey) {
7
- return jwt.verify(token, secretKey);
8
- },
2
+
3
+ class Auth {
4
+ constructor(secretKey) {
5
+ if (!secretKey) {
6
+ throw new Error('Auth instance must be initialized with secretKey');
7
+ }
8
+ this.secretKey = secretKey;
9
+ }
10
+
11
+ generateToken(params) {
12
+ return jwt.sign(params, this.secretKey);
13
+ }
14
+
15
+ verifyToken(token) {
16
+ return jwt.verify(token, this.secretKey);
17
+ }
18
+
9
19
  // Helper function to create custom validators
10
20
  createCustomValidator(validatorFn) {
11
21
  if (typeof validatorFn !== 'function') {
@@ -13,6 +23,6 @@ const auth = {
13
23
  }
14
24
  return validatorFn;
15
25
  }
16
- };
26
+ }
17
27
 
18
- module.exports = auth;
28
+ module.exports = Auth;
package/src/server.js CHANGED
@@ -36,6 +36,7 @@ class Server {
36
36
  this.heartbeat = config.heartbeat || null;
37
37
  this.container = new Container(config.services);
38
38
  this.secretKey = config.secretKey;
39
+ this.auth = config.secretKey ? new Auth(config.secretKey) : null;
39
40
  this.tokenValidator = config.tokenValidator || null;
40
41
  this.errors = config.errors || Errors;
41
42
  process.env.NODE_ENV = this.env || 'dev';
@@ -225,7 +226,10 @@ class Server {
225
226
  decoded = await this.tokenValidator(token, this.container);
226
227
  } else {
227
228
  // Default JWT validation
228
- decoded = await Auth.verifyToken(token, this.secretKey);
229
+ if (!this.auth) {
230
+ throw new Error(`Route ${route.name} requires token auth but server was not initialized with secretKey`);
231
+ }
232
+ decoded = this.auth.verifyToken(token);
229
233
  }
230
234
 
231
235
  // Allow route-specific validation to override the global validator
@@ -99,8 +99,9 @@ exports.testRoute = {
99
99
  await api.init();
100
100
 
101
101
  // Test default JWT validation
102
- const token = Auth.generateToken({ userId: 'test-user' }, { secretKey: 'test-secret' });
103
- const decoded = Auth.verifyToken(token, 'test-secret');
102
+ const auth = new Auth('test-secret');
103
+ const token = auth.generateToken({ userId: 'test-user' });
104
+ const decoded = auth.verifyToken(token);
104
105
  assert.strictEqual(decoded.userId, 'test-user');
105
106
  });
106
107
  });
@@ -290,8 +291,9 @@ exports.testRoute = {
290
291
  await api.init();
291
292
 
292
293
  // Test that default JWT validation still works
293
- const token = Auth.generateToken({ userId: 'legacy-user' }, { secretKey: 'test-secret' });
294
- const decoded = Auth.verifyToken(token, 'test-secret');
294
+ const auth = new Auth('test-secret');
295
+ const token = auth.generateToken({ userId: 'legacy-user' });
296
+ const decoded = auth.verifyToken(token);
295
297
  assert.strictEqual(decoded.userId, 'legacy-user');
296
298
  });
297
299
  });
@@ -1,4 +1,5 @@
1
1
  const Server = require('../src/server');
2
+ const Auth = require('../src/auth');
2
3
  const assert = require('assert');
3
4
  const sinon = require('sinon');
4
5
 
@@ -355,4 +356,147 @@ describe('server-test', () => {
355
356
  sinon.assert.notCalled(spy);
356
357
  });
357
358
  });
359
+
360
+ describe('Auth module', () => {
361
+ describe('instantiation', () => {
362
+ it('should throw an error if secretKey is not provided', () => {
363
+ try {
364
+ new Auth();
365
+ assert.fail('Should have thrown an error');
366
+ } catch (e) {
367
+ assert.contains(e.message, 'must be initialized with secretKey');
368
+ }
369
+ });
370
+
371
+ it('should throw an error if secretKey is null', () => {
372
+ try {
373
+ new Auth(null);
374
+ assert.fail('Should have thrown an error');
375
+ } catch (e) {
376
+ assert.contains(e.message, 'must be initialized with secretKey');
377
+ }
378
+ });
379
+
380
+ it('should throw an error if secretKey is undefined', () => {
381
+ try {
382
+ new Auth(undefined);
383
+ assert.fail('Should have thrown an error');
384
+ } catch (e) {
385
+ assert.contains(e.message, 'must be initialized with secretKey');
386
+ }
387
+ });
388
+
389
+ it('should create an instance when secretKey is provided', () => {
390
+ const auth = new Auth('test-secret-key');
391
+ assert.strictEqual(auth.secretKey, 'test-secret-key');
392
+ });
393
+ });
394
+
395
+ describe('generateToken', () => {
396
+ it('should generate a token without requiring secretKey parameter', () => {
397
+ const auth = new Auth('test-secret-key');
398
+ const params = { userId: 'test-user', role: 'admin' };
399
+ const token = auth.generateToken(params);
400
+ assert.strictEqual(typeof token, 'string');
401
+ assert.strictEqual(token.length > 0, true);
402
+ });
403
+
404
+ it('should generate different tokens for different params', () => {
405
+ const auth = new Auth('test-secret-key');
406
+ const token1 = auth.generateToken({ userId: 'user1' });
407
+ const token2 = auth.generateToken({ userId: 'user2' });
408
+ assert.notStrictEqual(token1, token2);
409
+ });
410
+ });
411
+
412
+ describe('verifyToken', () => {
413
+ it('should verify a token without requiring secretKey parameter', () => {
414
+ const auth = new Auth('test-secret-key');
415
+ const params = { userId: 'test-user', role: 'admin' };
416
+ const token = auth.generateToken(params);
417
+ const decoded = auth.verifyToken(token);
418
+ assert.strictEqual(decoded.userId, 'test-user');
419
+ assert.strictEqual(decoded.role, 'admin');
420
+ });
421
+
422
+ it('should throw an error when verifying a token with wrong secretKey', () => {
423
+ const auth1 = new Auth('secret-key-1');
424
+ const auth2 = new Auth('secret-key-2');
425
+ const token = auth1.generateToken({ userId: 'test-user' });
426
+ try {
427
+ auth2.verifyToken(token);
428
+ assert.fail('Should have thrown an error');
429
+ } catch (e) {
430
+ assert.contains(e.message, 'invalid signature');
431
+ }
432
+ });
433
+
434
+ it('should throw an error when verifying an invalid token', () => {
435
+ const auth = new Auth('test-secret-key');
436
+ try {
437
+ auth.verifyToken('invalid-token-string');
438
+ assert.fail('Should have thrown an error');
439
+ } catch (e) {
440
+ assert.strictEqual(typeof e.message, 'string');
441
+ }
442
+ });
443
+ });
444
+
445
+ describe('createCustomValidator', () => {
446
+ it('should throw an error if validator is not a function', () => {
447
+ const auth = new Auth('test-secret-key');
448
+ try {
449
+ auth.createCustomValidator('not-a-function');
450
+ assert.fail('Should have thrown an error');
451
+ } catch (e) {
452
+ assert.contains(e.message, 'must be a function');
453
+ }
454
+ });
455
+
456
+ it('should return the validator function if valid', () => {
457
+ const auth = new Auth('test-secret-key');
458
+ const validatorFn = () => true;
459
+ const result = auth.createCustomValidator(validatorFn);
460
+ assert.strictEqual(result, validatorFn);
461
+ });
462
+ });
463
+ });
464
+
465
+ describe('Server Auth integration', () => {
466
+ it('should create auth instance when secretKey is provided', () => {
467
+ const server = new Server({
468
+ name: 'test-server',
469
+ routeDir: './test-routes',
470
+ port: 4000,
471
+ env: 'test',
472
+ secretKey: 'test-secret-key',
473
+ services: {}
474
+ });
475
+ assert.strictEqual(server.auth !== null, true);
476
+ assert.strictEqual(server.auth.secretKey, 'test-secret-key');
477
+ });
478
+
479
+ it('should not create auth instance when secretKey is not provided', () => {
480
+ const server = new Server({
481
+ name: 'test-server',
482
+ routeDir: './test-routes',
483
+ port: 4000,
484
+ env: 'test',
485
+ services: {}
486
+ });
487
+ assert.strictEqual(server.auth, null);
488
+ });
489
+
490
+ it('should not create auth instance when secretKey is null', () => {
491
+ const server = new Server({
492
+ name: 'test-server',
493
+ routeDir: './test-routes',
494
+ port: 4000,
495
+ env: 'test',
496
+ secretKey: null,
497
+ services: {}
498
+ });
499
+ assert.strictEqual(server.auth, null);
500
+ });
501
+ });
358
502
  });