@dwtechs/toker-express 0.3.0 → 0.5.0

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
@@ -47,7 +47,7 @@ $ npm i @dwtechs/toker-express
47
47
  ```javascript
48
48
 
49
49
  // @ts-check
50
- import * as tk from "@dwtechs/Toker-express";
50
+ import { refresh as refreshTokens, parseBearerToken, decodeAccess, decodeRefresh } from "@dwtechs/toker-express";
51
51
  import express from "express";
52
52
  const router = express.Router();
53
53
 
@@ -66,16 +66,18 @@ const add = [
66
66
 
67
67
  const refresh = [
68
68
  cEntity.validate,
69
- tk.decodeAccess,
70
- tk.decodeRefresh,
69
+ parseBearerToken,
70
+ decodeAccess,
71
+ decodeRefresh,
71
72
  checkToken,
72
- tk.refresh,
73
+ refresh,
73
74
  cEntity.update,
74
75
  ];
75
76
 
76
77
  const del = [
77
78
  checkToken,
78
- tk.decodeAccess,
79
+ parseBearerToken,
80
+ decodeAccess,
79
81
  cEntity.delete,
80
82
  ];
81
83
 
@@ -126,7 +128,7 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
126
128
  * Express middleware to generate new access and refresh JWT tokens for a user.
127
129
  *
128
130
  * This middleware creates new access and refresh tokens based on:
129
- * 1. The issuer (iss) from `req.decodedAccessToken.iss` if available, OR
131
+ * 1. The issuer (iss) from `res.locals.decodedAccessToken.iss` if available, OR
130
132
  * 2. The user ID from `res.locals.id` if no decoded token is present
131
133
  *
132
134
  * The generated tokens are stored in:
@@ -134,9 +136,9 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
134
136
  * - `req.body.rows[0].accessToken` and `req.body.rows[0].refreshToken` (if rows array exists)
135
137
  *
136
138
  * @param {Request} req - The Express request object. May contain:
137
- * - `req.decodedAccessToken.iss`: User ID from decoded access token
138
139
  * - `req.body.rows`: Optional array where tokens will be added to first element
139
140
  * @param {Response} res - The Express response object. Should contain:
141
+ * - `res.locals.decodedAccessToken.iss`: User ID from decoded access token (checked first), OR
140
142
  * - `res.locals.id`: User ID (used if decodedAccessToken is not available)
141
143
  * Tokens will be added to `res.locals.accessToken` and `res.locals.refreshToken`
142
144
  * @param {NextFunction} next - Express next middleware function
@@ -159,20 +161,42 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
159
161
  function refresh(req: Request, res: Response, next: NextFunction): void {}
160
162
 
161
163
  /**
162
- * Express middleware function to decode and verify an access token from the Authorization header.
164
+ * Express middleware function to parse the bearer token from the Authorization header.
163
165
  *
164
- * This middleware extracts the JWT access token from the Authorization header, validates its format,
165
- * verifies its signature, and attaches the decoded token to req.decodedAccessToken for use by subsequent
166
- * middleware. It only processes requests that have `req.isProtected` set to true.
166
+ * This middleware extracts the JWT token from the Authorization header (Bearer scheme)
167
+ * and stores it in res.locals.accessToken for use by subsequent middleware.
168
+ * It only processes requests that have `res.locals.isProtected` set to true.
167
169
  *
168
170
  * @param {Request} req - The Express request object containing the Authorization header
169
- * @param {Response} _res - The Express response object (not used in this function)
171
+ * @param {Response} res - The Express response object. Should contain:
172
+ * - `res.locals.isProtected`: Boolean flag to determine if route requires JWT protection
173
+ * Parsed token will be added to `res.locals.accessToken`
170
174
  * @param {NextFunction} next - The next middleware function to be called
171
175
  *
172
- * @returns {void} Calls the next middleware function, either with an error or successfully
173
- *
176
+ * @returns {void} Calls the next middleware function with an error object if parsing fails.
177
+ *
174
178
  * @throws {MissingAuthorizationError} If the Authorization header is missing (HTTP 401)
175
179
  * @throws {InvalidBearerFormatError} If the Authorization header format is invalid (HTTP 401)
180
+ *
181
+ */
182
+ function parseBearerToken(req: Request, res: Response, next: NextFunction): void {}
183
+
184
+ /**
185
+ * Express middleware function to decode and verify an access token.
186
+ *
187
+ * This middleware validates the JWT access token from res.locals.accessToken,
188
+ * verifies its signature, and attaches the decoded token to res.locals.decodedAccessToken
189
+ * for use by subsequent middleware. It only processes requests that have `res.locals.isProtected` set to true.
190
+ *
191
+ * @param {Request} req - The Express request object
192
+ * @param {Response} res - The Express response object. Should contain:
193
+ * - `res.locals.isProtected`: Boolean flag to determine if route requires JWT protection
194
+ * - `res.locals.accessToken`: The JWT token to decode
195
+ * Decoded token will be added to `res.locals.decodedAccessToken`
196
+ * @param {NextFunction} next - The next middleware function to be called
197
+ *
198
+ * @returns {void} Calls the next middleware function with an error object if the token is invalid or iss is missing.
199
+ *
176
200
  * @throws {InvalidTokenError} If the token is malformed or has invalid structure (HTTP 401)
177
201
  * @throws {ExpiredTokenError} If the token has expired (exp claim) (HTTP 401)
178
202
  * @throws {InactiveTokenError} If the token cannot be used yet (nbf claim) (HTTP 401)
@@ -184,17 +208,17 @@ function refresh(req: Request, res: Response, next: NextFunction): void {}
184
208
  * - statusCode: 400 - When decoded token is missing required 'iss' claim
185
209
  *
186
210
  */
187
- function decodeAccess(req: Request, _res: Response, next: NextFunction): void {}
211
+ function decodeAccess(req: Request, res: Response, next: NextFunction): void {}
188
212
 
189
213
  /**
190
214
  * Middleware function to decode and verify a refresh token from the request body.
191
215
  *
192
- * @param {Request} req - The request object containing the refresh token in the body.
193
- * @param {Response} _res - The response object (not used in this function).
216
+ * @param {Request} req - The request object containing the refresh token in `req.body.refreshToken`
217
+ * @param {Response} res - The response object. Decoded token will be added to `res.locals.decodedRefreshToken`
194
218
  * @param {NextFunction} next - The next middleware function to be called.
195
219
  *
196
- * @returns {void} Calls the next middleware function with an error object if the token is invalid or required fields are missing.
197
- *
220
+ * @returns {void} Calls the next middleware function with an error object if the token is invalid or iss is missing.
221
+ *
198
222
  * @throws {InvalidTokenError} If the token is malformed or has invalid structure (HTTP 401)
199
223
  * @throws {InvalidSecretsError} If the secrets configuration is invalid (HTTP 500)
200
224
  * @throws {ExpiredTokenError} If the refresh token has expired (exp claim) (HTTP 401)
@@ -214,7 +238,7 @@ function decodeRefresh(req: Request, _res: Response, next: NextFunction): void {
214
238
  This function will look for an ISS (user ID) from two possible sources:
215
239
 
216
240
  ```Javascript
