@dwtechs/toker-express 0.2.1 → 0.4.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
@@ -123,26 +123,38 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
123
123
  ```typescript
124
124
 
125
125
  /**
126
- * Refreshes the JWT tokens for a user.
126
+ * Express middleware to generate new access and refresh JWT tokens for a user.
127
127
  *
128
- * This function generates new access and refresh tokens for a consumer based on the provided
129
- * decoded access token from req.decodedAccessToken or user ID from req.body.rows[0].id. It validates the issuer (iss) and
130
- * creates new tokens if the validation is successful. The new tokens are then added to the
131
- * response locals object and optionally to req.body.rows[0] if rows is an array with at least one element.
128
+ * This middleware creates new access and refresh tokens based on:
129
+ * 1. The issuer (iss) from `res.locals.decodedAccessToken.iss` if available, OR
130
+ * 2. The user ID from `res.locals.id` if no decoded token is present
132
131
  *
133
- * @param {Request} req - The request object containing req.decodedAccessToken or req.body.rows[0].id.
134
- * @param {Response} res - The response object where the new tokens will be added in res.locals.
135
- * @param {NextFunction} next - The next middleware function in the Express.js request-response cycle.
132
+ * The generated tokens are stored in:
133
+ * - `res.locals.accessToken` and `res.locals.refreshToken`
134
+ * - `req.body.rows[0].accessToken` and `req.body.rows[0].refreshToken` (if rows array exists)
136
135
  *
137
- * @returns {void} Calls the next middleware function with an error if the issuer is invalid,
138
- * otherwise proceeds to the next middleware function.
139
- *
140
- * @throws {InvalidIssuerError} If the issuer (iss) is not a string or number (HTTP 400)
141
- * @throws {InvalidSecretsError} If the secrets array is empty or invalid (HTTP 500)
142
- * @throws {InvalidDurationError} If the duration is not a positive number (HTTP 400)
143
- * @throws {InvalidBase64Secret} If the secret cannot be decoded from base64 (HTTP 500)
144
- * @throws {Object} Will call next() with error object containing:
145
- * - statusCode: 400 - When iss (issuer) is missing or invalid
136
+ * @param {Request} req - The Express request object. May contain:
137
+ * - `req.body.rows`: Optional array where tokens will be added to first element
138
+ * @param {Response} res - The Express response object. Should contain:
139
+ * - `res.locals.decodedAccessToken.iss`: User ID from decoded access token (checked first), OR
140
+ * - `res.locals.id`: User ID (used if decodedAccessToken is not available)
141
+ * Tokens will be added to `res.locals.accessToken` and `res.locals.refreshToken`
142
+ * @param {NextFunction} next - Express next middleware function
143
+ *
144
+ * @returns {void}
145
+ *
146
+ * @throws Will call next() with error object containing:
147
+ * - statusCode: 400 - When issuer (iss) is missing or invalid (not a number between 1-999999999)
148
+ * - statusCode: 500 - When token signing fails (invalid secrets, duration, or base64 secret)
149
+ *
150
+ * @example
151
+ * // After successful authentication, call refresh to generate tokens
152
+ * app.post('/login', authenticate, refresh, (req, res) => {
153
+ * res.json({
154
+ * accessToken: res.locals.accessToken,
155
+ * refreshToken: res.locals.refreshToken
156
+ * });
157
+ * });
146
158
  */
147
159
  function refresh(req: Request, res: Response, next: NextFunction): void {}
148
160
 
@@ -150,15 +162,17 @@ function refresh(req: Request, res: Response, next: NextFunction): void {}
150
162
  * Express middleware function to decode and verify an access token from the Authorization header.
151
163
  *
152
164
  * This middleware extracts the JWT access token from the Authorization header, validates its format,
153
- * verifies its signature, and attaches the decoded token to req.decodedAccessToken for use by subsequent
154
- * middleware. It only processes requests that have `req.isProtected` set to true.
165
+ * verifies its signature, and attaches the decoded token to res.locals.decodedAccessToken for use by subsequent
166
+ * middleware. It only processes requests that have `res.locals.isProtected` set to true.
155
167
  *
156
168
  * @param {Request} req - The Express request object containing the Authorization header
157
- * @param {Response} _res - The Express response object (not used in this function)
169
+ * @param {Response} res - The Express response object. Should contain:
170
+ * - `res.locals.isProtected`: Boolean flag to determine if route requires JWT protection
171
+ * Decoded token will be added to `res.locals.decodedAccessToken`
158
172
  * @param {NextFunction} next - The next middleware function to be called
159
173
  *
160
- * @returns {void} Calls the next middleware function, either with an error or successfully
161
- *
174
+ * @returns {void} Calls the next middleware function with an error object if the token is invalid or iss is missing.
175
+ *
162
176
  * @throws {MissingAuthorizationError} If the Authorization header is missing (HTTP 401)
163
177
  * @throws {InvalidBearerFormatError} If the Authorization header format is invalid (HTTP 401)
164
178
  * @throws {InvalidTokenError} If the token is malformed or has invalid structure (HTTP 401)
@@ -177,12 +191,12 @@ function decodeAccess(req: Request, _res: Response, next: NextFunction): void {}
177
191
  /**
178
192
  * Middleware function to decode and verify a refresh token from the request body.
179
193
  *
180
- * @param {Request} req - The request object containing the refresh token in the body.
181
- * @param {Response} _res - The response object (not used in this function).
194
+ * @param {Request} req - The request object containing the refresh token in `req.body.refreshToken`
195
+ * @param {Response} res - The response object. Decoded token will be added to `res.locals.decodedRefreshToken`
182
196
  * @param {NextFunction} next - The next middleware function to be called.
183
197
  *
184
- * @returns {void} Calls the next middleware function with an error object if the token is invalid or required fields are missing.
185
- *
198
+ * @returns {void} Calls the next middleware function with an error object if the token is invalid or iss is missing.
199
+ *
186
200
  * @throws {InvalidTokenError} If the token is malformed or has invalid structure (HTTP 401)
187
201
  * @throws {InvalidSecretsError} If the secrets configuration is invalid (HTTP 500)
188
202
  * @throws {ExpiredTokenError} If the refresh token has expired (exp claim) (HTTP 401)
@@ -199,26 +213,51 @@ function decodeRefresh(req: Request, _res: Response, next: NextFunction): void {
199
213
 
200
214
  ### JWT Refresh
201
215
 
202
- This function will look for an ISS in the client request body :
216
+ This function will look for an ISS (user ID) from two possible sources:
203
217
 
204
218
  ```Javascript
