@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 +19 -21
- package/dist/toker-express.d.ts +2 -3
- package/dist/toker-express.js +60 -61
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
[](https://opensource.org/licenses/MIT)
|
|
3
3
|
[](https://www.npmjs.com/package/@dwtechs/toker-express)
|
|
4
4
|
[](https://www.npmjs.com/package/@dwtechs/toker-express)
|
|
5
|
+

|
|
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
|
|
128
|
-
* decoded access token or user ID
|
|
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
|
|
133
|
-
* @param {
|
|
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 {
|
|
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:
|
|
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
|
|
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 {
|
|
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.
|
|
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
|
|
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.
|
|
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
|
package/dist/toker-express.d.ts
CHANGED
|
@@ -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:
|
|
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):
|
|
42
|
+
declare function decodeRefresh(req: Request, _res: Response, next: NextFunction): void;
|
|
44
43
|
|
|
45
44
|
export {
|
|
46
45
|
refresh,
|
package/dist/toker-express.js
CHANGED
|
@@ -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
|
|
32
|
+
const LOGS_PREFIX = "Toker-express: ";
|
|
42
33
|
if (!TOKEN_SECRET)
|
|
43
|
-
throw new Error(`${
|
|
34
|
+
throw new Error(`${LOGS_PREFIX}Missing TOKEN_SECRET environment variable`);
|
|
44
35
|
if (!isString(TOKEN_SECRET, "!0"))
|
|
45
|
-
throw new Error(`${
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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(`${
|
|
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(`${
|
|
81
|
+
log.debug(`${LOGS_PREFIX}accessToken : ${t}`);
|
|
82
82
|
if (!isJWT(t))
|
|
83
|
-
return next({ statusCode: 401, message: `${
|
|
84
|
-
let
|
|
83
|
+
return next({ statusCode: 401, message: `${LOGS_PREFIX}Invalid access token` });
|
|
84
|
+
let dt = null;
|
|
85
85
|
try {
|
|
86
|
-
|
|
86
|
+
dt = verify(t, secrets, true);
|
|
87
87
|
}
|
|
88
88
|
catch (e) {
|
|
89
89
|
return next(e);
|
|
90
90
|
}
|
|
91
|
-
if (!isValidNumber(
|
|
92
|
-
return next({ statusCode: 400, message: `${
|
|
93
|
-
log.debug(`${
|
|
94
|
-
req.decodedAccessToken =
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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.
|
|
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.
|
|
40
|
+
"@dwtechs/checkard": "3.6.0",
|
|
41
41
|
"@dwtechs/toker": "0.1.1",
|
|
42
|
-
"@dwtechs/winstan": "0.
|
|
42
|
+
"@dwtechs/winstan": "0.5.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@
|
|
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.
|
|
52
|
+
"typescript": "5.9.2"
|
|
50
53
|
}
|
|
51
54
|
}
|