beech-api 3.9.0-beta.8-rc → 3.9.0-beta.9-rc
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 +69 -32
- package/package.json +3 -2
- package/packages/cli/bin/beech-app.js +1 -1
- package/packages/cli/bin/beech-service.js +3 -2
- package/packages/cli/core/auth/Credentials.js +80 -58
- package/packages/cli/core/databases/test.js +1 -1
- package/packages/cli/core/generator/_package +5 -6
- package/packages/cli/core/generator/_scheduler +16 -6
- package/packages/cli/core/generator/index.js +80 -0
- package/packages/cli/core/index.js +80 -7
- package/packages/cli/core/middleware/express/duplicateRequest.js +10 -6
- package/packages/cli/core/middleware/express/jwtCheckAllow.js +3 -3
- package/packages/cli/core/middleware/express/rateLimit.js +14 -2
- package/packages/cli/core/services/http.express.js +49 -7
- package/packages/cli/core/test/check-node.js +21 -0
- package/packages/lib/src/endpoint.js +344 -194
|
@@ -15,7 +15,6 @@ _app_.use(helmet());
|
|
|
15
15
|
const cors = require("cors");
|
|
16
16
|
global.endpoint = _express_.Router();
|
|
17
17
|
const cookieParser = require("cookie-parser");
|
|
18
|
-
const bodyParser = require("body-parser");
|
|
19
18
|
const methodOverride = require("method-override");
|
|
20
19
|
const expressSession = require("express-session");
|
|
21
20
|
const expressValidator = require("express-validator");
|
|
@@ -27,10 +26,86 @@ const _beech_ = require(appRoot + "/beech.config.js").defineConfig;
|
|
|
27
26
|
global._publicPath_ = _beech_.base;
|
|
28
27
|
const mySqlDbConnect = require("./databases/mysql");
|
|
29
28
|
const SequelizeDbConnect = require("./databases/sequelize");
|
|
30
|
-
//
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Set limit payload for request body & multipart/form-data (multer)
|
|
30
|
+
const multer = require("multer");
|
|
31
|
+
const uploadAllowMethod = _beech_?.payload?.file?.uploadAllowMethod || ["POST", "PATCH", "PUT"];
|
|
32
|
+
const allowedTypes = _beech_?.payload?.file?.allowedTypes || []; // default: no allowed type
|
|
33
|
+
const fileLimitSize = _beech_?.payload?.file?.limit || 5 * 1024 * 1024; // 5MB
|
|
34
|
+
const uploadStrategy = multer({
|
|
35
|
+
limits: { fileSize: fileLimitSize },
|
|
36
|
+
fileFilter: (req, file, cb) => {
|
|
37
|
+
if (allowedTypes.length === 0) {
|
|
38
|
+
cb(new Error("INVALID_FILE_TYPE_ALLOW"), false); // Allow all file types if no allowed types specified
|
|
39
|
+
} else if (allowedTypes.includes(file.mimetype)) {
|
|
40
|
+
cb(null, true);
|
|
41
|
+
} else {
|
|
42
|
+
cb(new Error("INVALID_FILE_TYPE"), false);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}).any();
|
|
46
|
+
const jsonLimitSize = _beech_?.payload.json?.limit || "100KB"; // json payload
|
|
47
|
+
const urlencodedLimitSize = _beech_?.payload?.urlencoded?.limit || "100KB"; // urlencoded payload (multipart/form-data)
|
|
48
|
+
const urlencodedExtended = !!(_beech_?.payload?.urlencoded?.extended ?? true);
|
|
49
|
+
_app_.use(_express_.urlencoded({ limit: urlencodedLimitSize, extended: urlencodedExtended })); // application/x-www-form-urlencoded payload
|
|
50
|
+
_app_.use(_express_.json({ limit: jsonLimitSize }));
|
|
51
|
+
_app_.use((req, res, next) => {
|
|
52
|
+
const isUploadMethod = uploadAllowMethod.includes(req.method);
|
|
53
|
+
const isMultipart = req.headers["content-type"]?.includes("multipart/form-data");
|
|
54
|
+
// Handle file upload for allowed methods
|
|
55
|
+
if (isUploadMethod) {
|
|
56
|
+
return uploadStrategy(req, res, (err) => {
|
|
57
|
+
if (err) return next(err);
|
|
58
|
+
next();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Handle method not allowed for file upload
|
|
62
|
+
if (!isUploadMethod && isMultipart) {
|
|
63
|
+
return res.status(405).json({
|
|
64
|
+
code: 405,
|
|
65
|
+
status: "METHOD_NOT_ALLOWED_FOR_UPLOAD",
|
|
66
|
+
message: _config_.main_config?.dev ? `File upload is not allowed for ${req.method} method.` : "Method Not Allowed for file upload.",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
// next to next middleware
|
|
70
|
+
next();
|
|
71
|
+
});
|
|
72
|
+
_app_.use((err, req, res, next) => {
|
|
73
|
+
// Handle payload too large error for JSON and URL-encoded
|
|
74
|
+
if (err.type === "entity.too.large") {
|
|
75
|
+
const isJson = req.headers["content-type"]?.includes("application/json");
|
|
76
|
+
const limitUsed = isJson ? jsonLimitSize : urlencodedLimitSize;
|
|
77
|
+
return res.status(413).json({
|
|
78
|
+
code: 413,
|
|
79
|
+
status: "PAYLOAD_TOO_LARGE",
|
|
80
|
+
message:_config_.main_config?.dev ? `${isJson ? 'JSON' : 'Form data'} too large, Max limit is ${limitUsed}` : "Payload Too Large.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (err.message === "INVALID_FILE_TYPE_ALLOW") {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
code: 400,
|
|
86
|
+
status: "INVALID_FILE_TYPE_ALLOW",
|
|
87
|
+
message: "Invalid file type, No file types allowed.",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Handle invalid file type error from multer
|
|
91
|
+
if (err.message === "INVALID_FILE_TYPE") {
|
|
92
|
+
return res.status(400).json({
|
|
93
|
+
code: 400,
|
|
94
|
+
status: "INVALID_FILE_TYPE",
|
|
95
|
+
message: _config_.main_config?.dev ? `Invalid file type, Allowed: ${allowedTypes.join(', ')}` : "Invalid file type.",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Handle multer file size limit error
|
|
99
|
+
if (err.code === "LIMIT_FILE_SIZE") {
|
|
100
|
+
return res.status(413).json({
|
|
101
|
+
code: 413,
|
|
102
|
+
status: "PAYLOAD_TOO_LARGE",
|
|
103
|
+
message: _config_.main_config?.dev ? `File size too large, Max limit is ${fileLimitSize / 1024 / 1024}MB` : "Payload Too Large.",
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// next to general error handler
|
|
107
|
+
next(err);
|
|
108
|
+
});
|
|
34
109
|
// Database test
|
|
35
110
|
const {
|
|
36
111
|
testConnectInProcess,
|
|
@@ -57,8 +132,6 @@ _app_.use((req, res, next) => {
|
|
|
57
132
|
});
|
|
58
133
|
});
|
|
59
134
|
// View engine
|
|
60
|
-
_app_.use(bodyParser.json());
|
|
61
|
-
_app_.use(bodyParser.urlencoded({ extended: true }));
|
|
62
135
|
_app_.use(methodOverride());
|
|
63
136
|
_app_.use(cookieParser());
|
|
64
137
|
_app_.use(expressSession({
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
const _beech_ = require(appRoot + "/beech.config.js");
|
|
2
2
|
const { duplicateRequest } = require("express-duplicate-request");
|
|
3
|
-
const nextDuplicater = (req, res, next) =>
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
let configure = {
|
|
3
|
+
const nextDuplicater = (req, res, next) => next();
|
|
4
|
+
const defaultConfigure = {
|
|
7
5
|
expiration: _beech_.defineConfig.server.duplicateRequest ? _beech_.defineConfig.server.duplicateRequest.expiration : 0,
|
|
8
6
|
};
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const baseConfigure = {
|
|
8
|
+
..._beech_.defineConfig.server.duplicateRequest, // Override default configure with user configure.
|
|
9
|
+
...defaultConfigure,
|
|
10
|
+
};
|
|
11
|
+
const Duplicater = (more_configure = {}) => {
|
|
12
|
+
const config = { ...baseConfigure, ...more_configure };
|
|
13
|
+
return config.expiration ? duplicateRequest(config) : nextDuplicater;
|
|
14
|
+
};
|
|
11
15
|
|
|
12
16
|
module.exports = { Duplicater, duplicateRequest };
|
|
@@ -26,9 +26,10 @@ const checkRoleMiddleware = (options) => {
|
|
|
26
26
|
return next();
|
|
27
27
|
} else {
|
|
28
28
|
const allowed = options.some(rule => {
|
|
29
|
-
Object.entries(rule).every(([key, condition]) => {
|
|
29
|
+
return Object.entries(rule).every(([key, condition]) => {
|
|
30
|
+
//console.log('----matchCondition(user?.[key], condition, user)-->>', matchCondition(user?.[key], condition, user));
|
|
30
31
|
return matchCondition(user?.[key], condition, user);
|
|
31
|
-
})
|
|
32
|
+
});
|
|
32
33
|
});
|
|
33
34
|
if (allowed) {
|
|
34
35
|
return next();
|
|
@@ -63,7 +64,6 @@ const operators = {
|
|
|
63
64
|
};
|
|
64
65
|
|
|
65
66
|
const matchCondition = (userValue, condition, user) => {
|
|
66
|
-
// primitive -> eq
|
|
67
67
|
if (typeof condition !== 'object' || condition instanceof RegExp || Array.isArray(condition)) {
|
|
68
68
|
return Array.isArray(condition)
|
|
69
69
|
? operators.$in(userValue, condition)
|
|
@@ -11,7 +11,19 @@ let configure = {
|
|
|
11
11
|
legacyHeaders: (_beech_.defineConfig.server.rateLimit) ? (_beech_.defineConfig.server.rateLimit.legacyHeaders || false) : false,
|
|
12
12
|
message: (_beech_.defineConfig.server.rateLimit) ? (_beech_.defineConfig.server.rateLimit.message || tooManyMsg) : tooManyMsg,
|
|
13
13
|
};
|
|
14
|
-
configure = {
|
|
15
|
-
|
|
14
|
+
configure = {
|
|
15
|
+
..._beech_.defineConfig.server.rateLimit, // Override default configure with user configure.
|
|
16
|
+
...configure,
|
|
17
|
+
keyGenerator: (req) => `${req.ip}:${req.params.hash}${req.params['0']}`,
|
|
18
|
+
};
|
|
19
|
+
const Limiter = (more_configure = {}) => {
|
|
20
|
+
const middleware = rateLimit({
|
|
21
|
+
...configure,
|
|
22
|
+
...more_configure,
|
|
23
|
+
});
|
|
24
|
+
return (req, res, next) => {
|
|
25
|
+
middleware(req, res, next);
|
|
26
|
+
};
|
|
27
|
+
};
|
|
16
28
|
|
|
17
29
|
module.exports = { Limiter, rateLimit };
|
|
@@ -3,6 +3,7 @@ const fs = require("fs");
|
|
|
3
3
|
const passport_config_file = "/passport.config.js";
|
|
4
4
|
const auth = require("../auth/Credentials");
|
|
5
5
|
const { TwoFactor } = require("../helpers/2fa");
|
|
6
|
+
const { Limiter, Duplicater } = require("../middleware");
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
8
9
|
expressStart() {
|
|
@@ -142,7 +143,7 @@ module.exports = {
|
|
|
142
143
|
// declare authentication endpoint name with publicPath
|
|
143
144
|
let auth_endpoint = (passport_config.auth_endpoint) ? (passport_config.auth_endpoint[ 0 ] === "/" ? passport_config.auth_endpoint : "/" + passport_config.auth_endpoint) : "/authentication";
|
|
144
145
|
// authentication endpoints
|
|
145
|
-
endpoint.post(auth_endpoint, auth.credentialsGuard, (req, res, next) => {
|
|
146
|
+
endpoint.post(auth_endpoint, Duplicater(), Limiter(), auth.credentialsGuard, (req, res, next) => {
|
|
146
147
|
passport.authenticate('local', { session: false }, (err, user, opt) => {
|
|
147
148
|
if (err) {
|
|
148
149
|
res.status(502).json({ code: 502, status: "BAD_GATEWAY", message: String(err) });
|
|
@@ -194,8 +195,49 @@ module.exports = {
|
|
|
194
195
|
}
|
|
195
196
|
})(req, res, next);
|
|
196
197
|
});
|
|
198
|
+
// refresh token endpoints
|
|
199
|
+
endpoint.post(auth_endpoint + '/refresh', Duplicater(), Limiter(), auth.credentials, (req, res) => {
|
|
200
|
+
const refreshToken = req.headers.authorization.split("Bearer ")[1] || null;
|
|
201
|
+
if (!refreshToken) {
|
|
202
|
+
return res.status(400).json({
|
|
203
|
+
code: 400,
|
|
204
|
+
status: "BAD_REQUEST",
|
|
205
|
+
message: "Bad request.",
|
|
206
|
+
info: {
|
|
207
|
+
status: "BAD_ENTITY",
|
|
208
|
+
message: "Refresh token is required.",
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
jwt.verify(refreshToken, passport_config.secret, (err, user) => {
|
|
213
|
+
if (err) {
|
|
214
|
+
return res.status(401).json({
|
|
215
|
+
code: 401,
|
|
216
|
+
status: "UNAUTHORIZED",
|
|
217
|
+
message: "Unauthorized user.",
|
|
218
|
+
info: {
|
|
219
|
+
status: "TOKEN_INVALID_ERR",
|
|
220
|
+
message: "Invalid refresh token.",
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
delete user.iat;
|
|
225
|
+
delete user.exp;
|
|
226
|
+
const newAccessToken = jwt.sign(user, passport_config.secret, {
|
|
227
|
+
expiresIn: passport_config.token_expired
|
|
228
|
+
});
|
|
229
|
+
res.status(200).json({
|
|
230
|
+
code: 200,
|
|
231
|
+
status: "TOKEN_REFRESHED",
|
|
232
|
+
user: user,
|
|
233
|
+
accessToken: newAccessToken,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
197
239
|
// create auth data endpoints
|
|
198
|
-
endpoint.post(auth_endpoint + '/create', auth.credentialsGuard, (req, res) => {
|
|
240
|
+
endpoint.post(auth_endpoint + '/create', Duplicater(), Limiter(), auth.credentialsGuard, (req, res) => {
|
|
199
241
|
const promise = new Promise((resolve) => {
|
|
200
242
|
/**
|
|
201
243
|
*
|
|
@@ -249,7 +291,7 @@ module.exports = {
|
|
|
249
291
|
});
|
|
250
292
|
});
|
|
251
293
|
// patch auth data endpoints
|
|
252
|
-
endpoint.patch(auth_endpoint + '/update/:id', auth.credentials, (req, res) => {
|
|
294
|
+
endpoint.patch(auth_endpoint + '/update/:id', Duplicater(), Limiter(), auth.credentials, (req, res) => {
|
|
253
295
|
const promise = new Promise((resolve) => {
|
|
254
296
|
if (passport_config.app_key_allow) {
|
|
255
297
|
if (req.headers.app_key) {
|
|
@@ -303,7 +345,7 @@ module.exports = {
|
|
|
303
345
|
*
|
|
304
346
|
*/
|
|
305
347
|
if (passport_config.strategy.google.allow) {
|
|
306
|
-
endpoint.get(auth_endpoint + '/google', passport.authenticate('google', {
|
|
348
|
+
endpoint.get(auth_endpoint + '/google', Limiter(), passport.authenticate('google', {
|
|
307
349
|
scope: [
|
|
308
350
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
309
351
|
'https://www.googleapis.com/auth/plus.login'
|
|
@@ -311,7 +353,7 @@ module.exports = {
|
|
|
311
353
|
}));
|
|
312
354
|
// google auth callback
|
|
313
355
|
const googleCallback = (passport_config.strategy.google.callbackURL) ? (passport_config.strategy.google.callbackURL[ 0 ] === "/" ? passport_config.strategy.google.callbackURL : "/" + passport_config.strategy.google.callbackURL) : "/google/callback";
|
|
314
|
-
endpoint.get(auth_endpoint + googleCallback, passport.authenticate('google', { failureRedirect: passport_config.strategy.google.failureRedirect, failureMessage: true }), (req, res) => {
|
|
356
|
+
endpoint.get(auth_endpoint + googleCallback, Limiter(), passport.authenticate('google', { failureRedirect: passport_config.strategy.google.failureRedirect, failureMessage: true }), (req, res) => {
|
|
315
357
|
if (typeof req.user.user !== 'undefined') {
|
|
316
358
|
// declare user for sign JWT
|
|
317
359
|
let user = JSON.parse(JSON.stringify(req.user.user));
|
|
@@ -360,10 +402,10 @@ module.exports = {
|
|
|
360
402
|
*
|
|
361
403
|
*/
|
|
362
404
|
if (passport_config.strategy.facebook.allow) {
|
|
363
|
-
endpoint.get(auth_endpoint + '/facebook', passport.authenticate('facebook', { scope: [ 'email', 'public_profile' ] }));
|
|
405
|
+
endpoint.get(auth_endpoint + '/facebook', Limiter(), passport.authenticate('facebook', { scope: [ 'email', 'public_profile' ] }));
|
|
364
406
|
// facebook callback
|
|
365
407
|
const facebookCallback = (passport_config.strategy.facebook.callbackURL) ? (passport_config.strategy.facebook.callbackURL[ 0 ] === "/" ? passport_config.strategy.facebook.callbackURL : "/" + passport_config.strategy.facebook.callbackURL) : "/facebook/callback";
|
|
366
|
-
endpoint.get(auth_endpoint + facebookCallback, passport.authenticate('facebook', { failureRedirect: passport_config.strategy.facebook.failureRedirect, failureMessage: true }), (req, res) => {
|
|
408
|
+
endpoint.get(auth_endpoint + facebookCallback, Limiter(), passport.authenticate('facebook', { failureRedirect: passport_config.strategy.facebook.failureRedirect, failureMessage: true }), (req, res) => {
|
|
367
409
|
if (typeof req.user.user !== 'undefined') {
|
|
368
410
|
// declare user for sign JWT
|
|
369
411
|
let user = JSON.parse(JSON.stringify(req.user.user));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const current = process.versions.node;
|
|
2
|
+
|
|
3
|
+
const required = {
|
|
4
|
+
min16: "16.20.0",
|
|
5
|
+
min22: "22.18.0",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const semver = (a, b) => {
|
|
9
|
+
const pa = a.split(".").map(Number);
|
|
10
|
+
const pb = b.split(".").map(Number);
|
|
11
|
+
for (let i = 0; i < 3; i++) {
|
|
12
|
+
if (pa[i] > pb[i]) return 1;
|
|
13
|
+
if (pa[i] < pb[i]) return -1;
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
if (semver(current, required.min16) < 0 && semver(current, required.min22) < 0) {
|
|
19
|
+
console.error(`\n❌ You are using Node.js ${current}.\n` + `Beech requires Node.js version ${required.min16}+ or ${required.min22}+.\n` + `Please upgrade your Node.js version.\n`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|