@nabhiraj/nab-auth 1.0.0 → 1.1.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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # nab-auth
2
2
 
3
+ <img src="./nab-auth-logo.png" alt="nab-auth logo" width="180">
4
+
3
5
  [![license](https://img.shields.io/npm/l/@nabhiraj/nab-auth.svg)](LICENSE)
4
6
 
5
7
  `@nabhiraj/nab-auth` is a configurable Express authentication library. It
@@ -292,6 +294,21 @@ to `true` for HTTPS production deployments. `defaultAdmin.enabled` defaults to
292
294
 
293
295
  ### Registration and authorization policy
294
296
 
297
+ You can provide an optional password validator for registration:
298
+
299
+ ```js
300
+ registration: {
301
+ validatePassword: (password) => password.length >= 12
302
+ }
303
+ ```
304
+
305
+ The function receives the plaintext password and must return `true` or `false`;
306
+ async functions returning a boolean are also supported. A rejected password
307
+ receives HTTP `400` and is not stored. If omitted, the existing non-empty
308
+ password requirement applies. This setting applies to registration only, not
309
+ password changes or default-admin creation. Validator errors are passed to
310
+ Express error handling; the function must not log or persist passwords.
311
+
295
312
  | Setting | Effect |
296
313
  | --- | --- |
297
314
  | `registration.verificationRequired` | Requires a valid registration code on `POST /register`. |
@@ -378,6 +395,38 @@ When `jwt.keyProvider` is present, `jwt.keys` is not used.
378
395
 
379
396
  ## HTTP API
380
397
 
398
+ ### Per-route middleware
399
+
400
+ Pass ordered arrays of Express middleware in `createAuthRouter` options:
401
+
402
+ ```js
403
+ const router = auth.createAuthRouter(createTokenPayload, {
404
+ middleware: {
405
+ token: { beforeAuth: [loginLimiter, loginAudit] },
406
+ password: {
407
+ beforeAuth: [passwordIpLimiter],
408
+ afterAuth: [passwordUserLimiter]
409
+ }
410
+ }
411
+ });
412
+ app.use("/auth", router);
413
+ ```
414
+
415
+ Supported keys are `potentialUsers`, `register`, `token`, `refresh`,
416
+ `twoFactorCode`, `password`, `addRoles`, `removeRole`, and `replaceRoles`.
417
+ Each omitted array defaults to empty. Middleware runs in array order:
418
+ `beforeAuth`, built-in JWT and role checks, `afterAuth`, then the controller.
419
+ `afterAuth` is only permitted for routes with built-in authentication:
420
+ password and role routes, plus registration when
421
+ `userRegistrationRequiresNabAdmin` is enabled. The authenticated claims are
422
+ available as `req.auth` after those checks. Middleware may end the response or
423
+ call `next(error)` in the usual Express way.
424
+
425
+ The application supplies and configures limiters, including any shared store
426
+ needed across processes. Apply middleware to application-owned routes directly
427
+ with Express. Configure Express proxy trust to match your deployment before
428
+ using client IP addresses for rate limiting.
429
+
381
430
  Mount the router at `/auth` to use these routes:
382
431
 
383
432
  | Method and route | Request body | Success | Notes |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nabhiraj/nab-auth",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Configurable Express authentication library with pluggable database adapters, JWT, registration verification, and 2FA",
5
5
  "type": "module",
6
6
  "main": "./src/authenticationLogic/index.js",
@@ -15,6 +15,11 @@ export function createAuthConfig(overrides = {}) {
15
15
  validateCodeConfig(config.twoFactor, "twoFactor", "required");
16
16
  validateString(config.authorization.adminRole, "authorization.adminRole");
17
17
 
18
+ if (config.registration.validatePassword !== undefined &&
19
+ typeof config.registration.validatePassword !== "function") {
20
+ throw new TypeError("registration.validatePassword must be a function");
21
+ }
22
+
18
23
  if (
19
24
  config.database.adapter !== undefined &&
20
25
  typeof config.database.adapter !== "function"
@@ -24,6 +24,7 @@ export const defaultAuthConfig = {
24
24
  },
25
25
  registration: {
26
26
  verificationRequired: false,
27
+ validatePassword: undefined,
27
28
  codeDigits: 6,
28
29
  codeTtlMs: 10 * 60 * 1000
29
30
  },
@@ -143,6 +143,16 @@ export function createAuthController({
143
143
  return res.status(400).json({ message: "email and password are required" });
144
144
  }
145
145
 
146
+ if (config.registration.validatePassword) {
147
+ const valid = await config.registration.validatePassword(password);
148
+ if (typeof valid !== "boolean") {
149
+ throw new TypeError("registration.validatePassword must return a boolean");
150
+ }
151
+ if (!valid) {
152
+ return res.status(400).json({ message: "Password does not meet requirements" });
153
+ }
154
+ }
155
+
146
156
  const normalizedEmail = normalizeEmail(email);
147
157
  if (await repository.findUserByEmail(normalizedEmail)) {
148
158
  return res.status(409).json({ message: "User already exists" });
@@ -63,7 +63,7 @@ export function createAuthenticationLibrary(configOverrides = {}) {
63
63
  options
64
64
  });
65
65
 
66
- return createAuthRouter({ controller, middleware, config });
66
+ return createAuthRouter({ controller, middleware, config, routeMiddleware: options.middleware });
67
67
  }
68
68
  };
69
69
  }
@@ -1,7 +1,8 @@
1
1
  import { Router } from "express";
2
2
 
3
- export function createAuthRouter({ controller, middleware, config }) {
3
+ export function createAuthRouter({ controller, middleware, config, routeMiddleware = {} }) {
4
4
  const authRouter = Router();
5
+ console.log('route middleware is ',routeMiddleware);
5
6
 
6
7
  const registrationMiddleware = config.authorization.userRegistrationRequiresNabAdmin
7
8
  ? [middleware.requireJwt, middleware.requireNabAdmin]
@@ -14,27 +15,49 @@ export function createAuthRouter({ controller, middleware, config }) {
14
15
  middleware.requireNabAdmin
15
16
  ];
16
17
 
17
- authRouter.post("/roles", ...roleManagementMiddleware, controller.addRoles);
18
- authRouter.delete(
19
- "/roles",
20
- ...roleManagementMiddleware,
21
- controller.removeRole
22
- );
23
- authRouter.put(
24
- "/roles",
25
- ...roleManagementMiddleware,
26
- controller.replaceRoles
27
- );
28
- authRouter.patch(
29
- "/password",
30
- ...passwordChangeMiddleware,
31
- controller.changePassword
32
- );
33
- authRouter.post("/potential-users", controller.createPotentialUser);
34
- authRouter.post("/2fa/code", controller.generateTwoFactorCode);
35
- authRouter.post("/register", ...registrationMiddleware, controller.registerUser);
36
- authRouter.post("/token", controller.issueToken);
37
- authRouter.post("/refresh", controller.refreshToken);
18
+ const routes = {
19
+ addRoles: ["post", "/roles", roleManagementMiddleware, controller.addRoles],
20
+ removeRole: ["delete", "/roles", roleManagementMiddleware, controller.removeRole],
21
+ replaceRoles: ["put", "/roles", roleManagementMiddleware, controller.replaceRoles],
22
+ password: ["patch", "/password", passwordChangeMiddleware, controller.changePassword],
23
+ potentialUsers: ["post", "/potential-users", [], controller.createPotentialUser],
24
+ twoFactorCode: ["post", "/2fa/code", [], controller.generateTwoFactorCode],
25
+ register: ["post", "/register", registrationMiddleware, controller.registerUser],
26
+ token: ["post", "/token", [], controller.issueToken],
27
+ refresh: ["post", "/refresh", [], controller.refreshToken]
28
+ };
29
+
30
+ if (!routeMiddleware || typeof routeMiddleware !== "object" || Array.isArray(routeMiddleware)) {
31
+ throw new TypeError("options.middleware must be an object");
32
+ }
33
+ for (const name of Object.keys(routeMiddleware)) {
34
+ if (!Object.hasOwn(routes, name)) {
35
+ throw new TypeError(`Unknown middleware route: ${name}`);
36
+ }
37
+ }
38
+
39
+ for (const [name, [method, path, auth, handler]] of Object.entries(routes)) {
40
+ const settings = routeMiddleware[name] ?? {};
41
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) {
42
+ throw new TypeError(`options.middleware.${name} must be an object`);
43
+ }
44
+ for (const key of Object.keys(settings)) {
45
+ if (key !== "beforeAuth" && key !== "afterAuth") {
46
+ throw new TypeError(`Unknown middleware position: ${name}.${key}`);
47
+ }
48
+ }
49
+ const before = settings.beforeAuth ?? [];
50
+ const after = settings.afterAuth ?? [];
51
+ for (const [position, value] of [["beforeAuth", before], ["afterAuth", after]]) {
52
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "function")) {
53
+ throw new TypeError(`options.middleware.${name}.${position} must be an array of functions`);
54
+ }
55
+ }
56
+ if (!auth.length && Object.hasOwn(settings, "afterAuth")) {
57
+ throw new TypeError(`options.middleware.${name}.afterAuth requires built-in authentication`);
58
+ }
59
+ authRouter[method](path, ...before, ...auth, ...after, handler);
60
+ }
38
61
 
39
62
  return authRouter;
40
63
  }