@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.
- package/CHANGELOG.md +84 -0
- package/commands/CreateUser.js +4 -0
- package/commands/Documentation.js +4 -0
- package/commands/GenerateRandomBytes.js +19 -0
- package/commands/GetOpenApiJson.js +4 -0
- package/config/auth.js +4 -1
- package/config/ipDetector.js +14 -0
- package/controllers/Auth.js +2 -2
- package/controllers/Home.js +1 -1
- package/helpers/files.js +8 -8
- package/models/User.js +7 -1
- package/modules/AbstractCommand.js +1 -1
- package/modules/AbstractController.js +19 -19
- package/modules/AbstractModel.d.ts +48 -0
- package/modules/AbstractModel.js +16 -1
- package/modules/Base.d.ts +4 -4
- package/modules/Base.js +11 -1
- package/modules/BaseCli.js +23 -8
- package/package.json +10 -11
- package/server.js +1 -1
- package/services/cache/Cache.js +6 -4
- package/services/http/HttpServer.js +11 -9
- package/services/http/middleware/GetUserByToken.js +3 -2
- package/services/http/middleware/I18n.js +21 -22
- package/services/http/middleware/IpDetector.js +59 -0
- package/services/http/middleware/Pagination.js +3 -2
- package/services/http/middleware/RateLimiter.js +8 -2
- package/services/http/middleware/RequestLogger.js +3 -3
- package/services/http/middleware/RequestParser.js +1 -1
- package/services/validate/ValidateService.js +1 -1
- package/.eslintrc.cjs +0 -41
- package/commands/Generate.js +0 -14
- 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 -121
- package/services/http/middleware/Role.test.js +0 -93
- 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:
|
|
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(
|
|
30
|
+
i18next.use(BackendFS).init({
|
|
20
31
|
backend: {
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
23
|
-
maxLimit =
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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
|
|
13
|
+
const end = performance.now();
|
|
14
14
|
this.logger.info(
|
|
15
|
-
`Finished ${text}. Status: ${res.statusCode}.
|
|
15
|
+
`Finished ${text}. Status: ${res.statusCode}. [${(end - startTime).toFixed(2)} ms]`,
|
|
16
16
|
);
|
|
17
17
|
});
|
|
18
18
|
next();
|
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
|
-
};
|
package/commands/Generate.js
DELETED
|
@@ -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;
|