@dwtechs/toker-express 0.7.0 → 0.7.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/README.md CHANGED
@@ -29,7 +29,7 @@ It includes @dwtechs/toker library and adds Express middlewares to be used in a
29
29
 
30
30
  ## Support
31
31
 
32
- - node: 22
32
+ - node: 18
33
33
 
34
34
  This is the oldest targeted versions.
35
35
 
@@ -187,13 +187,13 @@ function refreshTokens(req: Request, res: Response, next: NextFunction): void {}
187
187
  *
188
188
  * This middleware extracts the JWT token from the Authorization header (Bearer scheme)
189
189
  * and stores it in `res.locals.tokens.access` for use by subsequent middleware (typically decodeAccess).
190
- * It only processes requests that have `res.locals.route.isProtected` set to true.
190
+ * It only processes requests that have `res.locals.route.isProtected` or `res.locals.route.protected` set to true.
191
191
  * For non-protected routes, it simply passes control to the next middleware.
192
192
  *
193
193
  * @param {Request} req - The Express request object. Should contain:
194
194
  * - `req.headers.authorization`: Authorization header with Bearer token format
195
195
  * @param {Response} res - The Express response object. Should contain:
196
- * - `res.locals.route.isProtected`: Boolean flag to determine if route requires JWT protection
196
+ * - `res.locals.route.isProtected` or `res.locals.route.protected`: Boolean flag to determine if route requires JWT protection
197
197
  * Parsed token will be added to `res.locals.tokens.access`
198
198
  * @param {NextFunction} next - The next middleware function to be called
199
199
  *
@@ -215,8 +215,8 @@ function parseBearer(req: Request, res: Response, next: NextFunction): void {}
215
215
  * This middleware validates the JWT access token from `res.locals.tokens.access`,
216
216
  * verifies its signature and structure, validates the issuer (iss) claim,
217
217
  * and stores the decoded token in `res.locals.tokens.decodedAccess` for use by
218
- * subsequent middleware. It only processes requests that have `res.locals.route.isProtected`
219
- * set to true. For non-protected routes, it simply passes control to the next middleware.
218
+ * subsequent middleware. It only processes requests that have `res.locals.route.isProtected`
219
+ * or `res.locals.route.protected` set to true. For non-protected routes, it simply passes control to the next middleware.
220
220
  *
221
221
  * Note: By default, this middleware checks token expiration (exp claim) and will reject
222
222
  * expired tokens. For token refresh flows where you need to identify the user even after
@@ -225,7 +225,7 @@ function parseBearer(req: Request, res: Response, next: NextFunction): void {}
225
225
  *
226
226
  * @param {Request} _req - The Express request object (unused)
227
227
  * @param {Response} res - The Express response object. Should contain:
228
- * - `res.locals.route.isProtected`: Boolean flag to determine if route requires JWT protection
228
+ * - `res.locals.route.isProtected` or `res.locals.route.protected`: Boolean flag to determine if route requires JWT protection
229
229
  * - `res.locals.tokens.access`: The JWT token to decode (from parseBearer middleware)
230
230
  * - `res.locals.tokens.ignoreExpiration`: Optional boolean to skip expiration checking (default: false)
231
231
  * Decoded token will be added to `res.locals.tokens.decodedAccess`
@@ -331,16 +331,16 @@ req.body.rows[0].refreshToken = rt;
331
331
 
332
332
  ### JWT Decoding
333
333
 
334
- #### Route Protection with isProtected
334
+ #### Route Protection with isProtected / protected
335
335
 
336
- The `parseBearer()` and `decodeAccess()` middlewares only process requests when `res.locals.route.isProtected` is set to `true`. This allows you to selectively protect routes that require authentication.
336
+ The `parseBearer()` and `decodeAccess()` middlewares only process requests when `res.locals.route.isProtected` or `res.locals.route.protected` is set to `true`. This allows you to selectively protect routes that require authentication.
337
337
 
338
- You should set this flag in a middleware before calling these functions:
338
+ You should set one of these flags in a middleware before calling these functions:
339
339
 
