@adaptivestone/framework 5.0.0-alpha.9 → 5.0.0-beta.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/commands/CreateUser.js +4 -0
  3. package/commands/Documentation.js +4 -0
  4. package/commands/GenerateRandomBytes.js +19 -0
  5. package/commands/GetOpenApiJson.js +4 -0
  6. package/config/auth.js +4 -1
  7. package/config/ipDetector.js +14 -0
  8. package/controllers/Auth.js +2 -2
  9. package/controllers/Home.js +1 -1
  10. package/helpers/files.js +8 -8
  11. package/models/User.js +7 -1
  12. package/modules/AbstractCommand.js +1 -1
  13. package/modules/AbstractController.js +19 -19
  14. package/modules/AbstractModel.d.ts +48 -0
  15. package/modules/AbstractModel.js +16 -1
  16. package/modules/Base.d.ts +4 -4
  17. package/modules/Base.js +11 -1
  18. package/modules/BaseCli.js +23 -8
  19. package/package.json +10 -11
  20. package/server.js +1 -1
  21. package/services/cache/Cache.js +6 -4
  22. package/services/http/HttpServer.js +11 -9
  23. package/services/http/middleware/GetUserByToken.js +3 -2
  24. package/services/http/middleware/I18n.js +21 -22
  25. package/services/http/middleware/IpDetector.js +59 -0
  26. package/services/http/middleware/Pagination.js +3 -2
  27. package/services/http/middleware/RateLimiter.js +8 -2
  28. package/services/http/middleware/RequestLogger.js +3 -3
  29. package/services/http/middleware/RequestParser.js +1 -1
  30. package/services/validate/ValidateService.js +1 -1
  31. package/.eslintrc.cjs +0 -41
  32. package/commands/Generate.js +0 -14
  33. package/controllers/Auth.test.js +0 -451
  34. package/controllers/Home.test.js +0 -12
  35. package/models/Migration.test.js +0 -20
  36. package/models/Sequence.test.js +0 -43
  37. package/models/User.test.js +0 -143
  38. package/modules/Modules.test.js +0 -18
  39. package/services/cache/Cache.test.js +0 -81
  40. package/services/http/middleware/Auth.test.js +0 -57
  41. package/services/http/middleware/Cors.test.js +0 -147
  42. package/services/http/middleware/GetUserByToken.test.js +0 -108
  43. package/services/http/middleware/I18n.test.js +0 -96
  44. package/services/http/middleware/PrepareAppInfo.test.js +0 -26
  45. package/services/http/middleware/RateLimiter.test.js +0 -233
  46. package/services/http/middleware/RequestParser.test.js +0 -121
  47. package/services/http/middleware/Role.test.js +0 -93
  48. package/services/validate/ValidateService.test.js +0 -107
@@ -5,13 +5,14 @@ class GetUserByToken extends AbstractMiddleware {
5
5
  return 'Grab a token and try to parse the user from it. It user exist will add req.appInfo.user variable';
6
6
  }
7
7
 
8
+ // eslint-disable-next-line class-methods-use-this
8
9
  get usedAuthParameters() {
9
10
  return [
10
11
  {
11
12
  name: 'Authorization',
12
13
  type: 'apiKey',
13
14
  in: 'header',
14
- description: this.constructor.description,
15
+ description: GetUserByToken.description,
15
16
  },
16
17
  ];
17
18
  }
@@ -21,7 +22,7 @@ class GetUserByToken extends AbstractMiddleware {
21
22
  this.logger.warn('You call GetUserByToken more then once');
22
23
  return next();
23
24
  }
