@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.
- package/CHANGELOG.md +50 -0
- package/commands/CreateUser.js +3 -1
- package/commands/migration/Migrate.js +1 -1
- package/config/auth.js +2 -1
- package/config/ipDetector.js +14 -0
- package/folderConfig.js +0 -1
- package/jsconfig.json +9 -0
- package/modules/AbstractCommand.js +2 -1
- package/modules/AbstractController.js +4 -0
- package/modules/AbstractModel.js +5 -2
- package/modules/Base.d.ts +4 -3
- package/package.json +14 -15
- package/server.d.ts +7 -5
- package/server.js +10 -6
- package/services/cache/Cache.d.ts +2 -2
- package/services/cache/Cache.js +4 -2
- package/services/http/HttpServer.js +3 -11
- package/services/http/middleware/GetUserByToken.js +1 -1
- package/services/http/middleware/IpDetector.js +59 -0
- package/services/http/middleware/RateLimiter.js +7 -1
- package/services/http/middleware/RequestParser.js +4 -1
- package/services/messaging/email/index.js +7 -15
- package/tests/setup.js +3 -1
- package/tests/setupVitest.js +1 -1
- package/types/TFoldersConfig.d.ts +0 -2
- package/.eslintrc.cjs +0 -41
- package/controllers/Auth.test.js +0 -451
- package/controllers/Home.test.js +0 -12
- package/models/Migration.test.js +0 -20
- package/models/Sequence.test.js +0 -43
- package/models/User.test.js +0 -143
- package/modules/Modules.test.js +0 -18
- package/services/cache/Cache.test.js +0 -81
- package/services/http/middleware/Auth.test.js +0 -57
- package/services/http/middleware/Cors.test.js +0 -147
- package/services/http/middleware/GetUserByToken.test.js +0 -108
- package/services/http/middleware/I18n.test.js +0 -96
- package/services/http/middleware/PrepareAppInfo.test.js +0 -26
- package/services/http/middleware/RateLimiter.test.js +0 -233
- package/services/http/middleware/RequestParser.test.js +0 -113
- package/services/http/middleware/Role.test.js +0 -93
- package/services/http/middleware/StaticFiles.js +0 -59
- 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
|
package/commands/CreateUser.js
CHANGED
|
@@ -60,7 +60,9 @@ class CreateUser extends AbstractCommand {
|
|
|
60
60
|
|
|
61
61
|
await user.generateToken();
|
|
62
62
|
|
|
63
|
-
this.logger.info(
|
|
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
|
@@ -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
|
@@ -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
|
-
* @
|
|
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
|
package/modules/AbstractModel.js
CHANGED
|
@@ -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<
|
|
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
|
|
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.
|
|
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": ">=
|
|
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": "
|
|
17
|
-
"prod": "
|
|
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
|
|
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": "^
|
|
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": "^
|
|
48
|
+
"rate-limiter-flexible": "^5.0.0",
|
|
49
49
|
"redis": "^4.3.1",
|
|
50
50
|
"winston": "^3.3.3",
|
|
51
|
-
"winston-transport-sentry-node": "^
|
|
51
|
+
"winston-transport-sentry-node": "^3.0.0",
|
|
52
52
|
"yup": "^1.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@vitest/coverage-v8": "^
|
|
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.
|
|
61
|
-
"husky": "^
|
|
60
|
+
"eslint-plugin-vitest": "^0.4.0",
|
|
61
|
+
"husky": "^9.0.0",
|
|
62
62
|
"lint-staged": "^15.0.0",
|
|
63
|
-
"mongodb-memory-server": "^
|
|
64
|
-
"nodemon": "^3.0.1",
|
|
63
|
+
"mongodb-memory-server": "^10.0.0",
|
|
65
64
|
"prettier": "^3.0.0",
|
|
66
|
-
"vitest": "^
|
|
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:
|
|
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
|
|
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
|
|
35
|
+
export default Cache;
|
package/services/cache/Cache.js
CHANGED
|
@@ -69,12 +69,14 @@ class Cache extends Base {
|
|
|
69
69
|
return Promise.reject(e);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
this.redisClient.
|
|
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
|
|
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.
|
|
52
|
+
this.httpServer = http.createServer(this.express);
|
|
61
53
|
|
|
62
54
|
const listener = this.httpServer.listen(
|
|
63
55
|
httpConfig.port,
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
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 {
|
|
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 ||
|
package/tests/setupVitest.js
CHANGED
|
@@ -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 ||
|