@lara-node/middlewares 0.1.1 → 0.1.3
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
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# @lara-node/middlewares
|
|
2
|
+
|
|
3
|
+
Predefined class-based middleware for Lara-Node applications. Every middleware is a plain class with a `handle()` method, keeping them testable and dependency-injectable.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @lara-node/middlewares
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Setup
|
|
12
|
+
|
|
13
|
+
Register the core middleware stack in your Express app:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import express from 'express';
|
|
17
|
+
import {
|
|
18
|
+
asyncContext,
|
|
19
|
+
requestLogger,
|
|
20
|
+
validatorAttach,
|
|
21
|
+
responseExtender,
|
|
22
|
+
errorHandler,
|
|
23
|
+
} from '@lara-node/middlewares';
|
|
24
|
+
|
|
25
|
+
const app = express();
|
|
26
|
+
app.use(express.json());
|
|
27
|
+
app.use(asyncContext); // async context per request
|
|
28
|
+
app.use(requestLogger); // colored request log
|
|
29
|
+
app.use(validatorAttach); // attaches req.validate()
|
|
30
|
+
app.use(responseExtender); // adds res.jsonAsync(), auto-serializes Models
|
|
31
|
+
|
|
32
|
+
// ... routes ...
|
|
33
|
+
|
|
34
|
+
app.use(errorHandler); // 4-arg error handler (must be last)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Middleware Reference
|
|
38
|
+
|
|
39
|
+
### AsyncContextMiddleware
|
|
40
|
+
|
|
41
|
+
Stores the request in `AsyncLocalStorage` so it's accessible anywhere in the call stack without prop-drilling:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { asyncLocalStorage } from '@lara-node/middlewares';
|
|
45
|
+
|
|
46
|
+
// Anywhere in the request lifecycle:
|
|
47
|
+
const store = asyncLocalStorage.getStore(); // { req, user? }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### RequestLoggerMiddleware
|
|
51
|
+
|
|
52
|
+
Logs `METHOD PATH STATUS TIME IP [user:id]` to stdout on response finish. Color-coded: green (2xx), yellow (4xx), red (5xx).
|
|
53
|
+
|
|
54
|
+
### ValidatorMiddleware
|
|
55
|
+
|
|
56
|
+
Attaches `req.validate()` to every request:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// In any controller:
|
|
60
|
+
const data = await req.validate({
|
|
61
|
+
name: 'required|string|min:2|max:100',
|
|
62
|
+
email: 'required|email',
|
|
63
|
+
age: 'required|integer|min:18',
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Throws `ValidationError` (caught by `ErrorHandlerMiddleware` → HTTP 422) on failure.
|
|
68
|
+
|
|
69
|
+
### ResponseExtenderMiddleware
|
|
70
|
+
|
|
71
|
+
Adds `res.jsonAsync()` and overrides `res.json()` to automatically call `model.toJSONAsync()` on `@lara-node/db` Model instances (handles hidden fields, casts, relations):
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// All equivalent — Model is auto-serialized:
|
|
75
|
+
res.json(user);
|
|
76
|
+
res.json([user1, user2]);
|
|
77
|
+
res.json(await User.paginate(15, 1)); // QueryResult
|
|
78
|
+
await res.jsonAsync(user);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### AuthMiddleware
|
|
82
|
+
|
|
83
|
+
JWT authentication. Verifies `Authorization: Bearer <token>` and optionally loads the user from the database:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { AuthMiddleware } from '@lara-node/middlewares';
|
|
87
|
+
|
|
88
|
+
const auth = new AuthMiddleware({
|
|
89
|
+
// Optional: load full user from DB (adds req.user with roles/permissions)
|
|
90
|
+
userLoader: async (uid) => {
|
|
91
|
+
const user = await User.with(['roles', 'roles.permissions']).find(uid);
|
|
92
|
+
if (!user) return null;
|
|
93
|
+
return {
|
|
94
|
+
id: user.id,
|
|
95
|
+
roles: user.roles.map(r => r.slug),
|
|
96
|
+
permissions: user.roles.flatMap(r => r.permissions.map(p => p.slug)),
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
// Optional: decrypt token before verification
|
|
100
|
+
decryptToken: (token) => myDecrypt(token),
|
|
101
|
+
}).toHandler();
|
|
102
|
+
|
|
103
|
+
app.use('/api/protected', auth);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If no `userLoader` is provided, `req.user` is populated from the JWT payload (`{ id: sub, roles, permissions }`).
|
|
107
|
+
|
|
108
|
+
### AuthorizeByStatusMiddleware
|
|
109
|
+
|
|
110
|
+
Rejects requests where `req.user.status !== 'active'` or `req.user.isActive()` returns false:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { authorizeByStatus } from '@lara-node/middlewares';
|
|
114
|
+
|
|
115
|
+
app.use('/api/protected', auth, authorizeByStatus);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### ErrorHandlerMiddleware
|
|
119
|
+
|
|
120
|
+
Express 4-argument error handler. Handles:
|
|
121
|
+
- `ValidationError` → 422 with `{ success: false, errors, messages }`
|
|
122
|
+
- Any `err.status` (400–599) → that status code
|
|
123
|
+
- Everything else → 500
|
|
124
|
+
- `err.stack` included in non-production responses
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { errorHandler } from '@lara-node/middlewares';
|
|
128
|
+
app.use(errorHandler); // must be registered last
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Authorization Helpers
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { authorizeRoles, authorizePermissions } from '@lara-node/middlewares';
|
|
135
|
+
|
|
136
|
+
// Allow only admin or moderator:
|
|
137
|
+
router.get('/admin', auth, authorizeRoles('admin', 'moderator'), handler);
|
|
138
|
+
|
|
139
|
+
// Require a specific permission:
|
|
140
|
+
router.delete('/users/:id', auth, authorizePermissions('delete_users'), handler);
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Class-Based Usage
|
|
144
|
+
|
|
145
|
+
All middleware can also be used as classes (useful for testing or custom wiring):
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { AuthMiddleware } from '@lara-node/middlewares';
|
|
149
|
+
|
|
150
|
+
// Class form:
|
|
151
|
+
const authMiddleware = new AuthMiddleware({ userLoader });
|
|
152
|
+
app.use(authMiddleware.handle.bind(authMiddleware));
|
|
153
|
+
// or:
|
|
154
|
+
app.use(authMiddleware.toHandler());
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Express Type Augmentation
|
|
158
|
+
|
|
159
|
+
This package augments the Express `Request` and `Response` interfaces globally:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// req.user is typed:
|
|
163
|
+
req.user?.id // number | string
|
|
164
|
+
req.user?.roles // string[]
|
|
165
|
+
req.user?.permissions // string[]
|
|
166
|
+
|
|
167
|
+
// req.validate is typed:
|
|
168
|
+
const data = await req.validate<{ email: string }>(rules);
|
|
169
|
+
|
|
170
|
+
// res.jsonAsync is typed:
|
|
171
|
+
await res.jsonAsync(model);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
You can also declare these locally in your project via `src/types/express.d.ts` — the `create-lara-node` scaffold generates this file automatically.
|
|
175
|
+
|
|
176
|
+
## Writing Custom Middleware
|
|
177
|
+
|
|
178
|
+
Follow the same class pattern:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { Request, Response, NextFunction } from 'express';
|
|
182
|
+
|
|
183
|
+
export class MaintenanceModeMiddleware {
|
|
184
|
+
handle(req: Request, res: Response, next: NextFunction): void {
|
|
185
|
+
if (process.env.MAINTENANCE === 'true') {
|
|
186
|
+
res.status(503).json({ message: 'Service temporarily unavailable.' });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
next();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
toHandler() {
|
|
193
|
+
return (req: Request, res: Response, next: NextFunction) =>
|
|
194
|
+
this.handle(req, res, next);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Async Context Store
|
|
200
|
+
|
|
201
|
+
The store is populated by `AsyncContextMiddleware` and augmented by `AuthMiddleware`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { asyncLocalStorage } from '@lara-node/middlewares';
|
|
205
|
+
|
|
206
|
+
function getCurrentUser() {
|
|
207
|
+
return asyncLocalStorage.getStore()?.user ?? null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getCurrentRequest() {
|
|
211
|
+
return asyncLocalStorage.getStore()?.req;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
MIT
|
|
@@ -24,6 +24,7 @@ interface AuthMiddlewareOptions {
|
|
|
24
24
|
id: number | string;
|
|
25
25
|
roles?: string[];
|
|
26
26
|
permissions?: string[];
|
|
27
|
+
[key: string]: any;
|
|
27
28
|
} | null>;
|
|
28
29
|
decryptToken?: (token: string) => string;
|
|
29
30
|
}
|
|
@@ -59,4 +60,4 @@ declare const errorHandler: (err: any, req: Request, res: Response, next: NextFu
|
|
|
59
60
|
|
|
60
61
|
//#endregion
|
|
61
62
|
export { AsyncContextMiddleware, AuthMiddleware, AuthMiddlewareOptions, AuthorizeByStatusMiddleware, ErrorHandlerMiddleware, RequestLoggerMiddleware, ResponseExtenderMiddleware, ValidatorMiddleware, asyncContext, asyncContextMiddleware, asyncLocalStorage, authorizeByStatus, authorizeByStatusMiddleware, authorizePermissions, authorizeRoles, errorHandler, errorHandlerMiddleware, requestLogger, requestLoggerMiddleware, responseExtender, responseExtenderMiddleware, validatorAttach, validatorMiddleware };
|
|
62
|
-
//# sourceMappingURL=index-
|
|
63
|
+
//# sourceMappingURL=index-CC42rCIZ.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-CC42rCIZ.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "async_hooks";
|
|
2
2
|
import jwt from "jsonwebtoken";
|
|
3
3
|
import { ValidationError, validate } from "@lara-node/validator";
|
|
4
|
+
import { Model } from "@lara-node/db";
|
|
4
5
|
//#region src/index.ts
|
|
5
6
|
const asyncLocalStorage = new AsyncLocalStorage();
|
|
6
7
|
var AsyncContextMiddleware = class {
|
|
@@ -58,21 +59,45 @@ async function serializeItem(item) {
|
|
|
58
59
|
if (item && typeof item.toJSONAsync === "function") return await item.toJSONAsync();
|
|
59
60
|
return item;
|
|
60
61
|
}
|
|
62
|
+
function containsModels(obj, visited = /* @__PURE__ */ new WeakSet()) {
|
|
63
|
+
if (obj === null || obj === void 0) return false;
|
|
64
|
+
const isObj = typeof obj === "object";
|
|
65
|
+
if (isObj && visited.has(obj)) return false;
|
|
66
|
+
if (isObj) visited.add(obj);
|
|
67
|
+
if (obj instanceof Model) return true;
|
|
68
|
+
if (Array.isArray(obj)) return obj.some((item) => containsModels(item, visited));
|
|
69
|
+
if (isQueryResult(obj)) return obj.data.some((item) => containsModels(item, visited));
|
|
70
|
+
if (isObj) return Object.values(obj).some((val) => containsModels(val, visited));
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
61
73
|
var ResponseExtenderMiddleware = class {
|
|
62
74
|
handle(_req, res, next) {
|
|
63
75
|
const originalJson = res.json.bind(res);
|
|
64
76
|
res.jsonAsync = async function(data) {
|
|
65
|
-
if (data
|
|
66
|
-
if (Array.isArray(data))
|
|
77
|
+
if (data instanceof Model) return originalJson(await data.toJSONAsync());
|
|
78
|
+
if (Array.isArray(data)) {
|
|
79
|
+
if (data.length === 0) return originalJson(data);
|
|
80
|
+
return originalJson(await Promise.all(data.map(serializeItem)));
|
|
81
|
+
}
|
|
67
82
|
if (isQueryResult(data)) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
if (data.data.length > 0) {
|
|
84
|
+
const processed = await Promise.all(data.data.map(serializeItem));
|
|
85
|
+
return originalJson({
|
|
86
|
+
...data,
|
|
87
|
+
data: processed
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return originalJson(data);
|
|
73
91
|
}
|
|
74
92
|
return originalJson(data);
|
|
75
93
|
};
|
|
94
|
+
res.json = function(data) {
|
|
95
|
+
if (data instanceof Model) return res.jsonAsync(data);
|
|
96
|
+
if (Array.isArray(data) && data.length > 0 && data.some((item) => item instanceof Model)) return res.jsonAsync(data);
|
|
97
|
+
if (isQueryResult(data)) return res.jsonAsync(data);
|
|
98
|
+
if (containsModels(data)) return res.jsonAsync(data);
|
|
99
|
+
return originalJson(data);
|
|
100
|
+
}.bind(res);
|
|
76
101
|
next();
|
|
77
102
|
}
|
|
78
103
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"async_hooks\";\nimport { Request, Response, NextFunction } from \"express\";\nimport jwt from \"jsonwebtoken\";\nimport { ValidationError, validate, RuleFn, RuleSpec } from \"@lara-node/validator\";\n\n// ─── Shared async context storage ──────────────────────────────────────────────\n\nexport const asyncLocalStorage = new AsyncLocalStorage<Record<string, any>>();\n\n// ─── Type augmentations ─────────────────────────────────────────────────────────\n\ndeclare global {\n namespace Express {\n interface Request {\n user?: {\n id: number | string;\n roles?: string[];\n permissions?: string[];\n };\n validate: <T extends Record<string, any>>(\n payloadOrRules?: any,\n rulesMaybe?: Record<string, RuleSpec> | Record<string, string | RuleFn>,\n customMessages?: Record<string, string>,\n ) => Promise<T>;\n }\n interface Response {\n jsonAsync: <T>(data: T) => Promise<Response>;\n }\n }\n}\n\n// ─── AsyncContextMiddleware ─────────────────────────────────────────────────────\n\nexport class AsyncContextMiddleware {\n handle(req: Request, _res: Response, next: NextFunction): void {\n asyncLocalStorage.run({ req }, () => next());\n }\n}\n\n// ─── RequestLoggerMiddleware ────────────────────────────────────────────────────\n\nexport class RequestLoggerMiddleware {\n handle(req: Request, res: Response, next: NextFunction): void {\n const start = process.hrtime();\n const { method, originalUrl } = req;\n const ip = (req.ip || req.headers[\"x-forwarded-for\"] || (req.socket && req.socket.remoteAddress)) as string | undefined;\n\n res.on(\"finish\", () => {\n const [sec, nano] = process.hrtime(start);\n const ms = (sec * 1e3 + nano / 1e6).toFixed(2);\n const status = res.statusCode;\n const reset = \"\\x1b[0m\";\n let color = \"\\x1b[32m\";\n if (status >= 500) color = \"\\x1b[31m\";\n else if (status >= 400) color = \"\\x1b[33m\";\n const maybeUser = (req as any).user;\n const userInfo = maybeUser ? ` - user:${maybeUser.id ?? maybeUser.email ?? JSON.stringify(maybeUser)}` : \"\";\n const query = req.query && Object.keys(req.query).length ? ` query=${JSON.stringify(req.query)}` : \"\";\n const params = req.params && Object.keys(req.params).length ? ` params=${JSON.stringify(req.params)}` : \"\";\n console.log(`${method} ${originalUrl} ${color}${status}${reset} - ${ms} ms - ${ip || \"-\"}${userInfo}${query}${params}`);\n });\n\n next();\n }\n}\n\n// ─── ValidatorMiddleware ────────────────────────────────────────────────────────\n\nexport class ValidatorMiddleware {\n handle(req: Request & { validate?: any }, _res: Response, next: NextFunction): void {\n req.validate = async function <T extends Record<string, any>>(\n payloadOrRules: any,\n maybeRules?: any,\n customMessages?: Record<string, string>,\n ): Promise<T> {\n let payload: any;\n let rules: any;\n\n if (\n maybeRules === undefined &&\n typeof payloadOrRules === \"object\" &&\n !Array.isArray(payloadOrRules) &&\n Object.keys(payloadOrRules || {}).length &&\n Object.values(payloadOrRules).every(\n (v) => typeof v === \"string\" || typeof v === \"function\" || (typeof v === \"object\" && v && \"rule\" in (v as any)),\n )\n ) {\n rules = payloadOrRules;\n payload = (req as any).body?.payload ?? (req as any).body;\n } else if (maybeRules !== undefined) {\n payload = payloadOrRules;\n rules = maybeRules;\n } else {\n payload = payloadOrRules ?? ((req as any).body?.payload ?? (req as any).body);\n rules = maybeRules;\n }\n\n if (!rules) throw new Error(\"No validation rules provided\");\n return await validate<T>(payload, rules, customMessages);\n };\n\n next();\n }\n}\n\n// ─── ResponseExtenderMiddleware ─────────────────────────────────────────────────\n\nfunction isQueryResult(obj: any): boolean {\n return obj && typeof obj === \"object\" && Array.isArray(obj.data);\n}\n\nasync function serializeItem(item: any): Promise<any> {\n if (item && typeof item.toJSONAsync === \"function\") return await item.toJSONAsync();\n return item;\n}\n\nexport class ResponseExtenderMiddleware {\n handle(_req: Request, res: Response, next: NextFunction): void {\n const originalJson = res.json.bind(res);\n\n res.jsonAsync = async function <T>(data: T): Promise<Response> {\n if (data && typeof (data as any).toJSONAsync === \"function\") {\n return originalJson(await (data as any).toJSONAsync());\n }\n if (Array.isArray(data)) {\n return originalJson(await Promise.all(data.map(serializeItem)));\n }\n if (isQueryResult(data)) {\n const processed = await Promise.all((data as any).data.map(serializeItem));\n return originalJson({ ...(data as any), data: processed });\n }\n return originalJson(data);\n };\n\n next();\n }\n}\n\n// ─── AuthMiddleware ─────────────────────────────────────────────────────────────\n\nexport interface AuthMiddlewareOptions {\n userLoader?: (uid: string | number) => Promise<{ id: number | string; roles?: string[]; permissions?: string[] } | null>;\n decryptToken?: (token: string) => string;\n}\n\nexport class AuthMiddleware {\n private userLoader?: (uid: string | number) => Promise<any | null>;\n private decryptFn?: (token: string) => string;\n\n constructor(options: AuthMiddlewareOptions = {}) {\n this.userLoader = options.userLoader;\n this.decryptFn = options.decryptToken;\n }\n\n async handle(req: Request, res: Response, next: NextFunction): Promise<void> {\n const header = req.headers[\"authorization\"] || \"\";\n const token = header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n\n if (!token) {\n res.status(401).json({ message: \"Unauthorized\" });\n return;\n }\n\n try {\n const JWT_SECRET = process.env.JWT_SECRET || \"dev-secret-change\";\n const rawToken = this.decryptFn ? this.decryptFn(token) : token;\n const decoded = jwt.verify(rawToken, JWT_SECRET) as any;\n const uid = decoded.sub;\n\n if (this.userLoader) {\n const user = await this.userLoader(uid);\n if (!user) { res.status(401).json({ message: \"Unauthorized\" }); return; }\n req.user = { id: user.id, roles: user.roles, permissions: user.permissions };\n const store = asyncLocalStorage.getStore();\n if (store) store.user = user;\n } else {\n req.user = { id: uid, roles: decoded.roles, permissions: decoded.permissions };\n }\n\n next();\n } catch {\n res.status(401).json({ message: \"Unauthorized\" });\n }\n }\n\n toHandler(): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n return this.handle.bind(this);\n }\n}\n\n// ─── AuthorizeByStatusMiddleware ────────────────────────────────────────────────\n\nexport class AuthorizeByStatusMiddleware {\n handle(req: Request, res: Response, next: NextFunction): void {\n const user = req.user as any;\n if (!user) { res.status(401).json({ message: \"Unauthorized\" }); return; }\n if (typeof user.isActive === \"function\" && !user.isActive()) {\n res.status(401).json({ message: \"Account Inactive\" });\n return;\n }\n if (user.status && user.status !== \"active\") {\n res.status(401).json({ message: \"Account Inactive\" });\n return;\n }\n next();\n }\n}\n\n// ─── ErrorHandlerMiddleware ─────────────────────────────────────────────────────\n\nexport class ErrorHandlerMiddleware {\n handle(err: any, _req: Request, res: Response, _next: NextFunction): void {\n if (res.headersSent) return;\n\n if (err instanceof ValidationError) {\n res.status(422).json({ success: false, errors: err.errors, messages: err.messages, message: err.message });\n return;\n }\n\n const status =\n typeof err.status === \"number\" && err.status >= 400 && err.status < 600 ? err.status : 500;\n const message = err.message || \"Internal Server Error\";\n const payload: any = { success: false, message };\n if (err.code) payload.code = err.code;\n if (err.errors && typeof err.errors === \"object\") payload.errors = err.errors;\n if (process.env.NODE_ENV !== \"production\" && err.stack)\n payload.stack = err.stack.split(\"\\n\").map((l: string) => l.trim());\n\n res.status(status).json(payload);\n }\n\n toHandler(): (err: any, req: Request, res: Response, next: NextFunction) => void {\n return this.handle.bind(this);\n }\n}\n\n// ─── Authorization helpers ──────────────────────────────────────────────────────\n\nexport function authorizeRoles(...roles: string[]) {\n return (req: Request, res: Response, next: NextFunction): void => {\n const userRoles = req.user?.roles || [];\n if (!roles.some((r) => userRoles.includes(r))) {\n res.status(403).json({ message: \"Forbidden\" });\n return;\n }\n next();\n };\n}\n\nexport function authorizePermissions(...perms: string[]) {\n return (req: Request, res: Response, next: NextFunction): void => {\n const userPerms = req.user?.permissions || [];\n if (!perms.some((p) => userPerms.includes(p))) {\n res.status(403).json({ message: \"Forbidden\" });\n return;\n }\n next();\n };\n}\n\n// ─── Singleton instances & function-form exports ────────────────────────────────\n\nexport const asyncContextMiddleware = new AsyncContextMiddleware();\nexport const requestLoggerMiddleware = new RequestLoggerMiddleware();\nexport const validatorMiddleware = new ValidatorMiddleware();\nexport const responseExtenderMiddleware = new ResponseExtenderMiddleware();\nexport const authorizeByStatusMiddleware = new AuthorizeByStatusMiddleware();\nexport const errorHandlerMiddleware = new ErrorHandlerMiddleware();\n\nexport const asyncContext = (req: Request, res: Response, next: NextFunction) =>\n asyncContextMiddleware.handle(req, res, next);\nexport const requestLogger = (req: Request, res: Response, next: NextFunction) =>\n requestLoggerMiddleware.handle(req, res, next);\nexport const validatorAttach = (req: Request, res: Response, next: NextFunction) =>\n validatorMiddleware.handle(req, res, next);\nexport const responseExtender = (req: Request, res: Response, next: NextFunction) =>\n responseExtenderMiddleware.handle(req, res, next);\nexport const authorizeByStatus = (req: Request, res: Response, next: NextFunction) =>\n authorizeByStatusMiddleware.handle(req, res, next);\nexport const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) =>\n errorHandlerMiddleware.handle(err, req, res, next);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"async_hooks\";\nimport { Request, Response, NextFunction } from \"express\";\nimport jwt from \"jsonwebtoken\";\nimport { ValidationError, validate, RuleFn, RuleSpec } from \"@lara-node/validator\";\nimport { Model, QueryResult } from \"@lara-node/db\";\n\n// ─── Shared async context storage ──────────────────────────────────────────────\n\nexport const asyncLocalStorage = new AsyncLocalStorage<Record<string, any>>();\n\n// ─── Type augmentations ─────────────────────────────────────────────────────────\n\ndeclare global {\n namespace Express {\n interface Request {\n user?: {\n id: number | string;\n email?: string;\n name?: string;\n roles?: string[];\n permissions?: string[];\n [key: string]: any;\n };\n validate: <T extends Record<string, any>>(\n payloadOrRules?: any,\n rulesMaybe?: Record<string, RuleSpec> | Record<string, string | RuleFn>,\n customMessages?: Record<string, string>,\n ) => Promise<T>;\n }\n interface Response {\n jsonAsync: <T>(data: T) => Promise<Response>;\n }\n }\n}\n\n// ─── AsyncContextMiddleware ─────────────────────────────────────────────────────\n\nexport class AsyncContextMiddleware {\n handle(req: Request, _res: Response, next: NextFunction): void {\n asyncLocalStorage.run({ req }, () => next());\n }\n}\n\n// ─── RequestLoggerMiddleware ────────────────────────────────────────────────────\n\nexport class RequestLoggerMiddleware {\n handle(req: Request, res: Response, next: NextFunction): void {\n const start = process.hrtime();\n const { method, originalUrl } = req;\n const ip = (req.ip || req.headers[\"x-forwarded-for\"] || (req.socket && req.socket.remoteAddress)) as string | undefined;\n\n res.on(\"finish\", () => {\n const [sec, nano] = process.hrtime(start);\n const ms = (sec * 1e3 + nano / 1e6).toFixed(2);\n const status = res.statusCode;\n const reset = \"\\x1b[0m\";\n let color = \"\\x1b[32m\";\n if (status >= 500) color = \"\\x1b[31m\";\n else if (status >= 400) color = \"\\x1b[33m\";\n const maybeUser = (req as any).user;\n const userInfo = maybeUser ? ` - user:${maybeUser.id ?? maybeUser.email ?? JSON.stringify(maybeUser)}` : \"\";\n const query = req.query && Object.keys(req.query).length ? ` query=${JSON.stringify(req.query)}` : \"\";\n const params = req.params && Object.keys(req.params).length ? ` params=${JSON.stringify(req.params)}` : \"\";\n console.log(`${method} ${originalUrl} ${color}${status}${reset} - ${ms} ms - ${ip || \"-\"}${userInfo}${query}${params}`);\n });\n\n next();\n }\n}\n\n// ─── ValidatorMiddleware ────────────────────────────────────────────────────────\n\nexport class ValidatorMiddleware {\n handle(req: Request & { validate?: any }, _res: Response, next: NextFunction): void {\n req.validate = async function <T extends Record<string, any>>(\n payloadOrRules: any,\n maybeRules?: any,\n customMessages?: Record<string, string>,\n ): Promise<T> {\n let payload: any;\n let rules: any;\n\n if (\n maybeRules === undefined &&\n typeof payloadOrRules === \"object\" &&\n !Array.isArray(payloadOrRules) &&\n Object.keys(payloadOrRules || {}).length &&\n Object.values(payloadOrRules).every(\n (v) => typeof v === \"string\" || typeof v === \"function\" || (typeof v === \"object\" && v && \"rule\" in (v as any)),\n )\n ) {\n rules = payloadOrRules;\n payload = (req as any).body?.payload ?? (req as any).body;\n } else if (maybeRules !== undefined) {\n payload = payloadOrRules;\n rules = maybeRules;\n } else {\n payload = payloadOrRules ?? ((req as any).body?.payload ?? (req as any).body);\n rules = maybeRules;\n }\n\n if (!rules) throw new Error(\"No validation rules provided\");\n return await validate<T>(payload, rules, customMessages);\n };\n\n next();\n }\n}\n\n// ─── ResponseExtenderMiddleware ─────────────────────────────────────────────────\n\nfunction isQueryResult(obj: any): obj is QueryResult<any> {\n return obj && typeof obj === \"object\" && Array.isArray(obj.data);\n}\n\nasync function serializeItem(item: any): Promise<any> {\n if (item && typeof item.toJSONAsync === \"function\") return await item.toJSONAsync();\n return item;\n}\n\nfunction containsModels(obj: any, visited = new WeakSet()): boolean {\n if (obj === null || obj === undefined) return false;\n const isObj = typeof obj === \"object\";\n if (isObj && visited.has(obj)) return false;\n if (isObj) visited.add(obj);\n if (obj instanceof Model) return true;\n if (Array.isArray(obj)) return obj.some((item) => containsModels(item, visited));\n if (isQueryResult(obj)) return obj.data.some((item) => containsModels(item, visited));\n if (isObj) return Object.values(obj).some((val) => containsModels(val, visited));\n return false;\n}\n\nexport class ResponseExtenderMiddleware {\n handle(_req: Request, res: Response, next: NextFunction): void {\n const originalJson = res.json.bind(res);\n\n res.jsonAsync = async function <T>(data: T): Promise<Response> {\n if (data instanceof Model) return originalJson(await (data as any).toJSONAsync());\n if (Array.isArray(data)) {\n if (data.length === 0) return originalJson(data);\n return originalJson(await Promise.all(data.map(serializeItem)));\n }\n if (isQueryResult(data)) {\n if ((data as any).data.length > 0) {\n const processed = await Promise.all((data as any).data.map(serializeItem));\n return originalJson({ ...(data as any), data: processed });\n }\n return originalJson(data);\n }\n return originalJson(data);\n };\n\n res.json = function <T>(this: Response, data?: T): Response {\n if (data instanceof Model) return (res as any).jsonAsync(data);\n if (Array.isArray(data) && data.length > 0 && data.some((item: any) => item instanceof Model))\n return (res as any).jsonAsync(data);\n if (isQueryResult(data)) return (res as any).jsonAsync(data);\n if (containsModels(data)) return (res as any).jsonAsync(data);\n return originalJson(data);\n }.bind(res);\n\n next();\n }\n}\n\n// ─── AuthMiddleware ─────────────────────────────────────────────────────────────\n\nexport interface AuthMiddlewareOptions {\n userLoader?: (uid: string | number) => Promise<{ id: number | string; roles?: string[]; permissions?: string[]; [key: string]: any } | null>;\n decryptToken?: (token: string) => string;\n}\n\nexport class AuthMiddleware {\n private userLoader?: (uid: string | number) => Promise<any | null>;\n private decryptFn?: (token: string) => string;\n\n constructor(options: AuthMiddlewareOptions = {}) {\n this.userLoader = options.userLoader;\n this.decryptFn = options.decryptToken;\n }\n\n async handle(req: Request, res: Response, next: NextFunction): Promise<void> {\n const header = req.headers[\"authorization\"] || \"\";\n const token = header.startsWith(\"Bearer \") ? header.slice(7) : \"\";\n\n if (!token) {\n res.status(401).json({ message: \"Unauthorized\" });\n return;\n }\n\n try {\n const JWT_SECRET = process.env.JWT_SECRET || \"dev-secret-change\";\n const rawToken = this.decryptFn ? this.decryptFn(token) : token;\n const decoded = jwt.verify(rawToken, JWT_SECRET) as any;\n const uid = decoded.sub;\n\n if (this.userLoader) {\n const user = await this.userLoader(uid);\n if (!user) { res.status(401).json({ message: \"Unauthorized\" }); return; }\n req.user = { id: user.id, roles: user.roles, permissions: user.permissions };\n const store = asyncLocalStorage.getStore();\n if (store) store.user = user;\n } else {\n req.user = { id: uid, roles: decoded.roles, permissions: decoded.permissions };\n }\n\n next();\n } catch {\n res.status(401).json({ message: \"Unauthorized\" });\n }\n }\n\n toHandler(): (req: Request, res: Response, next: NextFunction) => Promise<void> {\n return this.handle.bind(this);\n }\n}\n\n// ─── AuthorizeByStatusMiddleware ────────────────────────────────────────────────\n\nexport class AuthorizeByStatusMiddleware {\n handle(req: Request, res: Response, next: NextFunction): void {\n const user = req.user as any;\n if (!user) { res.status(401).json({ message: \"Unauthorized\" }); return; }\n if (typeof user.isActive === \"function\" && !user.isActive()) {\n res.status(401).json({ message: \"Account Inactive\" }); return;\n }\n if (user.status && user.status !== \"active\") {\n res.status(401).json({ message: \"Account Inactive\" }); return;\n }\n next();\n }\n}\n\n// ─── ErrorHandlerMiddleware ─────────────────────────────────────────────────────\n\nexport class ErrorHandlerMiddleware {\n handle(err: any, _req: Request, res: Response, _next: NextFunction): void {\n if (res.headersSent) return;\n\n if (err instanceof ValidationError) {\n res.status(422).json({ success: false, errors: err.errors, messages: err.messages, message: err.message });\n return;\n }\n\n const status =\n typeof err.status === \"number\" && err.status >= 400 && err.status < 600 ? err.status : 500;\n const message = err.message || \"Internal Server Error\";\n const payload: any = { success: false, message };\n if (err.code) payload.code = err.code;\n if (err.errors && typeof err.errors === \"object\") payload.errors = err.errors;\n if (process.env.NODE_ENV !== \"production\" && err.stack)\n payload.stack = err.stack.split(\"\\n\").map((l: string) => l.trim());\n\n res.status(status).json(payload);\n }\n\n toHandler(): (err: any, req: Request, res: Response, next: NextFunction) => void {\n return this.handle.bind(this);\n }\n}\n\n// ─── Authorization helpers ──────────────────────────────────────────────────────\n\nexport function authorizeRoles(...roles: string[]) {\n return (req: Request, res: Response, next: NextFunction): void => {\n const userRoles = req.user?.roles || [];\n if (!roles.some((r) => userRoles.includes(r))) {\n res.status(403).json({ message: \"Forbidden\" }); return;\n }\n next();\n };\n}\n\nexport function authorizePermissions(...perms: string[]) {\n return (req: Request, res: Response, next: NextFunction): void => {\n const userPerms = req.user?.permissions || [];\n if (!perms.some((p) => userPerms.includes(p))) {\n res.status(403).json({ message: \"Forbidden\" }); return;\n }\n next();\n };\n}\n\n// ─── Singleton instances & function-form exports ────────────────────────────────\n\nexport const asyncContextMiddleware = new AsyncContextMiddleware();\nexport const requestLoggerMiddleware = new RequestLoggerMiddleware();\nexport const validatorMiddleware = new ValidatorMiddleware();\nexport const responseExtenderMiddleware = new ResponseExtenderMiddleware();\nexport const authorizeByStatusMiddleware = new AuthorizeByStatusMiddleware();\nexport const errorHandlerMiddleware = new ErrorHandlerMiddleware();\n\nexport const asyncContext = (req: Request, res: Response, next: NextFunction) =>\n asyncContextMiddleware.handle(req, res, next);\nexport const requestLogger = (req: Request, res: Response, next: NextFunction) =>\n requestLoggerMiddleware.handle(req, res, next);\nexport const validatorAttach = (req: Request, res: Response, next: NextFunction) =>\n validatorMiddleware.handle(req, res, next);\nexport const responseExtender = (req: Request, res: Response, next: NextFunction) =>\n responseExtenderMiddleware.handle(req, res, next);\nexport const authorizeByStatus = (req: Request, res: Response, next: NextFunction) =>\n authorizeByStatusMiddleware.handle(req, res, next);\nexport const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) =>\n errorHandlerMiddleware.handle(err, req, res, next);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lara-node/middlewares",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Predefined class-based middleware for Lara-Node applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"jsonwebtoken": "^9.0.2",
|
|
19
|
-
"@lara-node/validator": "0.1.
|
|
19
|
+
"@lara-node/validator": "0.1.2",
|
|
20
|
+
"@lara-node/db": "0.1.1"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"@types/express": "^5.0.6",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-B9p8lFgD.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;cAOa,mBAAiB,kBAAA;AAmBU,UAAA,CAAA,CAAA;AAgB1B,cATD,sBAAA,CASC;QAAc,CAAA,GAAA,EARd,OAQc,EAAA,IAAA,EARC,QAQD,EAAA,IAAA,EARiB,YAQjB,CAAA,EAAA,IAAA;;AAA4B,cAD3C,uBAAA,CAC2C;EA0B3C,MAAA,CAAA,GAAA,EA1BC,OA0BD,EAAA,GAAmB,EA1BJ,QA0BI,EAAA,IAAA,EA1BY,YA0BZ,CAAA,EAAA,IAAA;;AAClB,cADD,mBAAA,CACC;QAAoC,CAAA,GAAA,EAApC,OAAoC,GAAA;IAAgB,QAAA,CAAA,EAAA,GAAA;EAAY,CAAA,EAAA,IAAA,EAA5B,QAA4B,EAAA,IAAA,EAAZ,YAAY,CAAA,EAAA,IAAA;AA+C9E;AAAuC,cAA1B,0BAAA,CAA0B;QACxB,CAAA,IAAA,EAAA,OAAA,EAAA,GAAA,EAAc,QAAd,EAAA,IAAA,EAA8B,YAA9B,CAAA,EAAA,IAAA;;AAA8B,UAuB5B,qBAAA,CAvB4B;EAAY,UAAA,CAAA,EAAA,CAAA,GAAA,EAAA,MAAA,GAAA,MAAA,EAAA,GAwBhB,OAxBgB,CAAA;IAuBxC,EAAA,EAAA,MAAA,GAAA,MAAA;IAKJ,KAAA,CAAA,EAAA,MAAc,EAAA;IAAA,WAAA,CAAA,EAAA,MAAA,EAAA;MAIJ,IAAA,CAAA;cAKH,CAAA,EAAA,CAAA,KAAA,EAAA,MAAA,EAAA,GAAA,MAAA;;AAA8B,cATrC,cAAA,CASqC;UAAe,UAAA;UA+B5C,SAAA;aAAc,CAAA,OAAA,CAAA,EApCZ,qBAoCY;QAAgB,CAAA,GAAA,EA/B/B,OA+B+B,EAAA,GAAA,EA/BjB,QA+BiB,EAAA,IAAA,EA/BD,YA+BC,CAAA,EA/Bc,OA+Bd,CAAA,IAAA,CAAA;WAAiB,CAAA,CAAA,EAAA,CAAA,GAAA,EAA/C,OAA+C,EAAA,GAAA,EAAjC,QAAiC,EAAA,IAAA,EAAjB,YAAiB,EAAA,GAAA,OAAA,CAAA,IAAA,CAAA;AAAO;AAO9D,cAAA,2BAAA,CAA2B;EAAA,MAAA,CAAA,GAAA,EAC1B,OAD0B,EAAA,GAAA,EACZ,QADY,EAAA,IAAA,EACI,YADJ,CAAA,EAAA,IAAA;;AACZ,cAiBf,sBAAA,CAjBe;QAAgB,CAAA,GAAA,EAAA,GAAA,EAAA,IAAA,EAkBnB,OAlBmB,EAAA,GAAA,EAkBL,QAlBK,EAAA,KAAA,EAkBY,YAlBZ,CAAA,EAAA,IAAA;EAAY,SAAA,CAAA,CAAA,EAAA,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,EAsCzB,OAtCyB,EAAA,GAAA,EAsCX,QAtCW,EAAA,IAAA,EAsCK,YAtCL,EAAA,GAAA,IAAA;AAiBxD;AAAmC,iBA4BnB,cAAA,CA5BmB,GAAA,KAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA,GAAA,EA6BpB,OA7BoB,EAAA,GAAA,EA6BN,QA7BM,EAAA,IAAA,EA6BU,YA7BV,EAAA,GAAA,IAAA;AACV,iBAsCT,oBAAA,CAtCS,GAAA,KAAA,EAAA,MAAA,EAAA,CAAA,EAAA,CAAA,GAAA,EAuCV,OAvCU,EAAA,GAAA,EAuCI,QAvCJ,EAAA,IAAA,EAuCoB,YAvCpB,EAAA,GAAA,IAAA;AAAc,cAmD1B,sBAnD0B,EAmDJ,sBAnDI;AAAiB,cAoD3C,uBApD2C,EAoDpB,uBApDoB;AAoBzB,cAiClB,mBAjCkB,EAiCC,mBAjCD;AAAc,cAkChC,0BAlCgC,EAkCN,0BAlCM;AAAgB,cAmChD,2BAnCgD,EAmCrB,2BAnCqB;AAAY,cAoC5D,sBApC4D,EAoCtC,sBApCsC;AAOzD,cA+BH,YA/BiB,EAAA,CAAA,GAAA,EA+BI,OA/BJ,EAAA,GAAA,EA+BkB,QA/BlB,EAAA,IAAA,EA+BkC,YA/BlC,EAAA,GAAA,IAAA;AAAA,cAiCjB,aAjCiB,EAAA,CAAA,GAAA,EAiCK,OAjCL,EAAA,GAAA,EAiCmB,QAjCnB,EAAA,IAAA,EAiCmC,YAjCnC,EAAA,GAAA,IAAA;AACf,cAkCF,eAlCE,EAAA,CAAA,GAAA,EAkCsB,OAlCtB,EAAA,GAAA,EAkCoC,QAlCpC,EAAA,IAAA,EAkCoD,YAlCpD,EAAA,GAAA,IAAA;AAAc,cAoChB,gBApCgB,EAAA,CAAA,GAAA,EAoCS,OApCT,EAAA,GAAA,EAoCuB,QApCvB,EAAA,IAAA,EAoCuC,YApCvC,EAAA,GAAA,IAAA;AAAgB,cAsChC,iBAtCgC,EAAA,CAAA,GAAA,EAsCN,OAtCM,EAAA,GAAA,EAsCQ,QAtCR,EAAA,IAAA,EAsCwB,YAtCxB,EAAA,GAAA,IAAA;AAAY,cAwC5C,YAxC4C,EAAA,CAAA,GAAA,EAAA,GAAA,EAAA,GAAA,EAwCb,OAxCa,EAAA,GAAA,EAwCC,QAxCD,EAAA,IAAA,EAwCiB,YAxCjB,EAAA,GAAA,IAAA;AAUzD"}
|