24
- let { token } = req.body;
25
+ let { token } = req.body || {};
25
26
  this.logger.verbose(
26
27
  `GetUserByToken token in BODY ${token}. Token in Authorization header ${req.get(
27
28
  'Authorization',
@@ -1,37 +1,36 @@
1
1
  import i18next from 'i18next';
2
2
  import BackendFS from 'i18next-fs-backend';
3
- import Backend from 'i18next-chained-backend';
4
3
  import AbstractMiddleware from './AbstractMiddleware.js';
5
4
 
6
5
  class I18n extends AbstractMiddleware {
6
+ cache = {};
7
+
8
+ enabled = true;
9
+
10
+ lookupQuerystring = '';
11
+
12
+ supportedLngs = [];
13
+
14
+ fallbackLng = 'en';
15
+
16
+ /** @type {i18next} */
17
+ i18n = {
18
+ // @ts-ignore
19
+ t: (text) => text,
20
+ language: 'en',
21
+ };
22
+
7
23
  constructor(app, params) {
8
24
  super(app, params);
9
25
  const I18NConfig = this.app.getConfig('i18n');
10
- this.i18n = {
11
- t: (text) => text,
12
- language: I18NConfig.fallbackLng,
13
- };
14
- this.cache = {};
15
26
 
16
27
  if (I18NConfig.enabled) {
17
28
  this.logger.info('Enabling i18n support');
18
29
  this.i18n = i18next;
19
- i18next.use(Backend).init({
30
+ i18next.use(BackendFS).init({
20
31
  backend: {
21
- backends: [
22
- BackendFS,
23
- // BackendFS,
24
- ],
25
- backendOptions: [
26
- // {
27
- // loadPath: __dirname + '/../../locales/{{lng}}/{{ns}}.json',
28
- // addPath: __dirname + '/../../locales/{{lng}}/{{ns}}.missing.json'
29
- // },
30
- {
31
- loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
32
- addPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.missing.json`,
33
- },
34
- ],
32
+ loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
33
+ addPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.missing.json`,
35
34
  },
36
35
  fallbackLng: I18NConfig.fallbackLng,
37
36
  preload: I18NConfig.preload,
@@ -64,7 +63,7 @@ class I18n extends AbstractMiddleware {
64
63
 
65
64
  if (!this.cache[lang]) {
66
65
  this.cache[lang] = i18next.cloneInstance({
67
- initImmediate: false,
66
+ initAsync: false,
68
67
  lng: lang,
69
68
  });
70
69
  }
@@ -0,0 +1,59 @@
1
+ import { BlockList } from 'node:net';
2
+
3
+ import AbstractMiddleware from './AbstractMiddleware.js';
4
+
5
+ class IpDetector extends AbstractMiddleware {
6
+ static get description() {
7
+ return 'Detect real user IP address. Support proxy and load balancer';
8
+ }
9
+
10
+ constructor(app, params) {
11
+ super(app, params);
12
+ const { trustedProxy } = this.app.getConfig('ipDetector');
13
+
14
+ this.blockList = new BlockList();
15
+
16
+ for (const subnet of trustedProxy) {
17
+ const addressType = subnet.includes(':') ? 'ipv6' : 'ipv4';
18
+ if (subnet.includes('/')) {
19
+ // CIDR
20
+ const [realSubnet, prefixLength] = subnet.split('/');
21
+ this.blockList.addSubnet(
22
+ realSubnet,
23
+ parseInt(prefixLength, 10),
24
+ addressType,
25
+ );
26
+ } else if (subnet.includes('-')) {
27
+ // RANGE
28
+ const [start, end] = subnet.split('-');
29
+ this.blockList.addRange(start, end, addressType);
30
+ } else {
31
+ // just an address
32
+ this.blockList.addAddress(subnet, addressType);
33
+ }
34
+ }
35
+ }
36
+
37
+ async middleware(req, res, next) {
38
+ const { headers } = this.app.getConfig('ipDetector');
39
+ const initialIp = req.socket.remoteAddress;
40
+ req.appInfo.ip = initialIp;
41
+ const addressType = initialIp.includes(':') ? 'ipv6' : 'ipv4';
42
+
43
+ if (this.blockList.check(initialIp, addressType)) {
44
+ // we can trust this source
45
+ for (const header of headers) {
46
+ // in a range
47
+ const ipHeader = req.headers[header.toLowerCase()];
48
+ if (ipHeader) {
49
+ const [firstIp] = ipHeader.split(',').map((ip) => ip.trim());
50
+ req.appInfo.ip = firstIp;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ next();
56
+ }
57
+ }
58
+
59
+ export default IpDetector;
@@ -19,8 +19,9 @@ class Pagination extends AbstractMiddleware {
19
19
  async middleware(req, res, next) {
20
20
  let { limit, maxLimit } = this.params;
21
21
 
22
- limit = typeof limit === 'number' ? parseInt(limit, 10) : 10;
23
- maxLimit = typeof maxLimit === 'number' ? parseInt(maxLimit, 10) : 100;
22
+ limit = (typeof limit !== 'number' ? parseInt(limit, 10) : limit) || 10;
23
+ maxLimit =
24
+ (typeof maxLimit !== 'number' ? parseInt(maxLimit, 10) : maxLimit) || 100;
24
25
 
25
26
  req.appInfo.pagination = {};
26
27
  req.appInfo.pagination.page =
@@ -78,10 +78,16 @@ class RateLimiter extends AbstractMiddleware {
78
78
 
79
79
  const key = [];
80
80
  if (ip) {
81
- key.push(req.ip);
81
+ if (!req.appInfo.ip) {
82
+ this.logger.error(
83
+ `RateLimiter: Can't get remote address from request. Please check that you used IpDetecor middleware before RateLimiter`,
84
+ );
85
+ } else {
86
+ key.push(req.appInfo.ip);
87
+ }
82
88
  }
