@increase21/simplenodejs 1.0.15 → 1.0.17
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 +12 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/router.d.ts +1 -1
- package/dist/router.js +5 -4
- package/dist/server.js +4 -4
- package/dist/typings/general.d.ts +5 -5
- package/dist/utils/helpers.d.ts +3 -10
- package/dist/utils/helpers.js +19 -155
- package/dist/utils/simpleController.js +7 -8
- package/dist/utils/simpleMiddleware.d.ts +8 -0
- package/dist/utils/simpleMiddleware.js +144 -0
- package/dist/utils/simplePlugins.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,9 +190,20 @@ app.use(async (req, res, next) => {
|
|
|
190
190
|
Registers a global error handler.
|
|
191
191
|
|
|
192
192
|
### Signature
|
|
193
|
+
app.useError() registers global error-handling middleware.
|
|
194
|
+
It catches all errors thrown anywhere in the request lifecycle — including:
|
|
195
|
+
• Errors thrown inside middlewares
|
|
196
|
+
• Errors thrown inside controllers / route handlers
|
|
197
|
+
• Async errors (throw or rejected promises)
|
|
198
|
+
• Validation errors
|
|
199
|
+
• Custom application errors
|
|
200
|
+
|
|
201
|
+
This gives you a single, centralized place to log, format, and return consistent error responses across your entire system.
|
|
193
202
|
|
|
194
203
|
```ts
|
|
195
|
-
(err
|
|
204
|
+
app.useError((err, req, res, next) => {
|
|
205
|
+
// handle error
|
|
206
|
+
});
|
|
196
207
|
```
|
|
197
208
|
|
|
198
209
|
---
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { SimpleNodeJsController } from "./utils/simpleController";
|
|
2
2
|
export { CreateSimpleJsHttpServer } from "./server";
|
|
3
|
-
export { SetRequestCORS, SetRateLimiter, SetBodyParser } from "./utils/
|
|
3
|
+
export { SetRequestCORS, SetRateLimiter, SetBodyParser } from "./utils/simpleMiddleware";
|
|
4
4
|
export * from "./utils/simplePlugins";
|
|
5
5
|
export type { SimpleJsPrivateMethodProps } from "./typings/general";
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,8 @@ var simpleController_1 = require("./utils/simpleController");
|
|
|
19
19
|
Object.defineProperty(exports, "SimpleNodeJsController", { enumerable: true, get: function () { return simpleController_1.SimpleNodeJsController; } });
|
|
20
20
|
var server_1 = require("./server");
|
|
21
21
|
Object.defineProperty(exports, "CreateSimpleJsHttpServer", { enumerable: true, get: function () { return server_1.CreateSimpleJsHttpServer; } });
|
|
22
|
-
var
|
|
23
|
-
Object.defineProperty(exports, "SetRequestCORS", { enumerable: true, get: function () { return
|
|
24
|
-
Object.defineProperty(exports, "SetRateLimiter", { enumerable: true, get: function () { return
|
|
25
|
-
Object.defineProperty(exports, "SetBodyParser", { enumerable: true, get: function () { return
|
|
22
|
+
var simpleMiddleware_1 = require("./utils/simpleMiddleware");
|
|
23
|
+
Object.defineProperty(exports, "SetRequestCORS", { enumerable: true, get: function () { return simpleMiddleware_1.SetRequestCORS; } });
|
|
24
|
+
Object.defineProperty(exports, "SetRateLimiter", { enumerable: true, get: function () { return simpleMiddleware_1.SetRateLimiter; } });
|
|
25
|
+
Object.defineProperty(exports, "SetBodyParser", { enumerable: true, get: function () { return simpleMiddleware_1.SetBodyParser; } });
|
|
26
26
|
__exportStar(require("./utils/simplePlugins"), exports);
|
package/dist/router.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { RequestObject, ResponseObject } from "./typings/general";
|
|
|
2
2
|
import { SimpleJsControllerMeta } from "./typings/simpletypes";
|
|
3
3
|
export declare function loadControllers(root?: string): Map<string, SimpleJsControllerMeta>;
|
|
4
4
|
export declare function setControllersDir(dir: string): void;
|
|
5
|
-
export declare function route(req: RequestObject, res: ResponseObject
|
|
5
|
+
export declare function route(req: RequestObject, res: ResponseObject): Promise<void>;
|
package/dist/router.js
CHANGED
|
@@ -9,6 +9,7 @@ exports.route = route;
|
|
|
9
9
|
// router.ts
|
|
10
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const helpers_1 = require("./utils/helpers");
|
|
12
13
|
let controllers = new Map();
|
|
13
14
|
function loadControllers(root = "controllers") {
|
|
14
15
|
const base = node_path_1.default.resolve(process.cwd(), root);
|
|
@@ -42,7 +43,7 @@ function loadControllers(root = "controllers") {
|
|
|
42
43
|
function setControllersDir(dir) {
|
|
43
44
|
controllers = loadControllers(dir);
|
|
44
45
|
}
|
|
45
|
-
async function route(req, res
|
|
46
|
+
async function route(req, res) {
|
|
46
47
|
let parts = req._end_point_path || [];
|
|
47
48
|
let controllerPath = (parts.length > 2 ? "/" + parts.slice(0, 2).join("/") : `/${parts.join("/")}`).toLocaleLowerCase();
|
|
48
49
|
let methodName = parts.length > 2 ? parts[2] : "index";
|
|
@@ -50,7 +51,7 @@ async function route(req, res, controllersDir) {
|
|
|
50
51
|
const meta = controllers.get(controllerPath);
|
|
51
52
|
//if the controller is not available or not found
|
|
52
53
|
if (!meta || !meta.name || !meta.Controller)
|
|
53
|
-
return
|
|
54
|
+
return (0, helpers_1.throwHttpError)(404, "The requested resource does not exist");
|
|
54
55
|
const ControllerClass = meta.Controller;
|
|
55
56
|
const controller = new ControllerClass();
|
|
56
57
|
//Update the method name to the framework pathen
|
|
@@ -63,12 +64,12 @@ async function route(req, res, controllersDir) {
|
|
|
63
64
|
id = parts.slice(2);
|
|
64
65
|
}
|
|
65
66
|
else {
|
|
66
|
-
return
|
|
67
|
+
return (0, helpers_1.throwHttpError)(404, "The requested resource does not exist");
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
//if the data require params but there's no matching params
|
|
70
71
|
if (id && id.length && (!controller[methodName].length || controller[methodName].length < id.length)) {
|
|
71
|
-
return
|
|
72
|
+
return (0, helpers_1.throwHttpError)(404, "The requested resource does not exist");
|
|
72
73
|
}
|
|
73
74
|
//bind the controller to use the global properties
|
|
74
75
|
controller.__bindContext({ req, res });
|
package/dist/server.js
CHANGED
|
@@ -42,10 +42,10 @@ const CreateSimpleJsHttpServer = (handler, opts) => {
|
|
|
42
42
|
if (opts?.controllersDir)
|
|
43
43
|
(0, router_1.setControllersDir)(opts.controllersDir);
|
|
44
44
|
const server = http_1.default.createServer(async (req, res) => {
|
|
45
|
-
extension(req, res);
|
|
46
|
-
const run = (0, helpers_1.composeWithError)(middlewares, errorMiddlewares);
|
|
47
45
|
try {
|
|
48
|
-
|
|
46
|
+
extension(req, res);
|
|
47
|
+
const run = (0, helpers_1.composeWithError)(middlewares);
|
|
48
|
+
// handler && await handler(req, res)
|
|
49
49
|
if (res.writableEnded)
|
|
50
50
|
return;
|
|
51
51
|
await run(req, res);
|
|
@@ -62,7 +62,7 @@ const CreateSimpleJsHttpServer = (handler, opts) => {
|
|
|
62
62
|
// 2. Safe HTTP response fallback
|
|
63
63
|
if (!res.headersSent) {
|
|
64
64
|
res.statusCode = 503;
|
|
65
|
-
res.end("Service unavailable at the moment");
|
|
65
|
+
res.end("No Error Handlers: Service unavailable at the moment");
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import http, { IncomingMessage, ServerResponse } from "http";
|
|
2
|
-
export type Next = () => Promise<
|
|
3
|
-
export type Plugin = (app: SimpleJsServer, opts?: any) => Promise<
|
|
2
|
+
export type Next = () => Promise<any> | void;
|
|
3
|
+
export type Plugin = (app: SimpleJsServer, opts?: any) => Promise<any> | void;
|
|
4
4
|
export type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
5
5
|
export type ObjectPayload = {
|
|
6
6
|
[key: string]: any;
|
|
7
7
|
};
|
|
8
|
-
export type Middleware = (req: RequestObject, res: ResponseObject, next: () => Promise<
|
|
9
|
-
export type ErrorMiddleware = (err: any, req: RequestObject, res: ResponseObject, next: Next) => Promise<
|
|
8
|
+
export type Middleware = (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void, errorHandler?: () => void) => Promise<any> | void;
|
|
9
|
+
export type ErrorMiddleware = (err: any, req: RequestObject, res: ResponseObject, next: Next) => Promise<boolean> | void;
|
|
10
10
|
export interface SimpleJsServer extends http.Server {
|
|
11
11
|
use(mw: Middleware): Promise<any> | void;
|
|
12
12
|
useError: (mw: ErrorMiddleware) => void;
|
|
13
|
-
registerPlugin: (plugin: Plugin) => Promise<void
|
|
13
|
+
registerPlugin: (plugin: Plugin) => Promise<any> | void;
|
|
14
14
|
_environment: 'dev' | 'stag' | 'live';
|
|
15
15
|
}
|
|
16
16
|
export interface RequestObject extends IncomingMessage {
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
import { RequestObject, ResponseObject } from "../typings/general";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare function SetRequestCORS(opts: {
|
|
5
|
-
name: string;
|
|
6
|
-
value: string;
|
|
7
|
-
}[]): (req: RequestObject, res: ResponseObject, next: any) => Promise<void>;
|
|
8
|
-
export declare function SetRateLimiter(opts: SimpleJSRateLimitType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
|
|
9
|
-
export declare function SetBodyParser(opts: SimpleJSBodyParseType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
|
|
10
|
-
export declare function composeWithError(middlewares: Middleware[], errorMiddlewares: ErrorMiddleware[]): (req: RequestObject, res: ResponseObject) => Promise<void>;
|
|
1
|
+
import { Middleware, RequestObject, ResponseObject } from "../typings/general";
|
|
2
|
+
export declare function composeWithError(middlewares: Middleware[]): (req: RequestObject, res: ResponseObject) => Promise<void>;
|
|
3
|
+
export declare function throwHttpError(code: number, message: string): void;
|
package/dist/utils/helpers.js
CHANGED
|
@@ -1,169 +1,33 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SetRequestCORS = SetRequestCORS;
|
|
7
|
-
exports.SetRateLimiter = SetRateLimiter;
|
|
8
|
-
exports.SetBodyParser = SetBodyParser;
|
|
9
3
|
exports.composeWithError = composeWithError;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
function SetRequestCORS(opts) {
|
|
13
|
-
return async (req, res, next) => {
|
|
14
|
-
const defaults = {
|
|
15
|
-
"Access-Control-Allow-Origin": "*",
|
|
16
|
-
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
17
|
-
"Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH",
|
|
18
|
-
"X-Content-Type-Options": "nosniff",
|
|
19
|
-
"X-Frame-Options": "DENY",
|
|
20
|
-
"Referrer-Policy": "no-referrer",
|
|
21
|
-
"Content-Security-Policy": "default-src 'none'",
|
|
22
|
-
};
|
|
23
|
-
let merged = { ...defaults };
|
|
24
|
-
for (const { name, value } of opts) {
|
|
25
|
-
merged[name] = value; // overrides defaults if same key
|
|
26
|
-
}
|
|
27
|
-
//allow credentials only when origin specified
|
|
28
|
-
if (merged["Access-Control-Allow-Credentials"] === "true") {
|
|
29
|
-
const origin = req.headers.origin;
|
|
30
|
-
res.status(403).end();
|
|
31
|
-
if (!origin)
|
|
32
|
-
return;
|
|
33
|
-
merged["Access-Control-Allow-Origin"] = origin;
|
|
34
|
-
}
|
|
35
|
-
for (const [key, value] of Object.entries(merged)) {
|
|
36
|
-
res.setHeader(key, value);
|
|
37
|
-
}
|
|
38
|
-
if (req.method === "OPTIONS") {
|
|
39
|
-
res.status(204).end();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
await next();
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
// core/rateLimit.ts
|
|
46
|
-
function SetRateLimiter(opts) {
|
|
47
|
-
const store = new Map();
|
|
48
|
-
const timer = setInterval(() => {
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
for (const [k, v] of store) {
|
|
51
|
-
if (now - v.ts > opts.windowMs * 2)
|
|
52
|
-
store.delete(k);
|
|
53
|
-
}
|
|
54
|
-
}, opts.windowMs);
|
|
55
|
-
timer.unref();
|
|
56
|
-
return async (req, res, next) => {
|
|
57
|
-
const rawIp = req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown";
|
|
58
|
-
const ip = Array.isArray(rawIp) ? rawIp[0] : String(rawIp).split(",")[0].trim();
|
|
59
|
-
const key = String(opts.keyGenerator?.(req) || ip || "unknown");
|
|
60
|
-
const now = Date.now();
|
|
61
|
-
const entry = store.get(key) || { count: 0, ts: now };
|
|
62
|
-
if (now - entry.ts > opts.windowMs) {
|
|
63
|
-
entry.count = 0;
|
|
64
|
-
entry.ts = now;
|
|
65
|
-
}
|
|
66
|
-
entry.count++;
|
|
67
|
-
store.set(key, entry);
|
|
68
|
-
if (entry.count > opts.max) {
|
|
69
|
-
res.setHeader("Retry-After", Math.ceil(opts.windowMs / 1000));
|
|
70
|
-
res.status(429).json({ error: "Too Many Requests" });
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
await next();
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
// core/body.ts
|
|
77
|
-
function SetBodyLimit(limit = "1mb") {
|
|
78
|
-
if (typeof limit === "number")
|
|
79
|
-
return limit;
|
|
80
|
-
const match = /^(\d+)(kb|mb)?$/i.exec(limit);
|
|
81
|
-
if (!match)
|
|
82
|
-
return 1024 * 1024;
|
|
83
|
-
const n = parseInt(match[1], 10);
|
|
84
|
-
const unit = match[2]?.toLowerCase();
|
|
85
|
-
if (unit === "kb")
|
|
86
|
-
return n * 1024;
|
|
87
|
-
if (unit === "mb")
|
|
88
|
-
return n * 1024 * 1024;
|
|
89
|
-
return n;
|
|
90
|
-
}
|
|
91
|
-
function SetBodyParser(opts) {
|
|
92
|
-
const maxSize = SetBodyLimit(opts.limit);
|
|
93
|
-
return async (req, res, next) => {
|
|
94
|
-
return new Promise(resolve => {
|
|
95
|
-
let size = 0;
|
|
96
|
-
let body = "";
|
|
97
|
-
req.on("data", chunk => {
|
|
98
|
-
size += chunk.length;
|
|
99
|
-
if (maxSize && size > maxSize) {
|
|
100
|
-
if (!res.writableEnded) {
|
|
101
|
-
res.status(413).json({ error: "Payload too large" });
|
|
102
|
-
}
|
|
103
|
-
req.destroy();
|
|
104
|
-
req.socket.destroy();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
//add the body
|
|
108
|
-
body += chunk;
|
|
109
|
-
});
|
|
110
|
-
req.on("end", () => {
|
|
111
|
-
if (res.writableEnded)
|
|
112
|
-
return resolve();
|
|
113
|
-
try {
|
|
114
|
-
if (body && !["application/text", "application/media"].includes(req.headers.accept)) {
|
|
115
|
-
req.body = JSON.parse(body);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
req.body = body;
|
|
119
|
-
}
|
|
120
|
-
//parse query
|
|
121
|
-
if (req.query) {
|
|
122
|
-
req.query = JSON.parse(JSON.stringify(node_querystring_1.default.parse(req.query)));
|
|
123
|
-
}
|
|
124
|
-
resolve(next());
|
|
125
|
-
}
|
|
126
|
-
catch (e) {
|
|
127
|
-
res.status(400).json({ error: "Invalid request body" });
|
|
128
|
-
return resolve();
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
req.on("error", () => {
|
|
132
|
-
if (!res.writableEnded) {
|
|
133
|
-
res.status(400).json({ error: "Request stream error" });
|
|
134
|
-
}
|
|
135
|
-
resolve();
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function composeWithError(middlewares, errorMiddlewares) {
|
|
4
|
+
exports.throwHttpError = throwHttpError;
|
|
5
|
+
function composeWithError(middlewares) {
|
|
141
6
|
return async function (req, res) {
|
|
142
7
|
let idx = -1;
|
|
143
8
|
async function dispatch(i) {
|
|
144
9
|
if (i <= idx)
|
|
145
10
|
throw new Error("next() called twice");
|
|
146
11
|
idx = i;
|
|
12
|
+
if (res.writableEnded)
|
|
13
|
+
return;
|
|
147
14
|
const fn = middlewares[i];
|
|
148
15
|
if (!fn)
|
|
149
16
|
return;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (!res.writableEnded) {
|
|
161
|
-
res.status(500).json({ error: "Unhandled error", detail: String(err) });
|
|
162
|
-
}
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
await fn(err, req, res, () => dispatchError(err, i + 1));
|
|
166
|
-
}
|
|
167
|
-
await dispatch(0);
|
|
17
|
+
let called = false;
|
|
18
|
+
const next = async () => {
|
|
19
|
+
if (called)
|
|
20
|
+
throw new Error("next() called multiple times in the same middleware");
|
|
21
|
+
called = true;
|
|
22
|
+
return dispatch(i + 1);
|
|
23
|
+
};
|
|
24
|
+
return await fn(req, res, next);
|
|
25
|
+
}
|
|
26
|
+
return await dispatch(0);
|
|
168
27
|
};
|
|
169
28
|
}
|
|
29
|
+
function throwHttpError(code, message) {
|
|
30
|
+
const error = new Error(message);
|
|
31
|
+
error.toJSON = () => ({ code, error: message });
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SimpleNodeJsController = void 0;
|
|
4
|
+
const helpers_1 = require("./helpers");
|
|
4
5
|
class SimpleNodeJsController {
|
|
5
6
|
/** framework-internal method */
|
|
6
7
|
__bindContext(ctx) {
|
|
@@ -16,17 +17,15 @@ class SimpleNodeJsController {
|
|
|
16
17
|
RunRequest(handlers, params) {
|
|
17
18
|
const method = this.req.method?.toLowerCase();
|
|
18
19
|
if (!method)
|
|
19
|
-
return
|
|
20
|
+
return (0, helpers_1.throwHttpError)(400, "Invalid HTTP Method");
|
|
20
21
|
const runFn = handlers[method];
|
|
21
22
|
if (typeof runFn !== "function")
|
|
22
|
-
return
|
|
23
|
+
return (0, helpers_1.throwHttpError)(405, "Method Not Allowed");
|
|
23
24
|
// ID validation rules
|
|
24
|
-
if (params && params.id && (!params.idMethod || !params.idMethod[method]))
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return this.res.status(404).json({ code: 404, error: "Resource not found" });
|
|
29
|
-
}
|
|
25
|
+
if (params && params.id && (!params.idMethod || !params.idMethod[method]))
|
|
26
|
+
return (0, helpers_1.throwHttpError)(404, "Resource not found");
|
|
27
|
+
if (params && params.idMethod?.[method] === "required" && !params.id)
|
|
28
|
+
return (0, helpers_1.throwHttpError)(404, "Resource not found");
|
|
30
29
|
return runFn({
|
|
31
30
|
...(params || {}), req: this.req,
|
|
32
31
|
res: this.res, query: this.query,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RequestObject, ResponseObject } from "../typings/general";
|
|
2
|
+
import { SimpleJSBodyParseType, SimpleJSRateLimitType } from "../typings/simpletypes";
|
|
3
|
+
export declare function SetRequestCORS(opts: {
|
|
4
|
+
name: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}[]): (req: RequestObject, res: ResponseObject, next: any) => Promise<void>;
|
|
7
|
+
export declare function SetRateLimiter(opts: SimpleJSRateLimitType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
|
|
8
|
+
export declare function SetBodyParser(opts: SimpleJSBodyParseType): (req: RequestObject, res: ResponseObject, next: () => Promise<any> | void) => Promise<void>;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SetRequestCORS = SetRequestCORS;
|
|
7
|
+
exports.SetRateLimiter = SetRateLimiter;
|
|
8
|
+
exports.SetBodyParser = SetBodyParser;
|
|
9
|
+
const node_querystring_1 = __importDefault(require("node:querystring"));
|
|
10
|
+
const helpers_1 = require("./helpers");
|
|
11
|
+
// core/cors.ts
|
|
12
|
+
function SetRequestCORS(opts) {
|
|
13
|
+
return async (req, res, next) => {
|
|
14
|
+
const defaults = {
|
|
15
|
+
"Access-Control-Allow-Origin": "*",
|
|
16
|
+
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization",
|
|
17
|
+
"Access-Control-Allow-Methods": "GET, POST, DELETE, PUT, PATCH",
|
|
18
|
+
"X-Content-Type-Options": "nosniff",
|
|
19
|
+
"X-Frame-Options": "DENY",
|
|
20
|
+
"Referrer-Policy": "no-referrer",
|
|
21
|
+
"Content-Security-Policy": "default-src 'none'",
|
|
22
|
+
};
|
|
23
|
+
let merged = { ...defaults };
|
|
24
|
+
for (const { name, value } of opts) {
|
|
25
|
+
merged[name] = value; // overrides defaults if same key
|
|
26
|
+
}
|
|
27
|
+
//allow credentials only when origin specified
|
|
28
|
+
if (merged["Access-Control-Allow-Credentials"] === "true") {
|
|
29
|
+
const origin = req.headers.origin;
|
|
30
|
+
if (!origin) {
|
|
31
|
+
(0, helpers_1.throwHttpError)(403, "CORS Error: Origin header is required when Access-Control-Allow-Credentials is true");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
merged["Access-Control-Allow-Origin"] = origin;
|
|
35
|
+
}
|
|
36
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
37
|
+
res.setHeader(key, value);
|
|
38
|
+
}
|
|
39
|
+
if (req.method === "OPTIONS") {
|
|
40
|
+
res.status(204).end();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await next();
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
// core/rateLimit.ts
|
|
47
|
+
function SetRateLimiter(opts) {
|
|
48
|
+
const store = new Map();
|
|
49
|
+
const timer = setInterval(() => {
|
|
50
|
+
const now = Date.now();
|
|
51
|
+
for (const [k, v] of store) {
|
|
52
|
+
if (now - v.ts > opts.windowMs * 2)
|
|
53
|
+
store.delete(k);
|
|
54
|
+
}
|
|
55
|
+
}, opts.windowMs);
|
|
56
|
+
timer.unref();
|
|
57
|
+
return async (req, res, next) => {
|
|
58
|
+
const rawIp = req.headers["x-forwarded-for"] || req.socket.remoteAddress || "unknown";
|
|
59
|
+
const ip = Array.isArray(rawIp) ? rawIp[0] : String(rawIp).split(",")[0].trim();
|
|
60
|
+
const key = String(opts.keyGenerator?.(req) || ip || "unknown");
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const entry = store.get(key) || { count: 0, ts: now };
|
|
63
|
+
if (now - entry.ts > opts.windowMs) {
|
|
64
|
+
entry.count = 0;
|
|
65
|
+
entry.ts = now;
|
|
66
|
+
}
|
|
67
|
+
entry.count++;
|
|
68
|
+
store.set(key, entry);
|
|
69
|
+
if (entry.count > opts.max) {
|
|
70
|
+
res.setHeader("Retry-After", Math.ceil(opts.windowMs / 1000));
|
|
71
|
+
if (!res.writableEnded)
|
|
72
|
+
(0, helpers_1.throwHttpError)(429, "Too Many Requests");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await next();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// core/body.ts
|
|
79
|
+
function SetBodyLimit(limit = "1mb") {
|
|
80
|
+
if (typeof limit === "number")
|
|
81
|
+
return limit;
|
|
82
|
+
const match = /^(\d+)(kb|mb)?$/i.exec(limit);
|
|
83
|
+
if (!match)
|
|
84
|
+
return 1024 * 1024;
|
|
85
|
+
const n = parseInt(match[1], 10);
|
|
86
|
+
const unit = match[2]?.toLowerCase();
|
|
87
|
+
if (unit === "kb")
|
|
88
|
+
return n * 1024;
|
|
89
|
+
if (unit === "mb")
|
|
90
|
+
return n * 1024 * 1024;
|
|
91
|
+
return n;
|
|
92
|
+
}
|
|
93
|
+
//For
|
|
94
|
+
function SetBodyParser(opts) {
|
|
95
|
+
const maxSize = SetBodyLimit(opts.limit);
|
|
96
|
+
return (req, res, next) => new Promise((resolve, reject) => {
|
|
97
|
+
//get the content type of the request
|
|
98
|
+
const contentType = req.headers["content-type"] || "";
|
|
99
|
+
// Skip multipart/form-data (file uploads)
|
|
100
|
+
if (contentType.includes("multipart/form-data"))
|
|
101
|
+
return resolve(next());
|
|
102
|
+
let size = 0;
|
|
103
|
+
let body = "";
|
|
104
|
+
req.on("data", chunk => {
|
|
105
|
+
size += chunk.length;
|
|
106
|
+
if (maxSize && size > maxSize) {
|
|
107
|
+
reject({ code: 413, error: "Payload Too Large" });
|
|
108
|
+
if (!res.writableEnded)
|
|
109
|
+
res.status(413).end("Payload Too Large");
|
|
110
|
+
req.destroy();
|
|
111
|
+
req.socket.destroy();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
//add the body
|
|
115
|
+
body += chunk;
|
|
116
|
+
});
|
|
117
|
+
req.on("end", () => {
|
|
118
|
+
if (res.writableEnded)
|
|
119
|
+
return resolve();
|
|
120
|
+
try {
|
|
121
|
+
if (body && contentType.includes("application/json")) {
|
|
122
|
+
req.body = JSON.parse(body);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
req.body = body;
|
|
126
|
+
}
|
|
127
|
+
//parse query
|
|
128
|
+
if (req.query) {
|
|
129
|
+
req.query = JSON.parse(JSON.stringify(node_querystring_1.default.parse(req.query)));
|
|
130
|
+
}
|
|
131
|
+
resolve(next());
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
if (!res.writableEnded)
|
|
135
|
+
reject({ code: 400, error: "Invalid request body" });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
req.on("error", () => {
|
|
139
|
+
if (!res.writableEnded)
|
|
140
|
+
reject({ code: 400, error: "Request stream error" });
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SimpleJsSecurityPlugin = SimpleJsSecurityPlugin;
|
|
4
|
-
const
|
|
4
|
+
const simpleMiddleware_1 = require("./simpleMiddleware");
|
|
5
5
|
function SimpleJsSecurityPlugin(app, opts) {
|
|
6
|
-
opts.cors && app.use((0,
|
|
7
|
-
opts.rateLimit && app.use((0,
|
|
6
|
+
opts.cors && app.use((0, simpleMiddleware_1.SetRequestCORS)(opts.cors || []));
|
|
7
|
+
opts.rateLimit && app.use((0, simpleMiddleware_1.SetRateLimiter)(opts.rateLimit || { windowMs: 1000, max: 100 }));
|
|
8
8
|
}
|
package/package.json
CHANGED