217
- let iss = req.decodedAccessToken?.iss;
241
+ let iss = res.locals?.decodedAccessToken?.iss;
218
242
 
219
243
  if (!iss)
220
244
  iss = res.locals.id ?? null;
@@ -235,18 +259,64 @@ if (isArray(rbr, ">=", 1) && isObject(rbr[0])) {
235
259
 
236
260
  ### JWT Decoding
237
261
 
238
- decodeAccess() functions will look for a bearer in authorization headers.
262
+ #### Route Protection with isProtected
263
+
264
+ The `parseBearerToken()` and `decodeAccess()` middlewares only process requests when `res.locals.isProtected` is set to `true`. This allows you to selectively protect routes that require authentication.
265
+
266
+ You should set this flag in a middleware before calling these functions:
239
267
 
240
268
  ```Javascript
241
- const bearer = req.headers.authorization;
269
+ // Example middleware to mark route as protected
270
+ function protectRoute(req, res, next) {
271
+ res.locals.isProtected = true;
272
+ next();
273
+ }
274
+
275
+ // Usage
276
+ router.get('/protected-route', protectRoute, tk.parseBearerToken, tk.decodeAccess, yourHandler);
242
277
  ```
243
278
 
244
- It will then send the decoded token in the res object.
279
+ If `res.locals.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.
280
+
281
+ #### Access Token Processing
282
+
283
+ The access token processing is now split into two separate middlewares for better flexibility:
284
+
285
+ 1. **parseBearerToken()** - Extracts the bearer token from the Authorization header
286
+ 2. **decodeAccess()** - Validates and decodes the JWT token
287
+
288
+ ##### parseBearerToken()
289
+
290
+ This middleware extracts the JWT token from the Authorization header using the Bearer scheme.
291
+
292
+ ```Javascript
293
+ const bearer = req.headers.authorization; // "Bearer <token>"
294
+ ```
295
+
296
+ The parsed token is then stored in `res.locals.accessToken`:
297
+
298
+ ```Javascript
299
+ res.locals.accessToken = token;
300
+ ```
301
+
302
+ ##### decodeAccess()
303
+
304
+ This middleware takes the token from `res.locals.accessToken`, validates it, and decodes it.
305
+
306
+ ```Javascript
307
+ const token = res.locals.accessToken;
308
+ ```
309
+
310
+ The decoded token is then stored in `res.locals.decodedAccessToken`:
245
311
 
246
312
  ```Javascript
247
- req.decodedAccessToken = decodedToken;
313
+ res.locals.decodedAccessToken = decodedToken;
248
314
  ```
249
315
 
316
+ **Note:** You should use both middlewares in sequence for full access token processing, or you can use just `parseBearerToken()` if you only need to extract the token without decoding it.
317
+
318
+ #### Refresh Token Decoding
319
+
250
320
  decodeRefresh() functions will look for a token in the client request body.
251
321
 
252
322
  ```Javascript
@@ -256,7 +326,7 @@ const token = req.body.refreshToken;
256
326
  It will then send the decoded token in the res object.
257
327
 
258
328
  ```Javascript
259
- req.decodedRefreshToken = decodedToken;
329
+ res.locals.decodedRefreshToken = decodedToken;
260
330
  ```
261
331
 
262
332
 
@@ -32,19 +32,9 @@ export interface RowWithTokens {
32
32
  [key: string]: any;
33
33
  }
34
34
 
35
- declare global {
36
- namespace Express {
37
- interface Request {
38
- isProtected?: boolean;
39
- decodedAccessToken?: any;
40
- decodedRefreshToken?: any;
41
- }
42
- }
43
- }
44
-
45
35
  declare function refresh(req: Request, res: Response, next: NextFunction): void;
46
- declare function decodeAccess(req: Request, _res: Response, next: NextFunction): void;
47
- declare function decodeRefresh(req: Request, _res: Response, next: NextFunction): void;
36
+ declare function decodeAccess(req: Request, res: Response, next: NextFunction): void;
37
+ declare function decodeRefresh(req: Request, res: Response, next: NextFunction): void;
48
38
 
49
39
  export {
50
40
  refresh,
@@ -38,10 +38,10 @@ 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 refresh(req, res, next) {
41
- var _a, _b, _c;
42
- let iss = (_a = req.decodedAccessToken) === null || _a === void 0 ? void 0 : _a.iss;
41
+ var _a, _b, _c, _d;
42
+ let iss = (_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.decodedAccessToken) === null || _b === void 0 ? void 0 : _b.iss;
43
43
  if (!iss)
44
- iss = (_b = res.locals.id) !== null && _b !== void 0 ? _b : null;
44
+ iss = (_c = res.locals.id) !== null && _c !== void 0 ? _c : null;
45
45
  if (!isValidNumber(iss, 1, 999999999, false))
46
46
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
47
47
  log.debug(`${LOGS_PREFIX}Create tokens for user ${iss}`);
@@ -57,25 +57,30 @@ function refresh(req, res, next) {
57
57
  log.debug(`refreshToken='${rt}', accessToken='${at}'`);
58
58
  res.locals.accessToken = at;
59
59
  res.locals.refreshToken = rt;
60
- const rbr = (_c = req.body) === null || _c === void 0 ? void 0 : _c.rows;
60
+ const rbr = (_d = req.body) === null || _d === void 0 ? void 0 : _d.rows;
61
61
  if (isArray(rbr, ">=", 1) && isObject(rbr[0])) {
62
62
  rbr[0].accessToken = at;
63
63
  rbr[0].refreshToken = rt;
64
64
  }
65
65
  next();
66
66
  }
67
- function decodeAccess(req, _res, next) {
68
- log.debug(`${LOGS_PREFIX}decode access token`);
69
- if (!req.isProtected)
67
+ function parseBearerToken(req, res, next) {
68
+ if (!res.locals.isProtected)
70
69
  return next();
71
- let t;
70
+ log.debug(`${LOGS_PREFIX}parse bearer token`);
72
71
  try {
73
- t = parseBearer(req.headers.authorization);
72
+ res.locals.accessToken = parseBearer(req.headers.authorization);
74
73
  }
75
74
  catch (e) {
76
75
  return next(e);
77
76
  }
78
- log.debug(`${LOGS_PREFIX}accessToken : ${t}`);
77
+ next();
78
+ }
79
+ function decodeAccess(_req, res, next) {
80
+ log.debug(`${LOGS_PREFIX}decode access token`);
81
+ if (!res.locals.isProtected)
82
+ return next();
83
+ const t = res.locals.accessToken;
79
84
  if (!isJWT(t))
80
85
  return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid access token` });
81
86
  let dt = null;
@@ -88,10 +93,10 @@ function decodeAccess(req, _res, next) {
88
93
  if (!isValidNumber(dt.iss, 1, 999999999, false))
89
94
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
90
95
  log.debug(`${LOGS_PREFIX}Decoded access token : ${JSON.stringify(dt)}`);
91
- req.decodedAccessToken = dt;
96
+ res.locals.decodedAccessToken = dt;
92
97
  next();
93
98
  }
94
- function decodeRefresh(req, _res, next) {
99
+ function decodeRefresh(req, res, next) {
95
100
  var _a;
96
101
  const token = (_a = req.body) === null || _a === void 0 ? void 0 : _a.refreshToken;
97
102
  log.debug(`${LOGS_PREFIX}decodeRefresh(token=${token})`);
@@ -107,8 +112,8 @@ function decodeRefresh(req, _res, next) {
107
112
  if (!isValidNumber(dt.iss, 1, 999999999, false))
108
113
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
109
114
  log.debug(`${LOGS_PREFIX}Decoded refresh token : ${JSON.stringify(dt)}`);
110
- req.decodedRefreshToken = dt;
115
+ res.locals.decodedRefreshToken = dt;
111
116
  next();
112
117
  }
113
118
 
114
- export { decodeAccess, decodeRefresh, refresh };
119
+ export { decodeAccess, decodeRefresh, parseBearerToken, refresh };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/toker-express",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Open source JWT management library for Express.js to refresh and decode tokens safely.",
5
5
  "keywords": [
6
6
  "JWT",