@lara-node/middlewares 0.1.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/index-B9p8lFgD.d.ts +62 -0
- package/dist/index-B9p8lFgD.d.ts.map +1 -0
- package/dist/index.js +202 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
import "@lara-node/validator";
|
|
3
|
+
import { NextFunction, Request, Response } from "express";
|
|
4
|
+
|
|
5
|
+
//#region src/index.d.ts
|
|
6
|
+
declare const asyncLocalStorage: AsyncLocalStorage<Record<string, any>>;
|
|
7
|
+
sideEffect();
|
|
8
|
+
declare class AsyncContextMiddleware {
|
|
9
|
+
handle(req: Request, _res: Response, next: NextFunction): void;
|
|
10
|
+
}
|
|
11
|
+
declare class RequestLoggerMiddleware {
|
|
12
|
+
handle(req: Request, res: Response, next: NextFunction): void;
|
|
13
|
+
}
|
|
14
|
+
declare class ValidatorMiddleware {
|
|
15
|
+
handle(req: Request & {
|
|
16
|
+
validate?: any;
|
|
17
|
+
}, _res: Response, next: NextFunction): void;
|
|
18
|
+
}
|
|
19
|
+
declare class ResponseExtenderMiddleware {
|
|
20
|
+
handle(_req: Request, res: Response, next: NextFunction): void;
|
|
21
|
+
}
|
|
22
|
+
interface AuthMiddlewareOptions {
|
|
23
|
+
userLoader?: (uid: string | number) => Promise<{
|
|
24
|
+
id: number | string;
|
|
25
|
+
roles?: string[];
|
|
26
|
+
permissions?: string[];
|
|
27
|
+
} | null>;
|
|
28
|
+
decryptToken?: (token: string) => string;
|
|
29
|
+
}
|
|
30
|
+
declare class AuthMiddleware {
|
|
31
|
+
private userLoader?;
|
|
32
|
+
private decryptFn?;
|
|
33
|
+
constructor(options?: AuthMiddlewareOptions);
|
|
34
|
+
handle(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
35
|
+
toHandler(): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
declare class AuthorizeByStatusMiddleware {
|
|
38
|
+
handle(req: Request, res: Response, next: NextFunction): void;
|
|
39
|
+
}
|
|
40
|
+
declare class ErrorHandlerMiddleware {
|
|
41
|
+
handle(err: any, _req: Request, res: Response, _next: NextFunction): void;
|
|
42
|
+
toHandler(): (err: any, req: Request, res: Response, next: NextFunction) => void;
|
|
43
|
+
}
|
|
44
|
+
declare function authorizeRoles(...roles: string[]): (req: Request, res: Response, next: NextFunction) => void;
|
|
45
|
+
declare function authorizePermissions(...perms: string[]): (req: Request, res: Response, next: NextFunction) => void;
|
|
46
|
+
declare const asyncContextMiddleware: AsyncContextMiddleware;
|
|
47
|
+
declare const requestLoggerMiddleware: RequestLoggerMiddleware;
|
|
48
|
+
declare const validatorMiddleware: ValidatorMiddleware;
|
|
49
|
+
declare const responseExtenderMiddleware: ResponseExtenderMiddleware;
|
|
50
|
+
declare const authorizeByStatusMiddleware: AuthorizeByStatusMiddleware;
|
|
51
|
+
declare const errorHandlerMiddleware: ErrorHandlerMiddleware;
|
|
52
|
+
declare const asyncContext: (req: Request, res: Response, next: NextFunction) => void;
|
|
53
|
+
declare const requestLogger: (req: Request, res: Response, next: NextFunction) => void;
|
|
54
|
+
declare const validatorAttach: (req: Request, res: Response, next: NextFunction) => void;
|
|
55
|
+
declare const responseExtender: (req: Request, res: Response, next: NextFunction) => void;
|
|
56
|
+
declare const authorizeByStatus: (req: Request, res: Response, next: NextFunction) => void;
|
|
57
|
+
declare const errorHandler: (err: any, req: Request, res: Response, next: NextFunction) => void;
|
|
58
|
+
//# sourceMappingURL=index.d.ts.map
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
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-B9p8lFgD.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-B9p8lFgD.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { ValidationError, validate } from "@lara-node/validator";
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
const asyncLocalStorage = new AsyncLocalStorage();
|
|
6
|
+
var AsyncContextMiddleware = class {
|
|
7
|
+
handle(req, _res, next) {
|
|
8
|
+
asyncLocalStorage.run({ req }, () => next());
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var RequestLoggerMiddleware = class {
|
|
12
|
+
handle(req, res, next) {
|
|
13
|
+
const start = process.hrtime();
|
|
14
|
+
const { method, originalUrl } = req;
|
|
15
|
+
const ip = req.ip || req.headers["x-forwarded-for"] || req.socket && req.socket.remoteAddress;
|
|
16
|
+
res.on("finish", () => {
|
|
17
|
+
const [sec, nano] = process.hrtime(start);
|
|
18
|
+
const ms = (sec * 1e3 + nano / 1e6).toFixed(2);
|
|
19
|
+
const status = res.statusCode;
|
|
20
|
+
const reset = "\x1B[0m";
|
|
21
|
+
let color = "\x1B[32m";
|
|
22
|
+
if (status >= 500) color = "\x1B[31m";
|
|
23
|
+
else if (status >= 400) color = "\x1B[33m";
|
|
24
|
+
const maybeUser = req.user;
|
|
25
|
+
const userInfo = maybeUser ? ` - user:${maybeUser.id ?? maybeUser.email ?? JSON.stringify(maybeUser)}` : "";
|
|
26
|
+
const query = req.query && Object.keys(req.query).length ? ` query=${JSON.stringify(req.query)}` : "";
|
|
27
|
+
const params = req.params && Object.keys(req.params).length ? ` params=${JSON.stringify(req.params)}` : "";
|
|
28
|
+
console.log(`${method} ${originalUrl} ${color}${status}${reset} - ${ms} ms - ${ip || "-"}${userInfo}${query}${params}`);
|
|
29
|
+
});
|
|
30
|
+
next();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var ValidatorMiddleware = class {
|
|
34
|
+
handle(req, _res, next) {
|
|
35
|
+
req.validate = async function(payloadOrRules, maybeRules, customMessages) {
|
|
36
|
+
let payload;
|
|
37
|
+
let rules;
|
|
38
|
+
if (maybeRules === void 0 && typeof payloadOrRules === "object" && !Array.isArray(payloadOrRules) && Object.keys(payloadOrRules || {}).length && Object.values(payloadOrRules).every((v) => typeof v === "string" || typeof v === "function" || typeof v === "object" && v && "rule" in v)) {
|
|
39
|
+
rules = payloadOrRules;
|
|
40
|
+
payload = req.body?.payload ?? req.body;
|
|
41
|
+
} else if (maybeRules !== void 0) {
|
|
42
|
+
payload = payloadOrRules;
|
|
43
|
+
rules = maybeRules;
|
|
44
|
+
} else {
|
|
45
|
+
payload = payloadOrRules ?? req.body?.payload ?? req.body;
|
|
46
|
+
rules = maybeRules;
|
|
47
|
+
}
|
|
48
|
+
if (!rules) throw new Error("No validation rules provided");
|
|
49
|
+
return await validate(payload, rules, customMessages);
|
|
50
|
+
};
|
|
51
|
+
next();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function isQueryResult(obj) {
|
|
55
|
+
return obj && typeof obj === "object" && Array.isArray(obj.data);
|
|
56
|
+
}
|
|
57
|
+
async function serializeItem(item) {
|
|
58
|
+
if (item && typeof item.toJSONAsync === "function") return await item.toJSONAsync();
|
|
59
|
+
return item;
|
|
60
|
+
}
|
|
61
|
+
var ResponseExtenderMiddleware = class {
|
|
62
|
+
handle(_req, res, next) {
|
|
63
|
+
const originalJson = res.json.bind(res);
|
|
64
|
+
res.jsonAsync = async function(data) {
|
|
65
|
+
if (data && typeof data.toJSONAsync === "function") return originalJson(await data.toJSONAsync());
|
|
66
|
+
if (Array.isArray(data)) return originalJson(await Promise.all(data.map(serializeItem)));
|
|
67
|
+
if (isQueryResult(data)) {
|
|
68
|
+
const processed = await Promise.all(data.data.map(serializeItem));
|
|
69
|
+
return originalJson({
|
|
70
|
+
...data,
|
|
71
|
+
data: processed
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return originalJson(data);
|
|
75
|
+
};
|
|
76
|
+
next();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var AuthMiddleware = class {
|
|
80
|
+
constructor(options = {}) {
|
|
81
|
+
this.userLoader = options.userLoader;
|
|
82
|
+
this.decryptFn = options.decryptToken;
|
|
83
|
+
}
|
|
84
|
+
async handle(req, res, next) {
|
|
85
|
+
const header = req.headers["authorization"] || "";
|
|
86
|
+
const token = header.startsWith("Bearer ") ? header.slice(7) : "";
|
|
87
|
+
if (!token) {
|
|
88
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-change";
|
|
93
|
+
const rawToken = this.decryptFn ? this.decryptFn(token) : token;
|
|
94
|
+
const decoded = jwt.verify(rawToken, JWT_SECRET);
|
|
95
|
+
const uid = decoded.sub;
|
|
96
|
+
if (this.userLoader) {
|
|
97
|
+
const user = await this.userLoader(uid);
|
|
98
|
+
if (!user) {
|
|
99
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
req.user = {
|
|
103
|
+
id: user.id,
|
|
104
|
+
roles: user.roles,
|
|
105
|
+
permissions: user.permissions
|
|
106
|
+
};
|
|
107
|
+
const store = asyncLocalStorage.getStore();
|
|
108
|
+
if (store) store.user = user;
|
|
109
|
+
} else req.user = {
|
|
110
|
+
id: uid,
|
|
111
|
+
roles: decoded.roles,
|
|
112
|
+
permissions: decoded.permissions
|
|
113
|
+
};
|
|
114
|
+
next();
|
|
115
|
+
} catch {
|
|
116
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
toHandler() {
|
|
120
|
+
return this.handle.bind(this);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var AuthorizeByStatusMiddleware = class {
|
|
124
|
+
handle(req, res, next) {
|
|
125
|
+
const user = req.user;
|
|
126
|
+
if (!user) {
|
|
127
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (typeof user.isActive === "function" && !user.isActive()) {
|
|
131
|
+
res.status(401).json({ message: "Account Inactive" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (user.status && user.status !== "active") {
|
|
135
|
+
res.status(401).json({ message: "Account Inactive" });
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
next();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var ErrorHandlerMiddleware = class {
|
|
142
|
+
handle(err, _req, res, _next) {
|
|
143
|
+
if (res.headersSent) return;
|
|
144
|
+
if (err instanceof ValidationError) {
|
|
145
|
+
res.status(422).json({
|
|
146
|
+
success: false,
|
|
147
|
+
errors: err.errors,
|
|
148
|
+
messages: err.messages,
|
|
149
|
+
message: err.message
|
|
150
|
+
});
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const status = typeof err.status === "number" && err.status >= 400 && err.status < 600 ? err.status : 500;
|
|
154
|
+
const payload = {
|
|
155
|
+
success: false,
|
|
156
|
+
message: err.message || "Internal Server Error"
|
|
157
|
+
};
|
|
158
|
+
if (err.code) payload.code = err.code;
|
|
159
|
+
if (err.errors && typeof err.errors === "object") payload.errors = err.errors;
|
|
160
|
+
if (process.env.NODE_ENV !== "production" && err.stack) payload.stack = err.stack.split("\n").map((l) => l.trim());
|
|
161
|
+
res.status(status).json(payload);
|
|
162
|
+
}
|
|
163
|
+
toHandler() {
|
|
164
|
+
return this.handle.bind(this);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function authorizeRoles(...roles) {
|
|
168
|
+
return (req, res, next) => {
|
|
169
|
+
const userRoles = req.user?.roles || [];
|
|
170
|
+
if (!roles.some((r) => userRoles.includes(r))) {
|
|
171
|
+
res.status(403).json({ message: "Forbidden" });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
next();
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function authorizePermissions(...perms) {
|
|
178
|
+
return (req, res, next) => {
|
|
179
|
+
const userPerms = req.user?.permissions || [];
|
|
180
|
+
if (!perms.some((p) => userPerms.includes(p))) {
|
|
181
|
+
res.status(403).json({ message: "Forbidden" });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
next();
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const asyncContextMiddleware = new AsyncContextMiddleware();
|
|
188
|
+
const requestLoggerMiddleware = new RequestLoggerMiddleware();
|
|
189
|
+
const validatorMiddleware = new ValidatorMiddleware();
|
|
190
|
+
const responseExtenderMiddleware = new ResponseExtenderMiddleware();
|
|
191
|
+
const authorizeByStatusMiddleware = new AuthorizeByStatusMiddleware();
|
|
192
|
+
const errorHandlerMiddleware = new ErrorHandlerMiddleware();
|
|
193
|
+
const asyncContext = (req, res, next) => asyncContextMiddleware.handle(req, res, next);
|
|
194
|
+
const requestLogger = (req, res, next) => requestLoggerMiddleware.handle(req, res, next);
|
|
195
|
+
const validatorAttach = (req, res, next) => validatorMiddleware.handle(req, res, next);
|
|
196
|
+
const responseExtender = (req, res, next) => responseExtenderMiddleware.handle(req, res, next);
|
|
197
|
+
const authorizeByStatus = (req, res, next) => authorizeByStatusMiddleware.handle(req, res, next);
|
|
198
|
+
const errorHandler = (err, req, res, next) => errorHandlerMiddleware.handle(err, req, res, next);
|
|
199
|
+
//#endregion
|
|
200
|
+
export { AsyncContextMiddleware, AuthMiddleware, AuthorizeByStatusMiddleware, ErrorHandlerMiddleware, RequestLoggerMiddleware, ResponseExtenderMiddleware, ValidatorMiddleware, asyncContext, asyncContextMiddleware, asyncLocalStorage, authorizeByStatus, authorizeByStatusMiddleware, authorizePermissions, authorizeRoles, errorHandler, errorHandlerMiddleware, requestLogger, requestLoggerMiddleware, responseExtender, responseExtenderMiddleware, validatorAttach, validatorMiddleware };
|
|
201
|
+
|
|
202
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lara-node/middlewares",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Predefined class-based middleware for Lara-Node applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"jsonwebtoken": "^9.0.2",
|
|
19
|
+
"@lara-node/validator": "0.1.1"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/express": "^5.0.6",
|
|
23
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
24
|
+
"@types/node": "^24.12.2",
|
|
25
|
+
"rimraf": "^6.0.1",
|
|
26
|
+
"tsdown": "^0.12.9",
|
|
27
|
+
"typescript": "^5.9.3"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"express": "^5.2.1"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsdown src/index.ts --format esm --out-dir dist --dts",
|
|
34
|
+
"dev": "tsdown src/index.ts --format esm --out-dir dist --dts --watch",
|
|
35
|
+
"clean": "rimraf dist",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
}
|
|
38
|
+
}
|