@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 +95 -25
- package/dist/toker-express.d.ts +2 -12
- package/dist/toker-express.js +19 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ $ npm i @dwtechs/toker-express
|
|
|
47
47
|
```javascript
|
|
48
48
|
|
|
49
49
|
// @ts-check
|
|
50
|
-
import
|
|
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
|
-
|
|
70
|
-
|
|
69
|
+
parseBearerToken,
|
|
70
|
+
decodeAccess,
|
|
71
|
+
decodeRefresh,
|
|
71
72
|
checkToken,
|
|
72
|
-
|
|
73
|
+
refresh,
|
|
73
74
|
cEntity.update,
|
|
74
75
|
];
|
|
75
76
|
|
|
76
77
|
const del = [
|
|
77
78
|
checkToken,
|
|
78
|
-
|
|
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 `
|
|
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
|
|
164
|
+
* Express middleware function to parse the bearer token from the Authorization header.
|
|
163
165
|
*
|
|
164
|
-
* This middleware extracts the JWT
|
|
165
|
-
*
|
|
166
|
-
*
|
|
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}
|
|
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
|
|
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,
|
|
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
|
|
193
|
-
* @param {Response}
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
329
|
+
res.locals.decodedRefreshToken = decodedToken;
|
|
260
330
|
```
|
|
261
331
|
|
|
262
332
|
|
package/dist/toker-express.d.ts
CHANGED
|
@@ -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,
|
|
47
|
-
declare function decodeRefresh(req: Request,
|
|
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,
|
package/dist/toker-express.js
CHANGED
|
@@ -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 =
|
|
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 = (
|
|
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 = (
|
|
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
|
|
68
|
-
|
|
69
|
-
if (!req.isProtected)
|
|
67
|
+
function parseBearerToken(req, res, next) {
|
|
68
|
+
if (!res.locals.isProtected)
|
|
70
69
|
return next();
|
|
71
|
-
|
|
70
|
+
log.debug(`${LOGS_PREFIX}parse bearer token`);
|
|
72
71
|
try {
|
|
73
|
-
|
|
72
|
+
res.locals.accessToken = parseBearer(req.headers.authorization);
|
|
74
73
|
}
|
|
75
74
|
catch (e) {
|
|
76
75
|
return next(e);
|
|
77
76
|
}
|
|
78
|
-
|
|
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
|
-
|
|
96
|
+
res.locals.decodedAccessToken = dt;
|
|
92
97
|
next();
|
|
93
98
|
}
|
|
94
|
-
function decodeRefresh(req,
|
|
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
|
-
|
|
115
|
+
res.locals.decodedRefreshToken = dt;
|
|
111
116
|
next();
|
|
112
117
|
}
|
|
113
118
|
|
|
114
|
-
export { decodeAccess, decodeRefresh, refresh };
|
|
119
|
+
export { decodeAccess, decodeRefresh, parseBearerToken, refresh };
|