205
- const iss = req.decodedAccessToken?.iss || (isArray(req.body.rows, null, 1) ? req.body.rows[0]?.id?.toString() : null);
219
+ let iss = res.locals?.decodedAccessToken?.iss;
220
+
221
+ if (!iss)
222
+ iss = res.locals.id ?? null;
206
223
  ```
207
224
 
208
- It will then send both new refresh and access tokens in the res.locals and req.body objects.
225
+ It will then send both new refresh and access tokens in the res.locals object and optionally in req.body.rows[0] if the rows array exists.
209
226
 
210
227
  ```Javascript
211
228
  res.locals.accessToken = accessToken;
212
229
  res.locals.refreshToken = refreshToken;
213
230
 
214
- if (isArray(req.body.rows)) {
215
- req.body.rows[0].accessToken = accessToken;
216
- req.body.rows[0].refreshToken = refreshToken;
231
+ const rbr = req.body?.rows;
232
+ if (isArray(rbr, ">=", 1) && isObject(rbr[0])) {
233
+ rbr[0].accessToken = accessToken;
234
+ rbr[0].refreshToken = refreshToken;
217
235
  }
218
236
  ```
219
237
 
220
238
  ### JWT Decoding
221
239
 
240
+ #### Route Protection with isProtected
241
+
242
+ The `decodeAccess()` middleware only processes requests when `res.locals.isProtected` is set to `true`. This allows you to selectively protect routes that require authentication.
243
+
244
+ You should set this flag in a middleware before calling `decodeAccess()`:
245
+
246
+ ```Javascript
247
+ // Example middleware to mark route as protected
248
+ function protectRoute(req, res, next) {
249
+ res.locals.isProtected = true;
250
+ next();
251
+ }
252
+
253
+ // Usage
254
+ router.get('/protected-route', protectRoute, tk.decodeAccess, yourHandler);
255
+ ```
256
+
257
+ If `res.locals.isProtected` is `false`, `undefined`, or `null`, the `decodeAccess()` middleware will simply call `next()` without processing the token, allowing the request to continue to the next middleware.
258
+
259
+ #### Access Token Decoding
260
+
222
261
  decodeAccess() functions will look for a bearer in authorization headers.
223
262
 
224
263
  ```Javascript
@@ -228,9 +267,11 @@ const bearer = req.headers.authorization;
228
267
  It will then send the decoded token in the res object.
229
268
 
230
269
  ```Javascript
231
- req.decodedAccessToken = decodedToken;
270
+ res.locals.decodedAccessToken = decodedToken;
232
271
  ```
233
272
 
273
+ #### Refresh Token Decoding
274
+
234
275
  decodeRefresh() functions will look for a token in the client request body.
235
276
 
236
277
  ```Javascript
@@ -240,7 +281,7 @@ const token = req.body.refreshToken;
240
281
  It will then send the decoded token in the res object.
241
282
 
242
283
  ```Javascript
243
- req.decodedRefreshToken = decodedToken;
284
+ res.locals.decodedRefreshToken = decodedToken;
244
285
  ```
245
286
 
246
287
 
@@ -26,20 +26,15 @@ https://github.com/DWTechs/Toker-express.js
26
26
 
27
27
  import type { Request, Response, NextFunction } from 'express';
28
28
 
29
- // Extend Express Request interface globally
30
- declare global {
31
- namespace Express {
32
- interface Request {
33
- isProtected?: boolean;
34
- decodedAccessToken?: any;
35
- decodedRefreshToken?: any;
36
- }
37
- }
29
+ export interface RowWithTokens {
30
+ accessToken?: string;
31
+ refreshToken?: string;
32
+ [key: string]: any;
38
33
  }
39
34
 
40
35
  declare function refresh(req: Request, res: Response, next: NextFunction): void;
41
- declare function decodeAccess(req: Request, _res: Response, next: NextFunction): void;
42
- 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;
43
38
 
44
39
  export {
45
40
  refresh,
@@ -25,7 +25,7 @@ https://github.com/DWTechs/Toker-express.js
25
25
  */
26
26
 
27
27
  import { sign, parseBearer, verify } from '@dwtechs/toker';
28
- import { isString, isNumber, isArray, isValidNumber, isJWT } from '@dwtechs/checkard';
28
+ import { isString, isNumber, isValidNumber, isArray, isObject, isJWT } from '@dwtechs/checkard';
29
29
  import { log } from '@dwtechs/winstan';
30
30
 
31
31
  const { TOKEN_SECRET, ACCESS_TOKEN_DURATION, REFRESH_TOKEN_DURATION } = process.env;
@@ -39,13 +39,9 @@ const accessDuration = isNumber(ACCESS_TOKEN_DURATION, false) ? Number(ACCESS_TO
39
39
  const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? Number(REFRESH_TOKEN_DURATION) : 86400;
40
40
  function refresh(req, res, next) {
41
41
  var _a, _b, _c, _d;
42
- let iss = (_a = req.decodedAccessToken) === null || _a === void 0 ? void 0 : _a.iss;
43
- const rbr = (_b = req.body) === null || _b === void 0 ? void 0 : _b.rows;
44
- let rbrIsArray = false;
45
- if (!iss) {
46
- rbrIsArray = isArray(rbr, ">=", 1);
47
- iss = rbrIsArray ? ((_d = (_c = rbr[0]) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : null) : null;
48
- }
42
+ let iss = (_b = (_a = res.locals) === null || _a === void 0 ? void 0 : _a.decodedAccessToken) === null || _b === void 0 ? void 0 : _b.iss;
43
+ if (!iss)
44
+ iss = (_c = res.locals.id) !== null && _c !== void 0 ? _c : null;
49
45
  if (!isValidNumber(iss, 1, 999999999, false))
50
46
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
51
47
  log.debug(`${LOGS_PREFIX}Create tokens for user ${iss}`);
@@ -61,15 +57,16 @@ function refresh(req, res, next) {
61
57
  log.debug(`refreshToken='${rt}', accessToken='${at}'`);
62
58
  res.locals.accessToken = at;
63
59
  res.locals.refreshToken = rt;
64
- if (rbrIsArray) {
60
+ const rbr = (_d = req.body) === null || _d === void 0 ? void 0 : _d.rows;
61
+ if (isArray(rbr, ">=", 1) && isObject(rbr[0])) {
65
62
  rbr[0].accessToken = at;
66
63
  rbr[0].refreshToken = rt;
67
64
  }
68
65
  next();
69
66
  }
70
- function decodeAccess(req, _res, next) {
67
+ function decodeAccess(req, res, next) {
71
68
  log.debug(`${LOGS_PREFIX}decode access token`);
72
- if (!req.isProtected)
69
+ if (!res.locals.isProtected)
73
70
  return next();
74
71
  let t;
75
72
  try {
@@ -91,10 +88,10 @@ function decodeAccess(req, _res, next) {
91
88
  if (!isValidNumber(dt.iss, 1, 999999999, false))
92
89
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
93
90
  log.debug(`${LOGS_PREFIX}Decoded access token : ${JSON.stringify(dt)}`);
94
- req.decodedAccessToken = dt;
91
+ res.locals.decodedAccessToken = dt;
95
92
  next();
96
93
  }
97
- function decodeRefresh(req, _res, next) {
94
+ function decodeRefresh(req, res, next) {
98
95
  var _a;
99
96
  const token = (_a = req.body) === null || _a === void 0 ? void 0 : _a.refreshToken;
100
97
  log.debug(`${LOGS_PREFIX}decodeRefresh(token=${token})`);
@@ -110,7 +107,7 @@ function decodeRefresh(req, _res, next) {
110
107
  if (!isValidNumber(dt.iss, 1, 999999999, false))
111
108
  return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
112
109
  log.debug(`${LOGS_PREFIX}Decoded refresh token : ${JSON.stringify(dt)}`);
113
- req.decodedRefreshToken = dt;
110
+ res.locals.decodedRefreshToken = dt;
114
111
  next();
115
112
  }
116
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/toker-express",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "Open source JWT management library for Express.js to refresh and decode tokens safely.",
5
5
  "keywords": [
6
6
  "JWT",