@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 +49 -0
- package/package.json +1 -1
- package/src/authenticationLogic/config/createAuthConfig.js +5 -0
- package/src/authenticationLogic/config/defaultAuthConfig.js +1 -0
- package/src/authenticationLogic/controllers/authController.js +10 -0
- package/src/authenticationLogic/createAuthenticationLibrary.js +1 -1
- package/src/authenticationLogic/routes/authRoutes.js +45 -22
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)
|
|
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.
|
|
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"
|
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
"/roles",
|
|
20
|
-
|
|
21
|
-
controller.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"/
|
|
25
|
-
|
|
26
|
-
controller.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
}
|