@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 +12 -13
- package/dist/toker-express.js +64 -42
- package/package.json +18 -13
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:
|
|
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
|
|
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`
|
|
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
|
|
package/dist/toker-express.js
CHANGED
|
@@ -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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
92
|
+
(_a = res.locals).tokens ?? (_a.tokens = {});
|
|
93
|
+
res.locals.tokens.access = parseBearer$1(req.headers.authorization);
|
|
87
94
|
}
|
|
88
95
|
catch (e) {
|
|
89
|
-
|
|
96
|
+
next(e);
|
|
97
|
+
return;
|
|
90
98
|
}
|
|
91
99
|
next();
|
|
92
100
|
}
|
|
93
101
|
function decodeAccess(_req, res, next) {
|
|
94
|
-
var _a
|
|
95
|
-
log.debug(`${LOGS_PREFIX}decode access token`);
|
|
96
|
-
if (!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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 =
|
|
118
|
-
log.debug(`${LOGS_PREFIX}decodeRefresh(token=${t})`);
|
|
119
|
-
if (!isJWT(t))
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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.
|
|
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.
|
|
42
|
-
"@dwtechs/winstan": "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.
|
|
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": "
|
|
57
|
+
"typescript": "6.0.3"
|
|
53
58
|
}
|
|
54
59
|
}
|