83
89
  if (route) {
84
- key.push(req.originalUrl);
90
+ key.push(`${req.baseUrl ?? ''}${req.path ?? ''}`); // to avoid quesry params
85
91
  }
86
92
  if (user && req.appInfo?.user) {
87
93
  key.push(req.appInfo?.user.id);
@@ -6,13 +6,13 @@ class RequestLogger extends AbstractMiddleware {
6
6
  }
7
7
 
8
8
  async middleware(req, res, next) {
9
- const startTime = Date.now();
9
+ const startTime = performance.now();
10
10
  const text = `Request is [${req.method}] ${req.url}`;
11
11
  this.logger.info(text);
12
12
  res.on('finish', () => {
13
- const duration = Date.now() - startTime;
13
+ const end = performance.now();
14
14
  this.logger.info(
15
- `Finished ${text}. Status: ${res.statusCode}. Duration ${duration} ms`,
15
+ `Finished ${text}. Status: ${res.statusCode}. [${(end - startTime).toFixed(2)} ms]`,
16
16
  );
17
17
  });
18
18
  next();
@@ -29,7 +29,7 @@ class RequestParser extends AbstractMiddleware {
29
29
 
30
30
  req.body = {
31
31
  // todo avoid body in next versions
32
- ...req.body,
32
+ ...(req.body || {}),
33
33
  ...fields,
34
34
  ...files,
35
35
  };
@@ -26,7 +26,7 @@ class ValidateService extends Base {
26
26
  );
27
27
  }
28
28
 
29
- static getDriverByValidatorBody(app, body) {
29
+ static getDriverByValidatorBody(app, body = {}) {
30
30
  if (this.isValidatorExists(body)) {
31
31
  return body;
32
32
  }
package/.eslintrc.cjs DELETED
@@ -1,41 +0,0 @@
1
- module.exports = {
2
- env: {
3
- es2023: true,
4
- node: true,
5
- },
6
- extends: ['airbnb-base', 'prettier'],
7
- plugins: ['prettier'],
8
- parserOptions: {
9
- ecmaVersion: 2023,
10
- },
11
- rules: {
12
- 'no-restricted-syntax': [
13
- 'error',
14
- {
15
- selector: 'ForInStatement',
16
- message:
17
- 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
18
- },
19
- {
20
- selector: 'LabeledStatement',
21
- message:
22
- 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
23
- },
24
- {
25
- selector: 'WithStatement',
26
- message:
27
- '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
28
- },
29
- ],
30
- 'prettier/prettier': 'error',
31
- 'import/extensions': 'off', // it have a problem with dynamic imports
32
- 'import/prefer-default-export': 'off', // we want to have signgke default export too
33
- },
34
- overrides: [
35
- {
36
- files: ['**/*.test.js'],
37
- extends: ['plugin:vitest/all', 'plugin:vitest/recommended'],
38
- plugins: ['vitest'],
39
- },
40
- ],
41
- };
@@ -1,14 +0,0 @@
1
- import AbstractCommand from '../modules/AbstractCommand.js';
2
-
3
- class Generate extends AbstractCommand {
4
- async run() {
5
- if (!this.args.type) {
6
- this.logger.error('Please provide type to generate "--type=model');
7
-
8
- return false;
9
- }
10
- return true;
11
- }
12
- }
13
-
14
- export default Generate;