quantum-flow 1.3.10 → 1.5.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 +22 -15
- package/dist/app/aws/lambda.d.ts +0 -3
- package/dist/app/aws/lambda.js +25 -112
- package/dist/app/aws/utils/helpers.d.ts +4 -0
- package/dist/app/aws/utils/helpers.js +83 -0
- package/dist/app/aws/utils/index.d.ts +3 -0
- package/dist/app/aws/utils/index.js +19 -0
- package/dist/app/aws/utils/request.d.ts +23 -0
- package/dist/app/aws/utils/request.js +96 -0
- package/dist/app/aws/utils/response.d.ts +14 -0
- package/dist/app/aws/utils/response.js +37 -0
- package/dist/app/http/Application.d.ts +1 -1
- package/dist/app/http/Application.js +36 -21
- package/dist/app/http/decorators.js +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +3 -1
- package/dist/core/Controller.d.ts +6 -25
- package/dist/core/Controller.js +84 -118
- package/dist/core/Endpoint.js +1 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/utils/index.d.ts +0 -2
- package/dist/core/utils/index.js +0 -2
- package/dist/examples/app.d.ts +0 -1
- package/dist/examples/app.js +70 -5
- package/dist/examples/controllers/user.js +44 -8
- package/dist/examples/controllers/userMetadata.d.ts +1 -1
- package/dist/examples/controllers/userMetadata.js +59 -9
- package/dist/middlewares/catch.d.ts +2 -0
- package/dist/middlewares/catch.js +10 -0
- package/dist/middlewares/cors.d.ts +2 -0
- package/dist/middlewares/cors.js +21 -0
- package/dist/middlewares/index.d.ts +5 -0
- package/dist/middlewares/index.js +21 -0
- package/dist/middlewares/sanitize.d.ts +2 -0
- package/dist/middlewares/sanitize.js +15 -0
- package/dist/{core/utils/helpers.js → middlewares/status.js} +1 -1
- package/dist/middlewares/use.d.ts +2 -0
- package/dist/middlewares/use.js +11 -0
- package/dist/types/common.d.ts +15 -32
- package/dist/types/controller.d.ts +7 -5
- package/dist/types/cors.d.ts +10 -0
- package/dist/types/cors.js +2 -0
- package/dist/types/http.d.ts +4 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +3 -0
- package/dist/types/lambda.d.ts +7 -17
- package/dist/types/multipart.d.ts +8 -0
- package/dist/types/multipart.js +2 -0
- package/dist/types/sanitize.d.ts +8 -0
- package/dist/types/sanitize.js +2 -0
- package/dist/utils/controller.d.ts +27 -3
- package/dist/utils/controller.js +122 -23
- package/dist/utils/cors.d.ts +8 -0
- package/dist/utils/cors.js +127 -0
- package/dist/utils/headers.d.ts +2 -0
- package/dist/utils/headers.js +9 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/multipart.d.ts +2 -8
- package/dist/utils/multipart.js +98 -2
- package/dist/utils/sanitize.d.ts +30 -0
- package/dist/utils/sanitize.js +134 -0
- package/dist/utils/server.js +5 -0
- package/package.json +9 -2
- package/dist/core/utils/middlewares.d.ts +0 -3
- package/dist/core/utils/middlewares.js +0 -22
- /package/dist/{core/utils/helpers.d.ts → middlewares/status.d.ts} +0 -0
package/dist/utils/controller.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getControllerMethods = exports.executeControllerMethod = void 0;
|
|
3
|
+
exports.applyMiddlewaresVsSanitizers = exports.getResponse = exports.NextFN = exports.findRouteInController = exports.getAllMethods = exports.getControllerMethods = exports.executeControllerMethod = void 0;
|
|
4
|
+
const _constants_1 = require("../constants.js");
|
|
4
5
|
const _types_1 = require("../types/index.js");
|
|
5
|
-
const _utils_1 = require("./index.js");
|
|
6
6
|
const WebsocketService_1 = require("../app/http/websocket/WebsocketService");
|
|
7
|
+
const helper_1 = require("./helper");
|
|
8
|
+
const multipart_1 = require("./multipart");
|
|
9
|
+
const sanitize_1 = require("./sanitize");
|
|
7
10
|
const validate_1 = require("./validate");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
let body = payload.body;
|
|
11
|
+
const getBodyAndMultipart = (request) => {
|
|
12
|
+
let body = request.body;
|
|
11
13
|
let multipart;
|
|
12
|
-
if (
|
|
14
|
+
if (multipart_1.MultipartProcessor.isMultipart({ headers: request.headers })) {
|
|
13
15
|
try {
|
|
14
|
-
const { fields, files } =
|
|
15
|
-
body:
|
|
16
|
-
headers:
|
|
17
|
-
isBase64Encoded:
|
|
16
|
+
const { fields, files } = multipart_1.MultipartProcessor.parse({
|
|
17
|
+
body: request.rawBody || request.body,
|
|
18
|
+
headers: request.headers,
|
|
19
|
+
isBase64Encoded: request.isBase64Encoded,
|
|
18
20
|
});
|
|
19
21
|
multipart = files;
|
|
20
22
|
body = fields;
|
|
@@ -26,7 +28,7 @@ const getBodyAndMultipart = (payload) => {
|
|
|
26
28
|
}
|
|
27
29
|
return { multipart, body };
|
|
28
30
|
};
|
|
29
|
-
const executeControllerMethod = async (controller, propertyName,
|
|
31
|
+
const executeControllerMethod = async (controller, propertyName, request, response) => {
|
|
30
32
|
const fn = controller[propertyName];
|
|
31
33
|
if (typeof fn !== 'function')
|
|
32
34
|
return null;
|
|
@@ -34,19 +36,15 @@ const executeControllerMethod = async (controller, propertyName, payload, reques
|
|
|
34
36
|
if (!endpointMeta)
|
|
35
37
|
return null;
|
|
36
38
|
const methodMiddlewares = Reflect.getMetadata(_constants_1.MIDDLEWARES, controller, propertyName) || [];
|
|
37
|
-
for (let
|
|
38
|
-
|
|
39
|
-
const result = await middleware(payload);
|
|
40
|
-
if (result) {
|
|
41
|
-
payload = { ...payload, ...result };
|
|
42
|
-
}
|
|
39
|
+
for (let middleware of methodMiddlewares) {
|
|
40
|
+
await middleware(request, response, exports.NextFN);
|
|
43
41
|
}
|
|
44
42
|
const prototype = Object.getPrototypeOf(controller);
|
|
45
43
|
const paramMetadata = Reflect.getMetadata(_constants_1.PARAM_METADATA_KEY, prototype, propertyName) || [];
|
|
46
44
|
if (paramMetadata.length === 0) {
|
|
47
|
-
return fn.call(controller,
|
|
45
|
+
return fn.call(controller, request, response);
|
|
48
46
|
}
|
|
49
|
-
const { body, multipart } = getBodyAndMultipart(
|
|
47
|
+
const { body, multipart } = getBodyAndMultipart(request);
|
|
50
48
|
const args = [];
|
|
51
49
|
const wsParams = Reflect.getMetadata(_constants_1.WS_SERVICE_KEY, controller, propertyName) || [];
|
|
52
50
|
const totalParams = Math.max(paramMetadata.length ? Math.max(...paramMetadata.map((p) => p.index)) + 1 : 0, wsParams.length ? Math.max(...wsParams.map((p) => p.index)) + 1 : 0);
|
|
@@ -61,13 +59,12 @@ const executeControllerMethod = async (controller, propertyName, payload, reques
|
|
|
61
59
|
args[i] = undefined;
|
|
62
60
|
continue;
|
|
63
61
|
}
|
|
64
|
-
let value = param.name
|
|
62
|
+
let value = param.name
|
|
63
|
+
? request[param.type]?.[param.name]
|
|
64
|
+
: request[param.type];
|
|
65
65
|
if (param.type === 'multipart') {
|
|
66
66
|
value = multipart;
|
|
67
67
|
}
|
|
68
|
-
if (param.type === 'request') {
|
|
69
|
-
value = payload;
|
|
70
|
-
}
|
|
71
68
|
if (param.type === 'request') {
|
|
72
69
|
value = request;
|
|
73
70
|
}
|
|
@@ -110,3 +107,105 @@ const getControllerMethods = (controller) => {
|
|
|
110
107
|
return methods.sort((a, b) => (a.httpMethod === _types_1.HTTP_METHODS.USE ? 1 : -1));
|
|
111
108
|
};
|
|
112
109
|
exports.getControllerMethods = getControllerMethods;
|
|
110
|
+
const getAllMethods = (obj) => {
|
|
111
|
+
let methods = new Set();
|
|
112
|
+
let current = Object.getPrototypeOf(obj);
|
|
113
|
+
while (current && current !== Object.prototype) {
|
|
114
|
+
Object.getOwnPropertyNames(current).forEach((name) => {
|
|
115
|
+
if (name !== 'constructor' && typeof current[name] === 'function') {
|
|
116
|
+
methods.add(name);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
current = Object.getPrototypeOf(current);
|
|
120
|
+
}
|
|
121
|
+
return Array.from(methods);
|
|
122
|
+
};
|
|
123
|
+
exports.getAllMethods = getAllMethods;
|
|
124
|
+
const findRouteInController = (instance, path, route, method) => {
|
|
125
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
126
|
+
const propertyNames = (0, exports.getAllMethods)(instance);
|
|
127
|
+
const matches = [];
|
|
128
|
+
for (const name of propertyNames) {
|
|
129
|
+
if ([
|
|
130
|
+
'constructor',
|
|
131
|
+
'getResponse',
|
|
132
|
+
'routeWalker',
|
|
133
|
+
'getAllMethods',
|
|
134
|
+
'findRouteInController',
|
|
135
|
+
].includes(name))
|
|
136
|
+
continue;
|
|
137
|
+
const endpointMeta = Reflect.getMetadata(_constants_1.ENDPOINT, prototype, name) || [];
|
|
138
|
+
const sanitizers = Reflect.getMetadata(_constants_1.SANITIZE, prototype, name) ?? [];
|
|
139
|
+
if (endpointMeta.length === 0)
|
|
140
|
+
continue;
|
|
141
|
+
const [httpMethod, routePattern, middlewares] = endpointMeta;
|
|
142
|
+
if (httpMethod !== method && httpMethod !== 'USE') {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (httpMethod === 'USE') {
|
|
146
|
+
let useRoute = route.split('/');
|
|
147
|
+
useRoute.pop();
|
|
148
|
+
route = useRoute.join('/');
|
|
149
|
+
}
|
|
150
|
+
const current = [path, routePattern].join('/').replace(/\/+/g, '/');
|
|
151
|
+
const pathParams = (0, helper_1.matchRoute)(current, route);
|
|
152
|
+
if (pathParams) {
|
|
153
|
+
const priority = httpMethod === 'USE' ? 0 : Object.keys(pathParams).length > 0 ? 1 : 2;
|
|
154
|
+
matches.push({
|
|
155
|
+
name,
|
|
156
|
+
pathParams,
|
|
157
|
+
priority,
|
|
158
|
+
cors: Reflect.getMetadata(_constants_1.CORS_METADATA, prototype, name),
|
|
159
|
+
middlewares,
|
|
160
|
+
sanitizers,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
matches.sort((a, b) => b.priority - a.priority);
|
|
165
|
+
return matches[0] || null;
|
|
166
|
+
};
|
|
167
|
+
exports.findRouteInController = findRouteInController;
|
|
168
|
+
const NextFN = (error) => {
|
|
169
|
+
if (error)
|
|
170
|
+
throw { status: error.status ?? 500, message: error.message ?? error };
|
|
171
|
+
};
|
|
172
|
+
exports.NextFN = NextFN;
|
|
173
|
+
const getResponse = async (data) => {
|
|
174
|
+
try {
|
|
175
|
+
let appResponse = await (0, exports.executeControllerMethod)(data.controllerInstance, data.name, data.request, data.response);
|
|
176
|
+
data.response.statusCode = appResponse.status ?? 200;
|
|
177
|
+
const isError = !_constants_1.OK_STATUSES.includes(data.response.statusCode);
|
|
178
|
+
const interceptors = data.interceptors.reverse();
|
|
179
|
+
for (let index = 0; index < interceptors?.length && !isError; index++) {
|
|
180
|
+
const interceptor = interceptors[index];
|
|
181
|
+
appResponse = await interceptor(appResponse, data.request, data.response);
|
|
182
|
+
}
|
|
183
|
+
const propertyName = data.name;
|
|
184
|
+
const prototype = Object.getPrototypeOf(data.controllerInstance);
|
|
185
|
+
const methodOkStatus = Reflect.getMetadata(_constants_1.OK_METADATA_KEY, data.controllerInstance, propertyName);
|
|
186
|
+
if (methodOkStatus) {
|
|
187
|
+
!isError && (data.response.statusCode = methodOkStatus);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const classOkStatus = Reflect.getMetadata(_constants_1.OK_METADATA_KEY, prototype);
|
|
191
|
+
!isError && classOkStatus && (data.response.statusCode = classOkStatus);
|
|
192
|
+
}
|
|
193
|
+
return { status: data.response.statusCode, data: appResponse };
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
exports.getResponse = getResponse;
|
|
200
|
+
const applyMiddlewaresVsSanitizers = async (request, response, functions) => {
|
|
201
|
+
const length = Math.max(functions.sanitizers.length, functions.middlewares.length);
|
|
202
|
+
for (let i = 0; i < length; i++) {
|
|
203
|
+
const mws = functions.middlewares[i] ?? [];
|
|
204
|
+
const sntzs = functions.sanitizers[i] ?? [];
|
|
205
|
+
(0, sanitize_1.sanitizeRequest)(request, sntzs);
|
|
206
|
+
for (let middleware of mws) {
|
|
207
|
+
await middleware(request, response, exports.NextFN);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
exports.applyMiddlewaresVsSanitizers = applyMiddlewaresVsSanitizers;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AppRequest, CORSConfig } from '../types/index.js';
|
|
2
|
+
export declare function handleCORS(req: AppRequest, res: any, config: CORSConfig): {
|
|
3
|
+
permitted: boolean;
|
|
4
|
+
continue: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function isPreflightRequest(req: any): boolean;
|
|
7
|
+
export declare const getCORSConfig: (controller: any, methodName?: string) => CORSConfig | null;
|
|
8
|
+
export declare const getCORSHeaders: (req: any, config: CORSConfig) => Record<string, string>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCORSHeaders = exports.getCORSConfig = void 0;
|
|
4
|
+
exports.handleCORS = handleCORS;
|
|
5
|
+
exports.isPreflightRequest = isPreflightRequest;
|
|
6
|
+
const _constants_1 = require("../constants.js");
|
|
7
|
+
const headers_1 = require("./headers");
|
|
8
|
+
function handleCORS(req, res, config) {
|
|
9
|
+
const origin = (0, headers_1.getOrigin)(req);
|
|
10
|
+
function isOriginAllowed() {
|
|
11
|
+
if (!origin)
|
|
12
|
+
return false;
|
|
13
|
+
if (config.origin === '*')
|
|
14
|
+
return true;
|
|
15
|
+
if (typeof config.origin === 'string')
|
|
16
|
+
return config.origin === origin;
|
|
17
|
+
if (Array.isArray(config.origin))
|
|
18
|
+
return config.origin.includes(origin);
|
|
19
|
+
if (typeof config.origin === 'function')
|
|
20
|
+
return config.origin(origin);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (origin && !isOriginAllowed()) {
|
|
24
|
+
res.statusCode = 403;
|
|
25
|
+
res.setHeader('Content-Type', 'application/json');
|
|
26
|
+
return { permitted: false, continue: false };
|
|
27
|
+
}
|
|
28
|
+
if (req.method === 'OPTIONS') {
|
|
29
|
+
if (origin) {
|
|
30
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
31
|
+
}
|
|
32
|
+
else if (config.origin === '*') {
|
|
33
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
34
|
+
}
|
|
35
|
+
if (config.methods) {
|
|
36
|
+
res.setHeader('Access-Control-Allow-Methods', config.methods.join(', '));
|
|
37
|
+
}
|
|
38
|
+
if (config.allowedHeaders) {
|
|
39
|
+
res.setHeader('Access-Control-Allow-Headers', config.allowedHeaders.join(', '));
|
|
40
|
+
}
|
|
41
|
+
if (config.credentials) {
|
|
42
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
43
|
+
}
|
|
44
|
+
if (config.maxAge) {
|
|
45
|
+
res.setHeader('Access-Control-Max-Age', config.maxAge.toString());
|
|
46
|
+
}
|
|
47
|
+
res.statusCode = config.optionsSuccessStatus || 204;
|
|
48
|
+
return { permitted: true, continue: false };
|
|
49
|
+
}
|
|
50
|
+
if (origin) {
|
|
51
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
52
|
+
if (config.credentials) {
|
|
53
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
54
|
+
}
|
|
55
|
+
if (config.exposedHeaders) {
|
|
56
|
+
res.setHeader('Access-Control-Expose-Headers', config.exposedHeaders.join(', '));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { permitted: true, continue: true };
|
|
60
|
+
}
|
|
61
|
+
function isPreflightRequest(req) {
|
|
62
|
+
return (req.method === 'OPTIONS' && req.headers['access-control-request-method'] && req.headers.origin);
|
|
63
|
+
}
|
|
64
|
+
const getCORSConfig = (controller, methodName) => {
|
|
65
|
+
if (methodName) {
|
|
66
|
+
const methodConfig = Reflect.getMetadata(_constants_1.CORS_METADATA, controller, methodName);
|
|
67
|
+
if (methodConfig) {
|
|
68
|
+
return methodConfig;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (controller && controller.constructor) {
|
|
72
|
+
const classConfig = Reflect.getMetadata(_constants_1.CORS_METADATA, controller.constructor.prototype);
|
|
73
|
+
if (classConfig) {
|
|
74
|
+
return classConfig;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
exports.getCORSConfig = getCORSConfig;
|
|
80
|
+
const getCORSHeaders = (req, config) => {
|
|
81
|
+
const headers = {};
|
|
82
|
+
const origin = req?.headers?.origin || req?.headers?.Origin;
|
|
83
|
+
if (!origin)
|
|
84
|
+
return headers;
|
|
85
|
+
let allowedOrigin = null;
|
|
86
|
+
if (config.origin === '*') {
|
|
87
|
+
allowedOrigin = '*';
|
|
88
|
+
}
|
|
89
|
+
else if (typeof config.origin === 'string') {
|
|
90
|
+
allowedOrigin = config.origin;
|
|
91
|
+
}
|
|
92
|
+
else if (Array.isArray(config.origin)) {
|
|
93
|
+
if (config.origin.includes(origin)) {
|
|
94
|
+
allowedOrigin = origin;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (typeof config.origin === 'function') {
|
|
98
|
+
if (config.origin(origin)) {
|
|
99
|
+
allowedOrigin = origin;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (config.origin === true) {
|
|
103
|
+
allowedOrigin = origin;
|
|
104
|
+
}
|
|
105
|
+
if (allowedOrigin) {
|
|
106
|
+
headers['Access-Control-Allow-Origin'] = allowedOrigin;
|
|
107
|
+
if (config.credentials) {
|
|
108
|
+
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
109
|
+
}
|
|
110
|
+
if (config.exposedHeaders?.length) {
|
|
111
|
+
headers['Access-Control-Expose-Headers'] = config.exposedHeaders.join(', ');
|
|
112
|
+
}
|
|
113
|
+
if (req.method === 'OPTIONS') {
|
|
114
|
+
if (config.methods?.length) {
|
|
115
|
+
headers['Access-Control-Allow-Methods'] = config.methods.join(', ');
|
|
116
|
+
}
|
|
117
|
+
if (config.allowedHeaders?.length) {
|
|
118
|
+
headers['Access-Control-Allow-Headers'] = config.allowedHeaders.join(', ');
|
|
119
|
+
}
|
|
120
|
+
if (config.maxAge) {
|
|
121
|
+
headers['Access-Control-Max-Age'] = config.maxAge.toString();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return headers;
|
|
126
|
+
};
|
|
127
|
+
exports.getCORSHeaders = getCORSHeaders;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getOrigin = void 0;
|
|
4
|
+
const getOrigin = (req) => {
|
|
5
|
+
let originHeader = req.headers.origin || req.headers.Origin || req.requestUrl.origin;
|
|
6
|
+
const origin = (Array.isArray(originHeader) ? originHeader[0] : originHeader);
|
|
7
|
+
return origin;
|
|
8
|
+
};
|
|
9
|
+
exports.getOrigin = getOrigin;
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export * from './controller';
|
|
2
|
+
export * from './cors';
|
|
2
3
|
export * from './endpoint';
|
|
4
|
+
export * from './headers';
|
|
3
5
|
export * from './helper';
|
|
4
6
|
export * from './lambda';
|
|
5
7
|
export * from './multipart';
|
|
6
8
|
export * from './parsers';
|
|
9
|
+
export * from './sanitize';
|
|
7
10
|
export * from './server';
|
|
8
11
|
export * from './transform';
|
|
9
12
|
export * from './validate';
|
package/dist/utils/index.js
CHANGED
|
@@ -15,11 +15,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./controller"), exports);
|
|
18
|
+
__exportStar(require("./cors"), exports);
|
|
18
19
|
__exportStar(require("./endpoint"), exports);
|
|
20
|
+
__exportStar(require("./headers"), exports);
|
|
19
21
|
__exportStar(require("./helper"), exports);
|
|
20
22
|
__exportStar(require("./lambda"), exports);
|
|
21
23
|
__exportStar(require("./multipart"), exports);
|
|
22
24
|
__exportStar(require("./parsers"), exports);
|
|
25
|
+
__exportStar(require("./sanitize"), exports);
|
|
23
26
|
__exportStar(require("./server"), exports);
|
|
24
27
|
__exportStar(require("./transform"), exports);
|
|
25
28
|
__exportStar(require("./validate"), exports);
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
fieldname: string;
|
|
3
|
-
filename: string;
|
|
4
|
-
contentType: string;
|
|
5
|
-
data: Buffer;
|
|
6
|
-
size: number;
|
|
7
|
-
encoding?: string;
|
|
8
|
-
}
|
|
1
|
+
import { MultipartFile } from '../types/index.js';
|
|
9
2
|
export declare class MultipartProcessor {
|
|
10
3
|
static parse(request: any): {
|
|
11
4
|
fields: Record<string, any>;
|
|
12
5
|
files: Record<string, MultipartFile | MultipartFile[]>;
|
|
13
6
|
};
|
|
14
7
|
static isMultipart(request: any): boolean;
|
|
8
|
+
private static getContentType;
|
|
15
9
|
}
|
package/dist/utils/multipart.js
CHANGED
|
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MultipartProcessor = void 0;
|
|
37
|
-
// utils/MultipartProcessor.ts
|
|
38
37
|
const multipart = __importStar(require("parse-multipart-data"));
|
|
39
38
|
class MultipartProcessor {
|
|
40
39
|
static parse(request) {
|
|
@@ -69,10 +68,11 @@ class MultipartProcessor {
|
|
|
69
68
|
parts.forEach((part) => {
|
|
70
69
|
if (part.filename) {
|
|
71
70
|
const fieldName = part.name || 'file';
|
|
71
|
+
const contentType = this.getContentType(part);
|
|
72
72
|
const fileData = {
|
|
73
73
|
fieldname: fieldName,
|
|
74
74
|
filename: part.filename,
|
|
75
|
-
contentType: part.type,
|
|
75
|
+
contentType: contentType ?? part.type,
|
|
76
76
|
data: part.data,
|
|
77
77
|
size: part.data.length,
|
|
78
78
|
encoding: part.encoding,
|
|
@@ -105,5 +105,101 @@ class MultipartProcessor {
|
|
|
105
105
|
const contentType = request.headers?.['content-type'] || request.headers?.['Content-Type'] || '';
|
|
106
106
|
return contentType.startsWith('multipart/form-data');
|
|
107
107
|
}
|
|
108
|
+
static getContentType(part) {
|
|
109
|
+
const filename = part.filename || '';
|
|
110
|
+
const extension = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
111
|
+
const mimeMap = {
|
|
112
|
+
txt: 'text/plain',
|
|
113
|
+
text: 'text/plain',
|
|
114
|
+
log: 'text/plain',
|
|
115
|
+
md: 'text/markdown',
|
|
116
|
+
csv: 'text/csv',
|
|
117
|
+
ts: 'application/typescript',
|
|
118
|
+
tsx: 'application/typescript',
|
|
119
|
+
js: 'application/javascript',
|
|
120
|
+
jsx: 'application/javascript',
|
|
121
|
+
mjs: 'application/javascript',
|
|
122
|
+
cjs: 'application/javascript',
|
|
123
|
+
json: 'application/json',
|
|
124
|
+
xml: 'application/xml',
|
|
125
|
+
yaml: 'application/yaml',
|
|
126
|
+
yml: 'application/yaml',
|
|
127
|
+
toml: 'application/toml',
|
|
128
|
+
php: 'application/x-httpd-php',
|
|
129
|
+
py: 'text/x-python',
|
|
130
|
+
rb: 'text/x-ruby',
|
|
131
|
+
java: 'text/x-java',
|
|
132
|
+
c: 'text/x-c',
|
|
133
|
+
cpp: 'text/x-c++',
|
|
134
|
+
h: 'text/x-c',
|
|
135
|
+
hpp: 'text/x-c++',
|
|
136
|
+
go: 'text/x-go',
|
|
137
|
+
rs: 'text/x-rust',
|
|
138
|
+
swift: 'text/x-swift',
|
|
139
|
+
kt: 'text/x-kotlin',
|
|
140
|
+
kts: 'text/x-kotlin',
|
|
141
|
+
html: 'text/html',
|
|
142
|
+
htm: 'text/html',
|
|
143
|
+
css: 'text/css',
|
|
144
|
+
scss: 'text/x-scss',
|
|
145
|
+
sass: 'text/x-sass',
|
|
146
|
+
less: 'text/x-less',
|
|
147
|
+
jpg: 'image/jpeg',
|
|
148
|
+
jpeg: 'image/jpeg',
|
|
149
|
+
png: 'image/png',
|
|
150
|
+
gif: 'image/gif',
|
|
151
|
+
webp: 'image/webp',
|
|
152
|
+
svg: 'image/svg+xml',
|
|
153
|
+
ico: 'image/x-icon',
|
|
154
|
+
bmp: 'image/bmp',
|
|
155
|
+
tiff: 'image/tiff',
|
|
156
|
+
tif: 'image/tiff',
|
|
157
|
+
avif: 'image/avif',
|
|
158
|
+
heic: 'image/heic',
|
|
159
|
+
heif: 'image/heif',
|
|
160
|
+
pdf: 'application/pdf',
|
|
161
|
+
doc: 'application/msword',
|
|
162
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
163
|
+
xls: 'application/vnd.ms-excel',
|
|
164
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
165
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
166
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
167
|
+
odt: 'application/vnd.oasis.opendocument.text',
|
|
168
|
+
ods: 'application/vnd.oasis.opendocument.spreadsheet',
|
|
169
|
+
odp: 'application/vnd.oasis.opendocument.presentation',
|
|
170
|
+
zip: 'application/zip',
|
|
171
|
+
rar: 'application/vnd.rar',
|
|
172
|
+
'7z': 'application/x-7z-compressed',
|
|
173
|
+
tar: 'application/x-tar',
|
|
174
|
+
gz: 'application/gzip',
|
|
175
|
+
bz2: 'application/x-bzip2',
|
|
176
|
+
mp3: 'audio/mpeg',
|
|
177
|
+
wav: 'audio/wav',
|
|
178
|
+
ogg: 'audio/ogg',
|
|
179
|
+
m4a: 'audio/mp4',
|
|
180
|
+
flac: 'audio/flac',
|
|
181
|
+
aac: 'audio/aac',
|
|
182
|
+
mp4: 'video/mp4',
|
|
183
|
+
mpg: 'video/mpeg',
|
|
184
|
+
mpeg: 'video/mpeg',
|
|
185
|
+
mov: 'video/quicktime',
|
|
186
|
+
avi: 'video/x-msvideo',
|
|
187
|
+
wmv: 'video/x-ms-wmv',
|
|
188
|
+
flv: 'video/x-flv',
|
|
189
|
+
webm: 'video/webm',
|
|
190
|
+
mkv: 'video/x-matroska',
|
|
191
|
+
m4v: 'video/x-m4v',
|
|
192
|
+
bin: 'application/octet-stream',
|
|
193
|
+
exe: 'application/vnd.microsoft.portable-executable',
|
|
194
|
+
dll: 'application/vnd.microsoft.portable-executable',
|
|
195
|
+
deb: 'application/vnd.debian.binary-package',
|
|
196
|
+
rpm: 'application/x-rpm',
|
|
197
|
+
iso: 'application/x-iso9660-image',
|
|
198
|
+
sh: 'application/x-sh',
|
|
199
|
+
bat: 'application/x-msdos-program',
|
|
200
|
+
ps1: 'application/x-powershell',
|
|
201
|
+
};
|
|
202
|
+
return mimeMap[extension];
|
|
203
|
+
}
|
|
108
204
|
}
|
|
109
205
|
exports.MultipartProcessor = MultipartProcessor;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AppRequest, SanitizerConfig } from '../types/index.js';
|
|
2
|
+
import * as Joi from 'joi';
|
|
3
|
+
export declare const SanitizeSchemas: {
|
|
4
|
+
string: {
|
|
5
|
+
trim: () => Joi.StringSchema<string>;
|
|
6
|
+
email: () => Joi.StringSchema<string>;
|
|
7
|
+
name: () => Joi.StringSchema<string>;
|
|
8
|
+
slug: () => Joi.StringSchema<string>;
|
|
9
|
+
phone: () => Joi.StringSchema<string>;
|
|
10
|
+
};
|
|
11
|
+
number: {
|
|
12
|
+
integer: () => Joi.NumberSchema<number>;
|
|
13
|
+
positive: () => Joi.NumberSchema<number>;
|
|
14
|
+
range: (min: number, max: number) => Joi.NumberSchema<number>;
|
|
15
|
+
};
|
|
16
|
+
object: {
|
|
17
|
+
stripUnknown: (schema: Joi.Schema) => Joi.ObjectSchema<any>;
|
|
18
|
+
withDefaults: (schema: Joi.Schema) => Joi.ObjectSchema<any>;
|
|
19
|
+
};
|
|
20
|
+
date: {
|
|
21
|
+
iso: () => Joi.DateSchema<Date>;
|
|
22
|
+
timestamp: () => Joi.DateSchema<Date>;
|
|
23
|
+
};
|
|
24
|
+
xss: () => Joi.StringSchema<string>;
|
|
25
|
+
};
|
|
26
|
+
export declare function applyJoiSanitization(value: any, config: SanitizerConfig): {
|
|
27
|
+
value: any;
|
|
28
|
+
error?: Joi.ValidationError;
|
|
29
|
+
};
|
|
30
|
+
export declare const sanitizeRequest: (request: AppRequest, config: SanitizerConfig[]) => void;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.sanitizeRequest = exports.SanitizeSchemas = void 0;
|
|
37
|
+
exports.applyJoiSanitization = applyJoiSanitization;
|
|
38
|
+
const Joi = __importStar(require("joi"));
|
|
39
|
+
exports.SanitizeSchemas = {
|
|
40
|
+
string: {
|
|
41
|
+
trim: () => Joi.string().trim(),
|
|
42
|
+
email: () => Joi.string().email().trim().lowercase(),
|
|
43
|
+
name: () => Joi.string()
|
|
44
|
+
.trim()
|
|
45
|
+
.min(2)
|
|
46
|
+
.max(50)
|
|
47
|
+
.pattern(/^[a-zA-Z\s-]+$/),
|
|
48
|
+
slug: () => Joi.string()
|
|
49
|
+
.trim()
|
|
50
|
+
.lowercase()
|
|
51
|
+
.pattern(/^[a-z0-9-]+$/),
|
|
52
|
+
phone: () => Joi.string()
|
|
53
|
+
.trim()
|
|
54
|
+
.pattern(/^[\d\s\+\-\(\)]+$/),
|
|
55
|
+
},
|
|
56
|
+
number: {
|
|
57
|
+
integer: () => Joi.number().integer(),
|
|
58
|
+
positive: () => Joi.number().positive(),
|
|
59
|
+
range: (min, max) => Joi.number().min(min).max(max),
|
|
60
|
+
},
|
|
61
|
+
object: {
|
|
62
|
+
stripUnknown: (schema) => Joi.object(schema).unknown(false),
|
|
63
|
+
withDefaults: (schema) => Joi.object(schema).options({ stripUnknown: true }),
|
|
64
|
+
},
|
|
65
|
+
date: {
|
|
66
|
+
iso: () => Joi.date().iso(),
|
|
67
|
+
timestamp: () => Joi.date().timestamp(),
|
|
68
|
+
},
|
|
69
|
+
xss: () => Joi.string().custom((value, helpers) => {
|
|
70
|
+
if (typeof value !== 'string')
|
|
71
|
+
return value;
|
|
72
|
+
const sanitized = value
|
|
73
|
+
.replace(/javascript:/gi, '')
|
|
74
|
+
.replace(/on\w+=/gi, '')
|
|
75
|
+
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
|
76
|
+
.replace(/data:/gi, '');
|
|
77
|
+
return sanitized;
|
|
78
|
+
}, 'XSS sanitization'),
|
|
79
|
+
};
|
|
80
|
+
function applyJoiSanitization(value, config) {
|
|
81
|
+
if (!['headers', 'body', 'params', 'query'].includes(config.type)) {
|
|
82
|
+
return { value };
|
|
83
|
+
}
|
|
84
|
+
if (value === null || value === undefined) {
|
|
85
|
+
return { value };
|
|
86
|
+
}
|
|
87
|
+
const action = config.action || 'both';
|
|
88
|
+
const options = {
|
|
89
|
+
convert: true,
|
|
90
|
+
stripUnknown: config.stripUnknown ?? true,
|
|
91
|
+
abortEarly: false,
|
|
92
|
+
...config.options,
|
|
93
|
+
};
|
|
94
|
+
try {
|
|
95
|
+
let result;
|
|
96
|
+
switch (action) {
|
|
97
|
+
case 'validate':
|
|
98
|
+
result = config.schema.validate(value, { ...options, convert: false, noDefaults: true });
|
|
99
|
+
break;
|
|
100
|
+
case 'sanitize':
|
|
101
|
+
result = config.schema.validate(value, {
|
|
102
|
+
...options,
|
|
103
|
+
convert: true,
|
|
104
|
+
presence: 'optional',
|
|
105
|
+
noDefaults: false,
|
|
106
|
+
});
|
|
107
|
+
break;
|
|
108
|
+
case 'both':
|
|
109
|
+
default:
|
|
110
|
+
result = config.schema.validate(value, options);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
value: result.value,
|
|
115
|
+
error: result.error,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
value,
|
|
121
|
+
error: error,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const sanitizeRequest = (request, config) => {
|
|
126
|
+
config.forEach((conf) => {
|
|
127
|
+
const { value, error } = applyJoiSanitization(request[conf.type], conf);
|
|
128
|
+
if (error) {
|
|
129
|
+
throw { error, status: 400 };
|
|
130
|
+
}
|
|
131
|
+
request[conf.type] = value;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
exports.sanitizeRequest = sanitizeRequest;
|