@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 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 { ApiError, BadRequest, Forbidden, NotFound, Unauthorized, } from './errors.js';
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 Error(`Failed to decode auth token: ${error.message}`);
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
- const user = await getUserByToken(decoded);
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 Forbidden('Permission denied');
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 ApiError) {
243
- next(error);
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
- else {
246
- next(Unauthorized('Invalid token'));
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 Error('Only Bearer type supported in jwt()');
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.43.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.1.0",
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.42.0",
40
- "@sentry/profiling-node": "^8.42.0",
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.1",
58
+ "express": "^4.21.2",
59
59
  "helmet": "^8.0.0",
60
60
  "jsonwebtoken": "^9.0.2",
61
61
  "nodemailer": "^6.9.16",