@devmoods/express-extras 0.43.0 → 0.44.1
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/dist/auth.d.ts +3 -1
- package/dist/auth.js +31 -11
- package/package.json +5 -5
package/dist/auth.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export interface AuthOptions<UserModel extends AnyUser> {
|
|
|
44
44
|
providers: AuthProvider<UserModel>[];
|
|
45
45
|
redis: RedisClient;
|
|
46
46
|
filterUser(user: UserModel): Partial<UserModel>;
|
|
47
|
+
loginUrl?: string;
|
|
47
48
|
}
|
|
48
49
|
export interface CreateAuth<UserModel extends AnyUser> {
|
|
49
50
|
createRouter: () => Router;
|
|
@@ -54,7 +55,7 @@ export interface CreateAuth<UserModel extends AnyUser> {
|
|
|
54
55
|
export type AuthMiddlewareOptions<UserModel extends AnyUser> = false | {
|
|
55
56
|
isAuthorized?: (user: UserModel) => Promise<boolean> | boolean;
|
|
56
57
|
};
|
|
57
|
-
export declare function createAuth<UserModel extends AnyUser>({ getUserByToken, filterUser, redis, providers, }: AuthOptions<UserModel>): CreateAuth<UserModel>;
|
|
58
|
+
export declare function createAuth<UserModel extends AnyUser>({ getUserByToken, filterUser, redis, providers, loginUrl, }: AuthOptions<UserModel>): CreateAuth<UserModel>;
|
|
58
59
|
/**
|
|
59
60
|
* Extract a Bearer token from the HTTP Authorization header
|
|
60
61
|
* of `req`.
|
|
@@ -66,6 +67,7 @@ export declare function parseAuthorizationHeader(req: Request): string | null;
|
|
|
66
67
|
* Checks multiple places and uses the first match. Priority:
|
|
67
68
|
* 1. Request header `Authorization: `
|
|
68
69
|
* 2. Cookie named `access_token`
|
|
70
|
+
*
|
|
69
71
|
*/
|
|
70
72
|
export declare function extractAccessTokenFromRequest(req: Request): string | null;
|
|
71
73
|
/**
|
package/dist/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import jwt from 'jsonwebtoken';
|
|
|
5
5
|
import { v4 } from 'uuid';
|
|
6
6
|
import { config } from './config.js';
|
|
7
7
|
import { setContext, useContext } from './context.js';
|
|
8
|
-
import {
|
|
8
|
+
import { BadRequest, Forbidden, NotFound, Unauthorized } from './errors.js';
|
|
9
9
|
import { getLogger } from './logger.js';
|
|
10
10
|
import { route } from './middleware.js';
|
|
11
11
|
import {} from './redis.js';
|
|
@@ -40,7 +40,7 @@ export function decodeAuthToken(token, secret = AUTH_SECRET) {
|
|
|
40
40
|
return { ...decoded };
|
|
41
41
|
}
|
|
42
42
|
catch (error) {
|
|
43
|
-
throw new
|
|
43
|
+
throw new InvalidToken(`Failed to decode auth token: ${error.message}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
export function EmailAndPasswordAuthProvider(options) {
|
|
@@ -140,7 +140,7 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
140
140
|
},
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
export function createAuth({ getUserByToken, filterUser, redis, providers, }) {
|
|
143
|
+
export function createAuth({ getUserByToken, filterUser, redis, providers, loginUrl = '/', }) {
|
|
144
144
|
const useCurrentUser = () => useContext('currentUser');
|
|
145
145
|
const setCurrentUser = (user) => setContext('currentUser', user);
|
|
146
146
|
const login = async (res, user) => {
|
|
@@ -170,7 +170,7 @@ export function createAuth({ getUserByToken, filterUser, redis, providers, }) {
|
|
|
170
170
|
await redis.del(token);
|
|
171
171
|
}
|
|
172
172
|
setAccessTokenCookie(res, null);
|
|
173
|
-
res.redirect(
|
|
173
|
+
res.redirect(loginUrl);
|
|
174
174
|
}));
|
|
175
175
|
/**
|
|
176
176
|
* Check that a reset token is valid.
|
|
@@ -230,21 +230,34 @@ export function createAuth({ getUserByToken, filterUser, redis, providers, }) {
|
|
|
230
230
|
throw new InvalidToken('token expired');
|
|
231
231
|
}
|
|
232
232
|
const decoded = decodeAuthToken(token);
|
|
233
|
-
|
|
233
|
+
let user;
|
|
234
|
+
try {
|
|
235
|
+
user = await getUserByToken(decoded);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
throw new InvalidToken('User not found');
|
|
239
|
+
}
|
|
234
240
|
if (options.isAuthorized && !(await options.isAuthorized(user))) {
|
|
235
|
-
throw
|
|
241
|
+
throw new PermissionDenied('Permission denied');
|
|
236
242
|
}
|
|
237
243
|
setCurrentUser(user);
|
|
238
244
|
next();
|
|
239
245
|
}
|
|
240
246
|
catch (error) {
|
|
241
247
|
logger.error(error);
|
|
242
|
-
if (error instanceof
|
|
243
|
-
|
|
248
|
+
if (error instanceof PermissionDenied) {
|
|
249
|
+
if (req.accepts('html')) {
|
|
250
|
+
return res.redirect(loginUrl);
|
|
251
|
+
}
|
|
252
|
+
return next(Forbidden('Permission denied'));
|
|
244
253
|
}
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
if (error instanceof InvalidToken) {
|
|
255
|
+
if (req.accepts('html')) {
|
|
256
|
+
return res.redirect(loginUrl);
|
|
257
|
+
}
|
|
258
|
+
return next(Unauthorized('Invalid token'));
|
|
247
259
|
}
|
|
260
|
+
next(error);
|
|
248
261
|
}
|
|
249
262
|
};
|
|
250
263
|
}
|
|
@@ -284,7 +297,7 @@ export function parseAuthorizationHeader(req) {
|
|
|
284
297
|
.split(' ')
|
|
285
298
|
.map((s) => s.trim());
|
|
286
299
|
if (token && type !== 'Bearer') {
|
|
287
|
-
throw new
|
|
300
|
+
throw new InvalidToken('Only Bearer type supported in jwt()');
|
|
288
301
|
}
|
|
289
302
|
// No token given, this is not necessarilly an error case
|
|
290
303
|
if (!token) {
|
|
@@ -302,6 +315,7 @@ export function parseAuthorizationHeader(req) {
|
|
|
302
315
|
* Checks multiple places and uses the first match. Priority:
|
|
303
316
|
* 1. Request header `Authorization: `
|
|
304
317
|
* 2. Cookie named `access_token`
|
|
318
|
+
*
|
|
305
319
|
*/
|
|
306
320
|
export function extractAccessTokenFromRequest(req) {
|
|
307
321
|
const authorizationHeader = parseAuthorizationHeader(req);
|
|
@@ -340,6 +354,12 @@ class InvalidToken extends Error {
|
|
|
340
354
|
this.name = 'Invalid Token';
|
|
341
355
|
}
|
|
342
356
|
}
|
|
357
|
+
class PermissionDenied extends Error {
|
|
358
|
+
constructor(message) {
|
|
359
|
+
super(message);
|
|
360
|
+
this.name = 'Permission Denied';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
343
363
|
/**
|
|
344
364
|
* Compare two password hashes using bcrypt.
|
|
345
365
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.1",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@devmoods/eslint-config": "^2.
|
|
21
|
+
"@devmoods/eslint-config": "^2.5.0",
|
|
22
22
|
"@types/koa-compose": "^3.2.8",
|
|
23
23
|
"@types/node": "22.10.1",
|
|
24
24
|
"@types/supertest": "^6.0.2",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@devmoods/fetch": "^4.0.1",
|
|
39
|
-
"@sentry/node": "^8.
|
|
40
|
-
"@sentry/profiling-node": "^8.
|
|
39
|
+
"@sentry/node": "^8.43.0",
|
|
40
|
+
"@sentry/profiling-node": "^8.43.0",
|
|
41
41
|
"@types/bcrypt": "^5.0.2",
|
|
42
42
|
"@types/body-parser": "^1.19.5",
|
|
43
43
|
"@types/cookie-parser": "^1.4.8",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"compose-middleware": "^5.0.1",
|
|
56
56
|
"cookie-parser": "^1.4.7",
|
|
57
57
|
"dotenv": "^16.4.7",
|
|
58
|
-
"express": "^4.21.
|
|
58
|
+
"express": "^4.21.2",
|
|
59
59
|
"helmet": "^8.0.0",
|
|
60
60
|
"jsonwebtoken": "^9.0.2",
|
|
61
61
|
"nodemailer": "^6.9.16",
|