340
340
  ```Javascript
341
341
  // Example middleware to mark route as protected
342
342
  function protectRoute(req, res, next) {
343
- res.locals.route = { isProtected: true };
343
+ res.locals.route = { isProtected: true }; // or { protected: true }
344
344
  next();
345
345
  }
346
346
 
@@ -348,7 +348,7 @@ function protectRoute(req, res, next) {
348
348
  router.get('/protected-route', protectRoute, tk.parseBearer, tk.decodeAccess, yourHandler);
349
349
  ```
350
350
 
351
- If `res.locals.route.isProtected` is `false`, `undefined`, or `null`, these middlewares will simply call `next()` without processing the token, allowing the request to continue to the next middleware.
351
+ If both `res.locals.route.isProtected` and `res.locals.route.protected` are `false`, `undefined`, or `null`, these middlewares will simply call `next()` without processing the token, allowing the request to continue to the next middleware.
352
352
 
353
353
  #### Access Token Processing
354
354
 
@@ -385,8 +385,6 @@ The decoded token is then stored in `res.locals.tokens.decodedAccess`:
385
385
  res.locals.tokens.decodedAccess = decodedToken;
386
386
  ```
387
387
 
388
- **Important:** This middleware **ignores token expiration** by design. This allows expired access tokens to be decoded, which is useful for token refresh flows where you need to identify the user even after their access token has expired.
389
-
390
388
  **Note:** You should use both middlewares in sequence for full access token processing, or you can use just `parseBearer()` if you only need to extract the token without decoding it.
391
389
 
392
390
  #### Refresh Token Decoding
@@ -410,6 +408,7 @@ res.locals.tokens.decodedRefresh = decodedToken;
410
408
 
411
409
  **Token-express.js** uses **[@dwtechs/Winstan](https://www.npmjs.com/package/@dwtechs/winstan)** library for logging.
412
410
  All logs are in debug mode. Meaning they should not appear in production mode.
411
+ Log messages are passed as lazy functions (`() => string`) so string interpolation and serialization are skipped entirely when debug logging is disabled.
413
412
 
414
413
  ## Contributors
415
414
 
@@ -38,11 +38,12 @@ const secrets = [TOKEN_SECRET];
38
38
  const accessDuration = isNumber(ACCESS_TOKEN_DURATION, false) ? Number(ACCESS_TOKEN_DURATION) : 600;
39
39
  const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? Number(REFRESH_TOKEN_DURATION) : 86400;
40
40
  function createTokens(req, res, next) {
41
- var _a, _b;
42
- const iss = (_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.id;
43
- if (!isValidInteger(iss, 1, 999999999, false))
44
- return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
45
- log.debug(`${LOGS_PREFIX}Create tokens for user ${iss}`);
41
+ const iss = res.locals?.user?.id;
42
+ if (!isValidInteger(iss, 1, 999999999, false)) {
43
+ next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
44
+ return;
45
+ }
46
+ log.debug(() => `${LOGS_PREFIX}Create tokens for user ${iss}`);
46
47
  let at;
47
48
  let rt;
48
49
  try {
@@ -50,19 +51,21 @@ function createTokens(req, res, next) {
50
51
  rt = sign(iss, refreshDuration, "refresh", secrets);
51
52
  }
52
53
  catch (err) {
53
- return next(err);
54
+ next(err);
55
+ return;
54
56
  }
55
- log.debug(`refreshToken='${rt}', accessToken='${at}'`);
57
+ log.debug(() => `refreshToken='${rt}', accessToken='${at}'`);
56
58
  req.body.rows[0].accessToken = at;
57
59
  req.body.rows[0].refreshToken = rt;
58
60
  next();
59
61
  }
60
62
  function refreshTokens(req, res, next) {
61
- var _a, _b, _c;
62
- const iss = (_c = (_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.tokens) === null || _b === void 0 ? void 0 : _b.decodedAccess) === null || _c === void 0 ? void 0 : _c.iss;
63
- if (!isValidInteger(iss, 1, 999999999, false))
64
- return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
65
- log.debug(`${LOGS_PREFIX}Create tokens for user ${iss}`);
63
+ const iss = res.locals?.tokens?.decodedAccess?.iss;
64
+ if (!isValidInteger(iss, 1, 999999999, false)) {
65
+ next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
66
+ return;
67
+ }
68
+ log.debug(() => `${LOGS_PREFIX}Create tokens for user ${iss}`);
66
69
  let at;
67
70
  let rt;
68
71
  try {
@@ -70,65 +73,84 @@ function refreshTokens(req, res, next) {
70
73
  rt = sign(iss, refreshDuration, "refresh", secrets);
71
74
  }
72
75
  catch (err) {
73
- return next(err);
76
+ next(err);
77
+ return;
74
78
  }
75
- log.debug(`refreshToken='${rt}', accessToken='${at}'`);
79
+ log.debug(() => `refreshToken='${rt}', accessToken='${at}'`);
76
80
  req.body.rows[0].accessToken = at;
77
81
  req.body.rows[0].refreshToken = rt;
78
82
  next();
79
83
  }
80
84
  function parseBearer(req, res, next) {
81
- var _a, _b;
82
- if (!((_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.route) === null || _b === void 0 ? void 0 : _b.isProtected))
83
- return next();
84
- log.debug(`${LOGS_PREFIX}parse bearer to get access token`);
85
+ var _a;
86
+ if (!res.locals?.route?.isProtected && !res.locals?.route?.protected) {
87
+ next();
88
+ return;
89
+ }
90
+ log.debug(() => `${LOGS_PREFIX}parse bearer to get access token`);
85
91
  try {
86
- res.locals.tokens = Object.assign(Object.assign({}, res.locals.tokens), { access: parseBearer$1(req.headers.authorization) });
92
+ (_a = res.locals).tokens ?? (_a.tokens = {});
93
+ res.locals.tokens.access = parseBearer$1(req.headers.authorization);
87
94
  }
88
95
  catch (e) {
89
- return next(e);
96
+ next(e);
97
+ return;
90
98
  }
91
99
  next();
92
100
  }
93
101
  function decodeAccess(_req, res, next) {
94
- var _a, _b, _c, _d, _e, _f, _g;
95
- log.debug(`${LOGS_PREFIX}decode access token`);
96
- if (!((_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.route) === null || _b === void 0 ? void 0 : _b.isProtected))
97
- return next();
98
- const t = (_d = (_c = res.locals) === null || _c === void 0 ? void 0 : _c.tokens) === null || _d === void 0 ? void 0 : _d.access;
99
- const ignoreExpiration = (_g = (_f = (_e = res.locals) === null || _e === void 0 ? void 0 : _e.tokens) === null || _f === void 0 ? void 0 : _f.ignoreExpiration) !== null && _g !== void 0 ? _g : false;
100
- if (!isJWT(t))
101
- return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid access token` });
102
+ var _a;
103
+ log.debug(() => `${LOGS_PREFIX}decode access token`);
104
+ if (!res.locals?.route?.isProtected && !res.locals?.route?.protected) {
105
+ next();
106
+ return;
107
+ }
108
+ const t = res.locals?.tokens?.access;
109
+ const ignoreExpiration = res.locals?.tokens?.ignoreExpiration ?? false;
110
+ if (!isJWT(t)) {
111
+ next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid access token` });
112
+ return;
113
+ }
102
114
  let dt = null;
103
115
  try {
104
116
  dt = verify(t, secrets, ignoreExpiration);
105
117
  }
106
118
  catch (e) {
107
- return next(e);
119
+ next(e);
120
+ return;
121
+ }
122
+ if (!isValidInteger(dt.iss, 1, 999999999, false)) {
123
+ next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
124
+ return;
108
125
  }
109
- if (!isValidInteger(dt.iss, 1, 999999999, false))
110
- return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
111
- log.debug(`${LOGS_PREFIX}Decoded access token : ${JSON.stringify(dt)}`);
112
- res.locals.tokens = Object.assign(Object.assign({}, res.locals.tokens), { decodedAccess: dt });
126
+ log.debug(() => `${LOGS_PREFIX}Decoded access token : ${JSON.stringify(dt)}`);
127
+ (_a = res.locals).tokens ?? (_a.tokens = {});
128
+ res.locals.tokens.decodedAccess = dt;
113
129
  next();
114
130
  }
115
131
  function decodeRefresh(req, res, next) {
116
132
  var _a;
117
- const t = (_a = req.body) === null || _a === void 0 ? void 0 : _a.refreshToken;
118
- log.debug(`${LOGS_PREFIX}decodeRefresh(token=${t})`);
119
- if (!isJWT(t))
120
- return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid refresh token` });
133
+ const t = req.body?.refreshToken;
134
+ log.debug(() => `${LOGS_PREFIX}decodeRefresh(token=${t})`);
135
+ if (!isJWT(t)) {
136
+ next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid refresh token` });
137
+ return;
138
+ }
121
139
  let dt = null;
122
140
  try {
123
141
  dt = verify(t, secrets, false);
124
142
  }
125
143
  catch (e) {
126
- return next(e);
144
+ next(e);
145
+ return;
146
+ }
147
+ if (!isValidInteger(dt.iss, 1, 999999999, false)) {
148
+ next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
149
+ return;
127
150
  }
128
- if (!isValidInteger(dt.iss, 1, 999999999, false))
129
- return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
130
- log.debug(`${LOGS_PREFIX}Decoded refresh token : ${JSON.stringify(dt)}`);
131
- res.locals.tokens = Object.assign(Object.assign({}, res.locals.tokens), { decodedRefresh: dt });
151
+ log.debug(() => `${LOGS_PREFIX}Decoded refresh token : ${JSON.stringify(dt)}`);
152
+ (_a = res.locals).tokens ?? (_a.tokens = {});
153
+ res.locals.tokens.decodedRefresh = dt;
132
154
  next();
133
155
  }
134
156
 
package/package.json CHANGED
@@ -1,35 +1,41 @@
1
1
  {
2
2
  "name": "@dwtechs/toker-express",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
+ "type": "module",
4
5
  "description": "Open source JWT management library for Express.js to refresh and decode tokens safely.",
5
6
  "keywords": [
6
7
  "JWT",
7
8
  "Express"
8
9
  ],
9
10
  "homepage": "https://github.com/DWTechs/Toker-express.js",
10
- "main": "dist/toker-express",
11
- "types": "dist/toker-express",
11
+ "main": "dist/toker-express.js",
12
+ "types": "dist/toker-express.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "import": "./dist/toker-express.js",
16
+ "types": "./dist/toker-express.d.ts"
17
+ }
18
+ },
12
19
  "repository": {
13
20
  "type": "git",
14
21
  "url": "https://github.com/DWTechs/Toker-express.js"
15
22
  },
16
23
  "bugs": {
17
- "url": "https://github.com/DWTechs/Toker-express.js/issues",
18
- "email": ""
24
+ "url": "https://github.com/DWTechs/Toker-express.js/issues"
19
25
  },
20
26
  "license": "MIT",
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
21
30
  "author": {
22
31
  "name": "Ludovic Cluber",
23
32
  "email": "http://www.lcluber.com/contact",
24
33
  "url": "http://www.lcluber.com"
25
34
  },
26
- "contributors": [],
27
35
  "scripts": {
28
- "start": "",
29
36
  "prebuild": "npm install",
30
37
  "build": "node ./scripts/clear && tsc && npm run rollup && node ./scripts/copy && npm run test",
31
38
  "rollup:mjs": "rollup --config rollup.config.mjs",
32
- "rollup:cjs": "rollup --config rollup.config.cjs.mjs",
33
39
  "rollup": "npm run rollup:mjs",
34
40
  "test": "jest --coverage"
35
41
  },
@@ -38,17 +44,16 @@
38
44
  ],
39
45
  "dependencies": {
40
46
  "@dwtechs/checkard": "3.6.0",
41
- "@dwtechs/toker": "0.1.1",
42
- "@dwtechs/winstan": "0.5.0"
47
+ "@dwtechs/toker": "0.1.2",
48
+ "@dwtechs/winstan": "0.7.0"
43
49
  },
44
50
  "devDependencies": {
45
51
  "@babel/preset-env": "7.26.0",
46
52
  "@rollup/plugin-node-resolve": "15.3.0",
47
- "@types/express": "5.0.3",
53
+ "@types/express": "5.0.6",
48
54
  "babel-jest": "29.7.0",
49
- "core-js": "3.38.1",
50
55
  "jest": "29.7.0",
51
56
  "rollup": "4.24.0",
52
- "typescript": "5.9.2"
57
+ "typescript": "6.0.3"
53
58
  }
54
59
  }