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.
@@ -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
- // Rate Request middleware
31
- const { Limiter, Duplicater } = require("./middleware/index");
32
- endpoint.use(Limiter);
33
- endpoint.use(Duplicater);
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
- next();
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
- configure = { ...configure, ..._beech_.defineConfig.server.duplicateRequest };
10
- const Duplicater = configure.expiration ? duplicateRequest(configure) : nextDuplicater;
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 = { ...configure, ..._beech_.defineConfig.server.rateLimit };
15
- const Limiter = rateLimit(configure);
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
+ }