@adaptivestone/framework 5.0.0-alpha.1 → 5.0.0-alpha.11

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 (43) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/commands/CreateUser.js +3 -1
  3. package/commands/migration/Migrate.js +1 -1
  4. package/config/auth.js +2 -1
  5. package/config/ipDetector.js +14 -0
  6. package/folderConfig.js +0 -1
  7. package/jsconfig.json +9 -0
  8. package/modules/AbstractCommand.js +2 -1
  9. package/modules/AbstractController.js +4 -0
  10. package/modules/AbstractModel.js +5 -2
  11. package/modules/Base.d.ts +4 -3
  12. package/package.json +14 -15
  13. package/server.d.ts +7 -5
  14. package/server.js +10 -6
  15. package/services/cache/Cache.d.ts +2 -2
  16. package/services/cache/Cache.js +4 -2
  17. package/services/http/HttpServer.js +3 -11
  18. package/services/http/middleware/GetUserByToken.js +1 -1
  19. package/services/http/middleware/IpDetector.js +59 -0
  20. package/services/http/middleware/RateLimiter.js +7 -1
  21. package/services/http/middleware/RequestParser.js +4 -1
  22. package/services/messaging/email/index.js +7 -15
  23. package/tests/setup.js +3 -1
  24. package/tests/setupVitest.js +1 -1
  25. package/types/TFoldersConfig.d.ts +0 -2
  26. package/.eslintrc.cjs +0 -41
  27. package/controllers/Auth.test.js +0 -451
  28. package/controllers/Home.test.js +0 -12
  29. package/models/Migration.test.js +0 -20
  30. package/models/Sequence.test.js +0 -43
  31. package/models/User.test.js +0 -143
  32. package/modules/Modules.test.js +0 -18
  33. package/services/cache/Cache.test.js +0 -81
  34. package/services/http/middleware/Auth.test.js +0 -57
  35. package/services/http/middleware/Cors.test.js +0 -147
  36. package/services/http/middleware/GetUserByToken.test.js +0 -108
  37. package/services/http/middleware/I18n.test.js +0 -96
  38. package/services/http/middleware/PrepareAppInfo.test.js +0 -26
  39. package/services/http/middleware/RateLimiter.test.js +0 -233
  40. package/services/http/middleware/RequestParser.test.js +0 -113
  41. package/services/http/middleware/Role.test.js +0 -93
  42. package/services/http/middleware/StaticFiles.js +0 -59
  43. package/services/validate/ValidateService.test.js +0 -107
package/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ ### 5.0.0-alpha.11
2
+
3
+ [UPDATE] update deps
4
+
5
+ ### 5.0.0-alpha.10
6
+
7
+ [UPDATE] update deps
8
+ [NEW] IpDetector middleware that support detecting proxy and X-Forwarded-For header
9
+ [BREAKING] RateLimiter now need to have IpDetector middleware before
10
+
11
+ ### 5.0.0-alpha.9
12
+
13
+ [UPDATE] update deps
14
+ [BREAKING] removing staticFiles middleware as it not used in projects anymore. Docs with nginx config will be provided
15
+ [BREAKING] remove default AUTH_SALT. It should be provided on a app level now
16
+ [BREAKING] Vitest 2.0.0 https://vitest.dev/guide/migration.html#migrating-to-vitest-2-0
17
+
18
+ ### 5.0.0-alpha.8
19
+
20
+ [UPDATE] replace dotenv with loadEnvFile
21
+ [UPDATE] replace nodemon with node --watch (dev only)
22
+ [BREAKING] Minimum node version is 20.12 as for now (process.loadEnvFile)
23
+
24
+ ### 5.0.0-alpha.7
25
+
26
+ [UPDATE] deps update
27
+
28
+ ### 5.0.0-alpha.6
29
+
30
+ [UPDATE] Update internal documentation (jsdoc, d.ts)
31
+
32
+ ### 5.0.0-alpha.5
33
+
34
+ [UPDATE] More verbose errors for rapsing body request.
35
+ [UPDATE] deps update
36
+
37
+ ### 5.0.0-alpha.4
38
+
39
+ [UPDATE] Update rate-limiter-flexible to v5
40
+ [CHANGE] Cache update redis.setEX to redis.set(..,..,{EX:xx}) as setEX deprecated
41
+
42
+ ### 5.0.0-alpha.3
43
+
44
+ [UPDATE] deps update
45
+ [FIX] Migration commands apply
46
+
47
+ ### 5.0.0-alpha.2
48
+
49
+ [UPDATE] deps update
50
+
1
51
  ### 5.0.0-alpha.1
