@dwtechs/toker-express 0.1.1 → 0.2.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
@@ -2,6 +2,7 @@
2
2
  [![License: MIT](https://img.shields.io/npm/l/@dwtechs/toker-express.svg?color=brightgreen)](https://opensource.org/licenses/MIT)
3
3
  [![npm version](https://badge.fury.io/js/%40dwtechs%2Ftoker-express.svg)](https://www.npmjs.com/package/@dwtechs/toker-express)
4
4
  [![last version release date](https://img.shields.io/github/release-date/DWTechs/Toker-express.js)](https://www.npmjs.com/package/@dwtechs/toker-express)
5
+ ![Jest:coverage](https://img.shields.io/badge/Jest:coverage-95%25-brightgreen.svg)
5
6
 
6
7
 
7
8
  - [Synopsis](#synopsis)
@@ -124,16 +125,16 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
124
125
  /**
125
126
  * Refreshes the JWT tokens for a user.
126
127
  *
127
- * This function generates new access and refresh tokens for a user based on the provided
128
- * decoded access token or user ID in the request body. It validates the issuer (iss) and
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
129
130
  * creates new tokens if the validation is successful. The new tokens are then added to the
130
- * response object.
131
+ * response locals object and optionally to req.body.rows[0] if rows is an array with at least one element.
131
132
  *
132
- * @param {Request} req - The request object containing the decoded access token or user ID.
133
- * @param {MyResponse} res - The response object where the new tokens will be added.
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.
134
135
  * @param {NextFunction} next - The next middleware function in the Express.js request-response cycle.
135
136
  *
136
- * @returns {Promise<void>} Calls the next middleware function with an error if the issuer is invalid,
137
+ * @returns {void} Calls the next middleware function with an error if the issuer is invalid,
137
138
  * otherwise proceeds to the next middleware function.
138
139
  *
139
140
  * @throws {InvalidIssuerError} If the issuer (iss) is not a string or number (HTTP 400)
@@ -142,15 +143,14 @@ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_
142
143
  * @throws {InvalidBase64Secret} If the secret cannot be decoded from base64 (HTTP 500)
143
144
  * @throws {Object} Will call next() with error object containing:
144
145
  * - statusCode: 400 - When iss (issuer) is missing or invalid
145
- * - statusCode: 400 - When iss is not a valid number between 1 and 999999999
146
146
  */
147
- function refresh(req: Request, res: MyResponse, next: NextFunction): void {}
147
+ function refresh(req: Request, res: Response, next: NextFunction): void {}
148
148
 
149
149
  /**
150
150
  * Express middleware function to decode and verify an access token from the Authorization header.
151
151
  *
152
152
  * This middleware extracts the JWT access token from the Authorization header, validates its format,
153
- * verifies its signature, and attaches the decoded token to the request object for use by subsequent
153
+ * verifies its signature, and attaches the decoded token to req.decodedAccessToken for use by subsequent
154
154
  * middleware. It only processes requests that have `req.isProtected` set to true.
155
155
  *
156
156
  * @param {Request} req - The Express request object containing the Authorization header
@@ -171,14 +171,6 @@ function refresh(req: Request, res: MyResponse, next: NextFunction): void {}
171
171
  * - statusCode: 401 - When token is not a valid JWT format
172
172
  * - statusCode: 400 - When decoded token is missing required 'iss' claim
173
173
  *
174
- * @example
175
- * ```typescript
176
- * // Usage in Express route with protection middleware
177
- * const protect = (req: Request, res: Response, next: NextFunction) => {
178
- * req.isProtected = true;
179
- * next();
180
- * };
181
- *
182
174
  */
183
175
  function decodeAccess(req: Request, _res: Response, next: NextFunction): void {}
184
176
 
@@ -189,7 +181,7 @@ function decodeAccess(req: Request, _res: Response, next: NextFunction): void {}
189
181
  * @param {Response} _res - The response object (not used in this function).
190
182
  * @param {NextFunction} next - The next middleware function to be called.
191
183
  *
192
- * @returns {Promise<void>} Calls the next middleware function with an error object if the token is invalid or missing required fields.
184
+ * @returns {void} Calls the next middleware function with an error object if the token is invalid or required fields are missing.
193
185
  *
194
186
  * @throws {InvalidTokenError} If the token is malformed or has invalid structure (HTTP 401)
195
187
  * @throws {InvalidSecretsError} If the secrets configuration is invalid (HTTP 500)
@@ -210,13 +202,19 @@ function decodeRefresh(req: Request, _res: Response, next: NextFunction): void {
210
202
  This function will look for an ISS in the client request body :
211
203
 
212
204
  ```Javascript
213
- const iss = req.body.decodedAccessToken?.iss || req.body?.id?.toString();
205
+ const iss = req.decodedAccessToken?.iss || (isArray(req.body.rows, null, 1) ? req.body.rows[0]?.id?.toString() : null);
214
206
  ```
215
207
 
216
- It will then send both new refresh and access tokens in the res object.
208
+ It will then send both new refresh and access tokens in the res.locals and req.body objects.
217
209
 
218
210
  ```Javascript
219
- res.rows = [{ accessToken, refreshToken }];
211
+ res.locals.accessToken = accessToken;
212
+ res.locals.refreshToken = refreshToken;
213
+
214
+ if (isArray(req.body.rows)) {
215
+ req.body.rows[0].accessToken = accessToken;
216
+ req.body.rows[0].refreshToken = refreshToken;
217
+ }
220
218
  ```
221
219
 
222
220
  ### JWT Decoding
@@ -25,7 +25,6 @@ https://github.com/DWTechs/Toker-express.js
25
25
  */
26
26
 
27
27
  import type { Request, Response, NextFunction } from 'express';
28
- import type { MyResponse } from './interfaces';
29
28
 
30
29
  // Extend Express Request interface globally
31
30
  declare global {
@@ -38,9 +37,9 @@ declare global {
38
37
  }
39
38
  }
40
39
 
41
- declare function refresh(req: Request, res: MyResponse, next: NextFunction): Promise<void>;
40
+ declare function refresh(req: Request, res: Response, next: NextFunction): void;
42
41
  declare function decodeAccess(req: Request, _res: Response, next: NextFunction): void;
43
- declare function decodeRefresh(req: Request, _res: Response, next: NextFunction): Promise<void>;
42
+ declare function decodeRefresh(req: Request, _res: Response, next: NextFunction): void;
44
43
 
45
44
  export {
46
45
  refresh,
@@ -25,50 +25,50 @@ https://github.com/DWTechs/Toker-express.js
25
25
  */
26
26
 
27
27
  import { sign, parseBearer, verify } from '@dwtechs/toker';
28
- import { isString, isNumber, isValidNumber, isJWT } from '@dwtechs/checkard';
28
+ import { isString, isNumber, isArray, isValidNumber, isJWT } from '@dwtechs/checkard';
29
29
  import { log } from '@dwtechs/winstan';
30
30
 
31
- var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
32
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
33
- return new (P || (P = Promise))(function (resolve, reject) {
34
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
35
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
36
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
37
- step((generator = generator.apply(thisArg, _arguments || [])).next());
38
- });
39
- };
40
31
  const { TOKEN_SECRET, ACCESS_TOKEN_DURATION, REFRESH_TOKEN_DURATION } = process.env;
41
- const TE_PREFIX = "Toker-express: ";
32
+ const LOGS_PREFIX = "Toker-express: ";
42
33
  if (!TOKEN_SECRET)
43
- throw new Error(`${TE_PREFIX}Missing TOKEN_SECRET environment variable`);
34
+ throw new Error(`${LOGS_PREFIX}Missing TOKEN_SECRET environment variable`);
44
35
  if (!isString(TOKEN_SECRET, "!0"))
45
- throw new Error(`${TE_PREFIX}Invalid TOKEN_SECRET environment variable`);
36
+ throw new Error(`${LOGS_PREFIX}Invalid TOKEN_SECRET environment variable`);
46
37
  const secrets = [TOKEN_SECRET];
47
- const accessDuration = isNumber(ACCESS_TOKEN_DURATION, false) ? ACCESS_TOKEN_DURATION : 600;
48
- const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? REFRESH_TOKEN_DURATION : 86400;
38
+ const accessDuration = isNumber(ACCESS_TOKEN_DURATION, false) ? Number(ACCESS_TOKEN_DURATION) : 600;
39
+ const refreshDuration = isNumber(REFRESH_TOKEN_DURATION, false) ? Number(REFRESH_TOKEN_DURATION) : 86400;
49
40
  function refresh(req, res, next) {
50
- return __awaiter(this, void 0, void 0, function* () {
51
- var _a, _b, _c;
52
- const iss = ((_a = req.decodedAccessToken) === null || _a === void 0 ? void 0 : _a.iss) || ((_c = (_b = req.body) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.toString());
53
- if (!isValidNumber(iss, 1, 999999999, false))
54
- return next({ statusCode: 400, message: `${TE_PREFIX}Missing iss` });
55
- log.debug(`${TE_PREFIX}Create tokens for user ${iss}`);
56
- let accessToken;
57
- let refreshToken;
58
- try {
59
- accessToken = sign(iss, accessDuration, "access", secrets);
60
- refreshToken = sign(iss, refreshDuration, "refresh", secrets);
61
- }
62
- catch (err) {
63
- return next(err);
64
- }
65
- log.debug(`refreshToken='${refreshToken}', accessToken='${accessToken}'`);
66
- res.rows = [{ accessToken, refreshToken }];
67
- next();
68
- });
41
+ var _a, _b, _c;
42
+ let iss = (_a = req.decodedAccessToken) === null || _a === void 0 ? void 0 : _a.iss;
43
+ const rbr = req.body.rows;
44
+ let rbrIsArray = false;
45
+ if (!iss) {
46
+ rbrIsArray = isArray(rbr, null, 1);
47
+ iss = rbrIsArray ? (_c = (_b = rbr[0]) === null || _b === void 0 ? void 0 : _b.id) === null || _c === void 0 ? void 0 : _c.toString() : null;
48
+ }
49
+ if (!isValidNumber(iss, 1, 999999999, false))
50
+ return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
51
+ log.debug(`${LOGS_PREFIX}Create tokens for user ${iss}`);
52
+ let at;
53
+ let rt;
54
+ try {
55
+ at = sign(iss, accessDuration, "access", secrets);
56
+ rt = sign(iss, refreshDuration, "refresh", secrets);
57
+ }
58
+ catch (err) {
59
+ return next(err);
60
+ }
61
+ log.debug(`refreshToken='${rt}', accessToken='${at}'`);
62
+ res.locals.accessToken = at;
63
+ res.locals.refreshToken = rt;
64
+ if (rbrIsArray) {
65
+ rbr[0].accessToken = at;
66
+ rbr[0].refreshToken = rt;
67
+ }
68
+ next();
69
69
  }
70
70
  function decodeAccess(req, _res, next) {
71
- log.debug(`${TE_PREFIX}decode access token`);
71
+ log.debug(`${LOGS_PREFIX}decode access token`);
72
72
  if (!req.isProtected)
73
73
  return next();
74
74
  let t;
@@ -78,41 +78,40 @@ function decodeAccess(req, _res, next) {
78
78
  catch (e) {
79
79
  return next(e);
80
80
  }
81
- log.debug(`${TE_PREFIX}accessToken : ${t}`);
81
+ log.debug(`${LOGS_PREFIX}accessToken : ${t}`);
82
82
  if (!isJWT(t))
83
- return next({ statusCode: 401, message: `${TE_PREFIX}Invalid access token` });
84
- let decodedToken = null;
83
+ return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid access token` });
84
+ let dt = null;
85
85
  try {
86
- decodedToken = verify(t, secrets, true);
86
+ dt = verify(t, secrets, true);
87
87
  }
88
88
  catch (e) {
89
89
  return next(e);
90
90
  }
91
- if (!isValidNumber(decodedToken.iss, 1, 999999999, false))
92
- return next({ statusCode: 400, message: `${TE_PREFIX}Missing iss` });
93
- log.debug(`${TE_PREFIX}Decoded access token : ${JSON.stringify(decodedToken)}`);
94
- req.decodedAccessToken = decodedToken;
91
+ if (!isValidNumber(dt.iss, 1, 999999999, false))
92
+ return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
93
+ log.debug(`${LOGS_PREFIX}Decoded access token : ${JSON.stringify(dt)}`);
94
+ req.decodedAccessToken = dt;
95
95
  next();
96
96
  }
97
97
  function decodeRefresh(req, _res, next) {
98
- return __awaiter(this, void 0, void 0, function* () {
99
- const token = req.body.refreshToken;
100
- log.debug(`${TE_PREFIX}decodeRefresh(token=${token})`);
101
- if (!isJWT(token))
102
- return next({ statusCode: 401, message: `${TE_PREFIX}Invalid refresh token` });
103
- let decodedToken = null;
104
- try {
105
- decodedToken = verify(token, secrets, false);
106
- }
107
- catch (e) {
108
- return next(e);
109
- }
110
- if (!isValidNumber(decodedToken.iss, 1, 999999999, false))
111
- return next({ statusCode: 400, message: `${TE_PREFIX}Missing iss` });
112
- log.debug(`${TE_PREFIX}Decoded refresh token : ${JSON.stringify(req.decodedRefreshToken)}`);
113
- req.decodedRefreshToken = decodedToken;
114
- next();
115
- });
98
+ var _a;
99
+ const token = (_a = req.body) === null || _a === void 0 ? void 0 : _a.refreshToken;
100
+ log.debug(`${LOGS_PREFIX}decodeRefresh(token=${token})`);
101
+ if (!isJWT(token))
102
+ return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid refresh token` });
103
+ let dt = null;
104
+ try {
105
+ dt = verify(token, secrets, false);
106
+ }
107
+ catch (e) {
108
+ return next(e);
109
+ }
110
+ if (!isValidNumber(dt.iss, 1, 999999999, false))
111
+ return next({ statusCode: 400, message: `${LOGS_PREFIX}Missing iss` });
112
+ log.debug(`${LOGS_PREFIX}Decoded refresh token : ${JSON.stringify(dt)}`);
113
+ req.decodedRefreshToken = dt;
114
+ next();
116
115
  }
117
116
 
118
117
  export { decodeAccess, decodeRefresh, refresh };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dwtechs/toker-express",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Open source JWT management library for Express.js to refresh and decode tokens safely.",
5
5
  "keywords": [
6
6
  "JWT",
@@ -31,21 +31,24 @@
31
31
  "rollup:mjs": "rollup --config rollup.config.mjs",
32
32
  "rollup:cjs": "rollup --config rollup.config.cjs.mjs",
33
33
  "rollup": "npm run rollup:mjs",
34
- "test": ""
34
+ "test": "jest --coverage"
35
35
  },
36
36
  "files": [
37
37
  "dist/"
38
38
  ],
39
39
  "dependencies": {
40
- "@dwtechs/checkard": "3.5.1",
40
+ "@dwtechs/checkard": "3.6.0",
41
41
  "@dwtechs/toker": "0.1.1",
42
- "@dwtechs/winstan": "0.4.0"
42
+ "@dwtechs/winstan": "0.5.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/express": "5.0.0",
45
+ "@babel/preset-env": "7.26.0",
46
46
  "@rollup/plugin-node-resolve": "15.3.0",
47
+ "@types/express": "5.0.3",
48
+ "babel-jest": "29.7.0",
47
49
  "core-js": "3.38.1",
50
+ "jest": "29.7.0",
48
51
  "rollup": "4.24.0",
49
- "typescript": "5.6.3"
52
+ "typescript": "5.9.2"
50
53
  }
51
54
  }