@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 +75 -34
- package/dist/toker-express.d.ts +6 -11
- package/dist/toker-express.js +11 -14
- package/package.json +1 -1
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
|
-
*
|
|
126
|
+
* Express middleware to generate new access and refresh JWT tokens for a user.
|
|
127
127
|
*
|
|
128
|
-
* This
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
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
|
-
* @
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* @
|
|
144
|
-
*
|
|
145
|
-
*
|
|
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
|
|
154
|
-
* middleware. It only processes requests that have `
|
|
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}
|
|
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
|
|
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
|
|
181
|
-
* @param {Response}
|
|
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
|
|
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
|
|
216
|
+
This function will look for an ISS (user ID) from two possible sources:
|
|
203
217
|
|
|
204
218
|
```Javascript
|
|
205
|
-
|
|
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
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
284
|
+
res.locals.decodedRefreshToken = decodedToken;
|
|
244
285
|
```
|
|
245
286
|
|
|
246
287
|
|
package/dist/toker-express.d.ts
CHANGED
|
@@ -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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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,
|
|
42
|
-
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;
|
|
43
38
|
|
|
44
39
|
export {
|
|
45
40
|
refresh,
|
package/dist/toker-express.js
CHANGED
|
@@ -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,
|
|
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 =
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
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,
|
|
67
|
+
function decodeAccess(req, res, next) {
|
|
71
68
|
log.debug(`${LOGS_PREFIX}decode access token`);
|
|
72
|
-
if (!
|
|
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
|
-
|
|
91
|
+
res.locals.decodedAccessToken = dt;
|
|
95
92
|
next();
|
|
96
93
|
}
|
|
97
|
-
function decodeRefresh(req,
|
|
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
|
-
|
|
110
|
+
res.locals.decodedRefreshToken = dt;
|
|
114
111
|
next();
|
|
115
112
|
}
|
|
116
113
|
|