2
52
 
3
53
  [BREAKING] Vitest 1.0.0 https://vitest.dev/guide/migration.html#migrating-from-vitest-0-34-6
@@ -60,7 +60,9 @@ class CreateUser extends AbstractCommand {
60
60
 
61
61
  await user.generateToken();
62
62
 
63
- this.logger.info(`User was created/updated ${JSON.stringify(user, 0, 4)}`);
63
+ this.logger.info(
64
+ `User was created/updated ${JSON.stringify(user, null, 4)}`,
65
+ );
64
66
 
65
67
  return user;
66
68
  }
@@ -36,7 +36,7 @@ class Migrate extends AbstractCommand {
36
36
  for (const migration of migrations) {
37
37
  this.logger.info(`=== Start migration ${migration.file} ===`);
38
38
  // eslint-disable-next-line no-await-in-loop
39
- const MigrationCommand = await import(migration.path);
39
+ const { default: MigrationCommand } = await import(migration.path);
40
40
  const migrationCommand = new MigrationCommand(this.app);
41
41
  // eslint-disable-next-line no-await-in-loop
42
42
  await migrationCommand.up();
package/config/auth.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  hashRounds: 64,
3
- saltSecret: process.env.AUTH_SALT || 'gdfg45667_%%^trterte',
3
+ saltSecret:
4
+ process.env.AUTH_SALT || console.error('AUTH_SALT is not defined'),
4
5
  isAuthWithVefificationFlow: true,
5
6
  };
@@ -0,0 +1,14 @@
1
+ export default {
2
+ headers: ['X-Forwarded-For'],
3
+ trustedProxy: [
4
+ // list of trusted proxies.
5
+ '169.254.0.0/16', // linklocal
6
+ 'fe80::/10', // linklocal
7
+ '127.0.0.1/8', // loopback
8
+ '::1/128', // loopback
9
+ '10.0.0.0/8', // uniquelocal
10
+ '172.16.0.0/12', // uniquelocal
11
+ '192.168.0.0/16', // uniquelocal
12
+ 'fc00::/7', // uniquelocal
13
+ ],
14
+ };
package/folderConfig.js CHANGED
@@ -6,7 +6,6 @@ export default {
6
6
  models: path.resolve('./models'),
7
7
  controllers: path.resolve('./controllers'),
8
8
  views: path.resolve('./views'),
9
- public: path.resolve('./public'),
10
9
  locales: path.resolve('./locales'),
11
10
  emails: path.resolve('./services/messaging/email/templates'),
12
11
  commands: path.resolve('./commands'),
package/jsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "node16",
4
+ "target": "ES2022",
5
+ "moduleResolution": "node16",
6
+ "checkJs": true
7
+ },
8
+ "exclude": ["node_modules"]
9
+ }
@@ -13,10 +13,11 @@ class AbstractCommand extends Base {
13
13
 
14
14
  /**
15
15
  * Entry point to every command. This method should be overridden
16
- * @override
16
+ * @return {Promise<boolean>} resut
17
17
  */
18
18
  async run() {
19
19
  this.logger.error('You should implement run method');
20
+ return false;
20
21
  }
21
22
 
22
23
  static get loggerGroup() {
@@ -25,10 +25,12 @@ class AbstractController extends Base {
25
25
  const { routes } = this;
26
26
  let httpPath = this.getHttpPath();
27
27
 
28
+ // @ts-ignore
28
29
  if (this.getExpressPath) {
29
30
  this.logger.warn(
30
31
  `getExpressPath deprecated. Please use getHttpPath instead. Will be removed on v5`,
31
32
  );
33
+ // @ts-ignore
32
34
  httpPath = this.getExpressPath();
33
35
  }
34
36
 
@@ -58,6 +60,7 @@ class AbstractController extends Base {
58
60
  httpPath,
59
61
  );
60
62
  const middlewaresInfo = this.parseMiddlewares(
63
+ // @ts-ignore
61
64
  this.constructor.middleware,
62
65
  httpPath,
63
66
  );
@@ -370,6 +373,7 @@ class AbstractController extends Base {
370
373
  * You should provide path relative to controller and then array of middlewares to apply.
371
374
  * Order is matter.
372
375
  * Be default path apply to ANY' method, but you can preattach 'METHOD' into patch to scope patch to this METHOD
376
+ * @returns {Map<string, Array<AbstractMiddleware | [Function, ...any]>>}
373
377
  * @example
374
378
  * return new Map([
375
379
  * ['/*', [GetUserByToken]] // for any method for this controller
@@ -3,12 +3,12 @@ import Base from './Base.js';
3
3
 
4
4
  class AbstractModel extends Base {
5
5
  /**
6
- * @param {import('../server')} app //TODO change to *.d.ts as this is a Server, not app
6
+ * @param {import('../server.js').default['app']} app //TODO change to *.d.ts as this is a Server, not app
7
7
  * @param function callback optional callback when connection ready
8
8
  */
9
9
  constructor(app, callback = () => {}) {
10
10
  super(app);
11
- this.mongooseSchema = mongoose.Schema(this.modelSchema);
11
+ this.mongooseSchema = new mongoose.Schema(this.modelSchema);
12
12
  mongoose.set('strictQuery', true);
13
13
  this.mongooseSchema.set('timestamps', true);
14
14
  this.mongooseSchema.set('minimize', false);
@@ -22,6 +22,9 @@ class AbstractModel extends Base {
22
22
  );
23
23
  if (!mongoose.connection.readyState) {
24
24
  this.app.events.on('shutdown', async () => {
25
+ this.logger.verbose(
26
+ 'Shutdown was called. Closing all mongoose connections',
27
+ );
25
28
  for (const c of mongoose.connections) {
26
29
  c.close(true);
27
30
  }
package/modules/Base.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import winston from 'winston';
2
- import Server from '../server';
2
+ import Server from '../server.js';
3
+ import type { Dirent } from 'fs';
3
4
 
4
5
  declare class Base {
5
6
  app: Server['app'];
@@ -26,11 +27,11 @@ declare class Base {
26
27
  getFilesPathWithInheritance(
27
28
  internalFolder: string,
28
29
  externalFolder: string,
29
- ): Promise<string[]>;
30
+ ): Promise<Dirent[]>;
30
31
 
31
32
  /**
32
33
  * Return logger group. Just to have all logs groupped logically
33
34
  */
34
35
  static get loggerGroup(): string;
35
36
  }
36
- export = Base;
37
+ export default Base;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@adaptivestone/framework",
3
- "version": "5.0.0-alpha.1",
3
+ "version": "5.0.0-alpha.11",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "engines": {
8
- "node": ">=18.17.0"
8
+ "node": ">=20.12.0"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
@@ -13,14 +13,15 @@
13
13
  },
14
14
  "homepage": "https://framework.adaptivestone.com/",
15
15
  "scripts": {
16
- "dev": "nodemon ./index.js",
17
- "prod": "nodemon ./cluster.js",
16
+ "dev": "node --watch ./index.js",
17
+ "prod": "node --watch ./cluster.js",
18
18
  "test": "vitest run",
19
+ "t": "vitest --coverage=false --reporter=default",
19
20
  "prettier": "prettier --check '**/*.(js|jsx|ts|tsx|json|css|scss|md)'",
20
21
  "lint": "eslint '**/*.js'",
21
22
  "lint:fix": "eslint '**/*.js' --fix",
22
23
  "codestyle": "npm run prettier && npm run lint",
23
- "prepare": "husky install",
24
+ "prepare": "husky",
24
25
  "cli": "node cliCommand",
25
26
  "benchmark": "h2load -n 10000 -c 50 -p 'http/1.1' http://localhost:3300/",
26
27
  "benchmark2": "h2load -n 10000 -c 50 https://localhost:3300/",
@@ -30,14 +31,13 @@
30
31
  "license": "MIT",
31
32
  "dependencies": {
32
33
  "deepmerge": "^4.2.2",
33
- "dotenv": "^16.0.0",
34
34
  "express": "^4.17.1",
35
35
  "formidable": "^3.5.1",
36
36
  "html-to-text": "^9.0.3",
37
37
  "i18next": "^23.2.8",
38
38
  "i18next-chained-backend": "^4.0.0",
39
39
  "i18next-fs-backend": "^2.0.0",
40
- "juice": "^9.0.0",
40
+ "juice": "^10.0.0",
41
41
  "mime": "^4.0.0",
42
42
  "minimist": "^1.2.5",
43
43
  "mongoose": "^8.0.0",
@@ -45,25 +45,24 @@
45
45
  "nodemailer-sendmail-transport": "^1.0.2",
46
46
  "nodemailer-stub-transport": "^1.1.0",
47
47
  "pug": "^3.0.2",
48
- "rate-limiter-flexible": "^3.0.0",
48
+ "rate-limiter-flexible": "^5.0.0",
49
49
  "redis": "^4.3.1",
50
50
  "winston": "^3.3.3",
51
- "winston-transport-sentry-node": "^2.0.0",
51
+ "winston-transport-sentry-node": "^3.0.0",
52
52
  "yup": "^1.0.0"
53
53
  },
54
54
  "devDependencies": {
55
- "@vitest/coverage-v8": "^1.0.0",
55
+ "@vitest/coverage-v8": "^2.0.0",
56
56
  "eslint": "^8.0.0",
57
57
  "eslint-config-airbnb-base": "^15.0.0",
58
58
  "eslint-config-prettier": "^9.0.0",
59
59
  "eslint-plugin-prettier": "^5.0.0",
60
- "eslint-plugin-vitest": "^0.3.1",
61
- "husky": "^8.0.0",
60
+ "eslint-plugin-vitest": "^0.4.0",
61
+ "husky": "^9.0.0",
62
62
  "lint-staged": "^15.0.0",
63
- "mongodb-memory-server": "^9.0.0",
64
- "nodemon": "^3.0.1",
63
+ "mongodb-memory-server": "^10.0.0",
65
64
  "prettier": "^3.0.0",
66
- "vitest": "^1.0.0"
65
+ "vitest": "^2.0.0"
67
66
  },
68
67
  "lint-staged": {
69
68
  "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
package/server.d.ts CHANGED
@@ -9,6 +9,8 @@ import BaseCli from './modules/BaseCli';
9
9
  import Cache from './services/cache/Cache';
10
10
  import winston from 'winston';
11
11
 
12
+ import HttpServer from './services/http/HttpServer.js';
13
+
12
14
  type ServerConfig = {
13
15
  folders: ExpandDeep<TFolderConfig>;
14
16
  };
@@ -24,7 +26,7 @@ declare class Server {
24
26
  events: EventEmitter;
25
27
  get cache(): Server['cacheService'];
26
28
  get logger(): winston.Logger;
27
- httpServer: null;
29
+ httpServer: HttpServer | null;
28
30
  controllerManager: null;
29
31
  };
30
32
  cacheService: Cache;
@@ -33,7 +35,7 @@ declare class Server {
33
35
  configs: Map<string, {}>;
34
36
  models: Map<string, MongooseModel<any>>;
35
37
  };
36
- cli: boolean;
38
+ cli: null | BaseCli;
37
39
 
38
40
  /**
39
41
  * Construct new server
@@ -68,7 +70,7 @@ declare class Server {
68
70
  * @see updateConfig
69
71
  * @TODO generate that based on real data
70
72
  */
71
- getConfig(configName: string): {};
73
+ getConfig(configName: string): { [key: string]: any };
72
74
 
73
75
  /**
74
76
  * Return or create new logger instance. This is a main logger instance
@@ -79,7 +81,7 @@ declare class Server {
79
81
  * Primary designed for tests when we need to update some configs before start testing
80
82
  * Should be called before any initialization was done
81
83
  */
82
- updateConfig(configName: string, config: {}): {};
84
+ updateConfig(configName: string, config: {}): { [key: string]: any };
83
85
 
84
86
  /**
85
87
  * Return model from {modelName} (file name) on model folder.
@@ -93,4 +95,4 @@ declare class Server {
93
95
  runCliCommand(commandName: string, args: {}): Promise<BaseCli['run']>;
94
96
  }
95
97
 
96
- export = Server;
98
+ export default Server;
package/server.js CHANGED
@@ -1,16 +1,21 @@
1
1
  /* eslint-disable no-console */
2
2
  import EventEmitter from 'node:events';
3
- import { hrtime } from 'node:process';
3
+ import { hrtime, loadEnvFile } from 'node:process';
4
4
  import * as url from 'node:url';
5
5
  import path from 'node:path';
6
6
 
7
- import 'dotenv/config';
8
7
  import merge from 'deepmerge';
9
8
  import winston from 'winston';
10
9
  import { getFilesPathWithInheritance } from './helpers/files.js';
11
10
  import { consoleLogger } from './helpers/logger.js';
12
11
  import Cache from './services/cache/Cache.js';
13
12
 
13
+ try {
14
+ loadEnvFile();
15
+ } catch (e) {
16
+ console.warn('No env file found. This is ok. But please check youself.');
17
+ }
18
+
14
19
  /**
15
20
  * Main framework class.
16
21
  */
@@ -19,6 +24,8 @@ class Server {
19
24
 
20
25
  #isInited = false;
21
26
 
27
+ cli = null;
28
+
22
29
  /**
23
30
  * Construct new server
24
31
  * @param {Object} config main config object
@@ -27,7 +34,6 @@ class Server {
27
34
  * @param {String} config.folders.models path to folder with moidels files
28
35
  * @param {String} config.folders.controllers path to folder with controllers files
29
36
  * @param {String} config.folders.views path to folder with view files
30
- * @param {String} config.folders.public path to folder with public files
31
37
  * @param {String} config.folders.locales path to folder with locales files
32
38
  * @param {String} config.folders.emails path to folder with emails files
33
39
  */
@@ -56,8 +62,6 @@ class Server {
56
62
  models: new Map(),
57
63
  modelConstructors: new Map(),
58
64
  };
59
-
60
- this.cli = false;
61
65
  }
62
66
 
63
67
  /**
@@ -350,7 +354,7 @@ class Server {
350
354
  * Return model from {modelName} (file name) on model folder.
351
355
  * Support cache
352
356
  * @param {String} modelName name on config file to load
353
- * @returns {import('mongoose').Model}
357
+ * @returns {import('mongoose').Model | false| {}}
354
358
  */
355
359
  getModel(modelName) {
356
360
  if (modelName.endsWith('s')) {
@@ -1,5 +1,5 @@
1
1
  import Base from '../../modules/Base';
2
- import Server from '../../server';
2
+ import Server from '../../server.js';
3
3
 
4
4
  declare class Cache extends Base {
5
5
  app: Server['app'];
@@ -32,4 +32,4 @@ declare class Cache extends Base {
32
32
  removeKey(key: string): Promise<number>;
33
33
  }
34
34
 
35
- export = Cache;
35
+ export default Cache;
@@ -69,12 +69,14 @@ class Cache extends Base {
69
69
  return Promise.reject(e);
70
70
  }
71
71
 
72
- this.redisClient.setEx(
72
+ this.redisClient.set(
73
73
  key,
74
- storeTime,
75
74
  JSON.stringify(result, (jsonkey, value) =>
76
75
  typeof value === 'bigint' ? `${value}n` : value,
77
76
  ),
77
+ {
78
+ EX: storeTime,
79
+ },
78
80
  );
79
81
  } else {
80
82
  this.logger.verbose(
@@ -6,7 +6,7 @@ import RequestLoggerMiddleware from './middleware/RequestLogger.js';
6
6
  import I18nMiddleware from './middleware/I18n.js';
7
7
  import PrepareAppInfoMiddleware from './middleware/PrepareAppInfo.js';
8
8
  import RequestParserMiddleware from './middleware/RequestParser.js';
9
- import StaticFilesMiddleware from './middleware/StaticFiles.js';
9
+ import IpDetector from './middleware/IpDetector.js';
10
10
  import Cors from './middleware/Cors.js';
11
11
  import Base from '../../modules/Base.js';
12
12
 
@@ -26,6 +26,7 @@ class HttpServer extends Base {
26
26
  this.express.set('view engine', 'pug');
27
27
 
28
28
  this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
29
+ this.express.use(new IpDetector(this.app).getMiddleware());
29
30
  this.express.use(new RequestLoggerMiddleware(this.app).getMiddleware());
30
31
  this.express.use(new I18nMiddleware(this.app).getMiddleware());
31
32
 
@@ -35,15 +36,6 @@ class HttpServer extends Base {
35
36
  origins: httpConfig.corsDomains,
36
37
  }).getMiddleware(),
37
38
  );
38
- // todo whitelist
39
- this.express.use(
40
- new StaticFilesMiddleware(this.app, {
41
- folders: [
42
- this.app.foldersConfig.public,
43
- path.join(dirname, '../../public/files'),
44
- ],
45
- }).getMiddleware(),
46
- );
47
39
 
48
40
  this.express.use(new RequestParserMiddleware(this.app).getMiddleware());
49
41
 
@@ -57,7 +49,7 @@ class HttpServer extends Base {
57
49
  res.status(500).json({ message: 'Something broke!' });
58
50
  });
59
51
 
60
- this.httpServer = http.Server(this.express);
52
+ this.httpServer = http.createServer(this.express);
61
53
 
62
54
  const listener = this.httpServer.listen(
63
55
  httpConfig.port,
@@ -11,7 +11,7 @@ class GetUserByToken extends AbstractMiddleware {
11
11
  name: 'Authorization',
12
12
  type: 'apiKey',
13
13
  in: 'header',
14
- description: this?.description,
14
+ description: this.constructor.description,
15
15
  },
16
16
  ];
17
17
  }
@@ -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;
@@ -78,7 +78,13 @@ 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
90
  key.push(req.originalUrl);
@@ -18,7 +18,10 @@ class RequestParser extends AbstractMiddleware {
18
18
  [fields, files] = await form.parse(req);
19
19
  } catch (err) {
20
20
  this.logger.error(`Parsing failed ${err}`);
21
- return next(err);
21
+ return res.status(400).json({
22
+ message: `Error to parse your request. You provided invalid content type or content-length. Please check your request headers and content type.`,
23
+ });
24
+ // return next(err);
22
25
  }
23
26
  this.logger.verbose(
24
27
  `Parsing multipart/formdata request DONE ${Date.now() - time}ms`,
@@ -66,7 +66,8 @@ class Mail extends Base {
66
66
  * @param {object} templateData
67
67
  * @returns string
68
68
  */
69
- static async #renderTemplateFile({ type, fullPath } = {}, templateData = {}) {
69
+ // eslint-disable-next-line class-methods-use-this
70
+ async #renderTemplateFile({ type, fullPath } = {}, templateData = {}) {
70
71
  if (!type) {
71
72
  return null;
72
73
  }
@@ -116,19 +117,10 @@ class Mail extends Base {
116
117
 
117
118
  const [htmlRendered, subjectRendered, textRendered, extraCss] =
118
119
  await Promise.all([
119
- this.constructor.#renderTemplateFile(
120
- templates.html,
121
- templateDataToRender,
122
- ),
123
- this.constructor.#renderTemplateFile(
124
- templates.subject,
125
- templateDataToRender,
126
- ),
127
- this.constructor.#renderTemplateFile(
128
- templates.text,
129
- templateDataToRender,
130
- ),
131
- this.constructor.#renderTemplateFile(templates.style),
120
+ this.#renderTemplateFile(templates.html, templateDataToRender),
121
+ this.#renderTemplateFile(templates.subject, templateDataToRender),
122
+ this.#renderTemplateFile(templates.text, templateDataToRender),
123
+ this.#renderTemplateFile(templates.style),
132
124
  ]);
133
125
 
134
126
  juice.tableElements = ['TABLE'];
@@ -171,7 +163,7 @@ class Mail extends Base {
171
163
 
172
164
  /**
173
165
  * Send provided text (html) to email. Low level function. All data should be prepared before sending (like inline styles)
174
- * @param {objetc} app application
166
+ * @param {import('../../../server.js').default['app']} app application
175
167
  * @param {string} to send to
176
168
  * @param {string} subject email topic
177
169
  * @param {string} html hmlt body of emain
package/tests/setup.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable no-undef */
2
2
  import path from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
3
4
  import { MongoMemoryReplSet } from 'mongodb-memory-server';
4
5
  import mongoose from 'mongoose';
5
6
  import redis from 'redis';
@@ -19,6 +20,8 @@ beforeAll(async () => {
19
20
  });
20
21
  await mongoMemoryServerInstance.waitUntilRunning();
21
22
  process.env.LOGGER_CONSOLE_LEVEL = 'error';
23
+ process.env.AUTH_SALT = randomBytes(16).toString('hex');
24
+
22
25
  const connectionStringMongo = await mongoMemoryServerInstance.getUri();
23
26
  // console.info('MONGO_URI: ', connectionStringMongo);
24
27
  global.server = new Server({
@@ -27,7 +30,6 @@ beforeAll(async () => {
27
30
  controllers:
28
31
  process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
29
32
  views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
30
- public: process.env.TEST_FOLDER_PUBLIC || path.resolve('./public'),
31
33
  models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
32
34
  emails:
33
35
  process.env.TEST_FOLDER_EMAIL ||
@@ -12,13 +12,13 @@ mongoose.set('autoIndex', false);
12
12
 
13
13
  beforeAll(async () => {
14
14
  process.env.LOGGER_CONSOLE_LEVEL = 'error';
15
+ process.env.AUTH_SALT = crypto.randomBytes(16).toString('hex');
15
16
  global.server = new Server({
16
17
  folders: {
17
18
  config: process.env.TEST_FOLDER_CONFIG || path.resolve('./config'),
18
19
  controllers:
19
20
  process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
20
21
  views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
21
- public: process.env.TEST_FOLDER_PUBLIC || path.resolve('./public'),
22
22
  models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
23
23
  emails:
24
24
  process.env.TEST_FOLDER_EMAIL ||