@adaptivestone/framework 4.4.0 → 4.5.0

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ### 4.5.0
2
+
3
+ [NEW] Now getSuper() available as a method on monsgoose models
4
+ [UPDATE] Update rate-limiter-flexible to v3
5
+ [UPDATE] Update test runner to suport ESM. In case problem with test please copy babel.config.js from framework to your project directory
6
+
1
7
  ### 4.4.0
2
8
 
3
9
  [NEW] New method to grab url of server it testing enviroument global.server.testingGetUrl("/some/endpoint")
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
3
+ };
@@ -28,26 +28,29 @@ class ControllerManager extends Base {
28
28
  }
29
29
  return 0;
30
30
  });
31
- // const controllers = [];
31
+ const controllers = [];
32
32
  for (const controller of controllersToLoad) {
33
33
  // TODO wait until https://github.com/nodejs/node/issues/35889
34
- // controllers.push(
35
- // import(controller.path).then(({ default: ControllerModule }) => {
36
- // eslint-disable-next-line import/no-dynamic-require, global-require
37
- const ControllerModule = require(controller.path);
38
- const contollerName = ControllerModule.name.toLowerCase();
39
- let prefix = path.dirname(controller.file);
40
- if (prefix === '.') {
41
- prefix = '';
42
- }
43
- const controllePath = prefix
44
- ? `${prefix}/${contollerName}`
45
- : contollerName;
46
- this.controllers[controllePath] = new ControllerModule(this.app, prefix);
47
- // }),
48
- // );
34
+ controllers.push(
35
+ import(controller.path).then(({ default: ControllerModule }) => {
36
+ // eslint-disable-next-line import/no-dynamic-require, global-require
37
+ // const ControllerModule = require(controller.path);
38
+ const contollerName = ControllerModule.name.toLowerCase();
39
+ let prefix = path.dirname(controller.file);
40
+ if (prefix === '.') {
41
+ prefix = '';
42
+ }
43
+ const controllePath = prefix
44
+ ? `${prefix}/${contollerName}`
45
+ : contollerName;
46
+ this.controllers[controllePath] = new ControllerModule(
47
+ this.app,
48
+ prefix,
49
+ );
50
+ }),
51
+ );
49
52
  }
50
- // await Promise.all(controllers);
53
+ await Promise.all(controllers);
51
54
  }
52
55
 
53
56
  static get loggerGroup() {
package/models/User.js CHANGED
@@ -88,8 +88,8 @@ class User extends AbstractModel {
88
88
  const scryptAsync = promisify(scrypt);
89
89
  const data = await scryptAsync(
90
90
  this.email + Date.now(),
91
- this.constructor.getSuper().saltSecret,
92
- this.constructor.getSuper().hashRounds,
91
+ this.getSuper().saltSecret,
92
+ this.getSuper().hashRounds,
93
93
  );
94
94
  const token = data.toString('base64url');
95
95
  this.sessionTokens.push({ token, valid: timestamp });
@@ -138,8 +138,8 @@ class User extends AbstractModel {
138
138
  const scryptAsync = promisify(scrypt);
139
139
  const data = await scryptAsync(
140
140
  userMongoose.email + Date.now(),
141
- userMongoose.constructor.getSuper().saltSecret,
142
- userMongoose.constructor.getSuper().hashRounds,
141
+ userMongoose.getSuper().saltSecret,
142
+ userMongoose.getSuper().hashRounds,
143
143
  );
144
144
  const token = data.toString('base64url');
145
145
  // if (err) {
@@ -177,7 +177,7 @@ class User extends AbstractModel {
177
177
  const passwordRecoveryToken =
178
178
  await User.generateUserPasswordRecoveryToken(this);
179
179
  const mail = new Mailer(
180
- this.constructor.getSuper().app,
180
+ this.getSuper().app,
181
181
  'recovery',
182
182
  {
183
183
  link: `${i18n.language}/auth/recovery?password_recovery_token=${passwordRecoveryToken.token}`,
@@ -194,8 +194,8 @@ class User extends AbstractModel {
194
194
  const scryptAsync = promisify(scrypt);
195
195
  const data = await scryptAsync(
196
196
  userMongoose.email + Date.now(),
197
- userMongoose.constructor.getSuper().saltSecret,
198
- userMongoose.constructor.getSuper().hashRounds,
197
+ userMongoose.getSuper().saltSecret,
198
+ userMongoose.getSuper().hashRounds,
199
199
  );
200
200
  const token = data.toString('base64url');
201
201
  // if (err) {
@@ -243,7 +243,7 @@ class User extends AbstractModel {
243
243
  async sendVerificationEmail(i18n) {
244
244
  const verificationToken = await User.generateUserVerificationToken(this);
245
245
  const mail = new Mailer(
246
- this.constructor.getSuper().app,
246
+ this.getSuper().app,
247
247
  'verification',
248
248
  {
249
249
  link: `${i18n.language}/auth/login?verification_token=${verificationToken.token}`,
@@ -14,6 +14,7 @@ class AbstractModel extends Base {
14
14
  this.mongooseSchema.set('minimize', false);
15
15
  this.mongooseSchema.loadClass(this.constructor);
16
16
  this.mongooseSchema.statics.getSuper = () => this;
17
+ this.mongooseSchema.methods.getSuper = () => this;
17
18
  this.initHooks();
18
19
  this.mongooseModel = mongoose.model(
19
20
  this.constructor.name,
@@ -48,10 +48,10 @@ class Cli extends Base {
48
48
  return false;
49
49
  }
50
50
  // TODO wait until https://github.com/nodejs/node/issues/35889
51
- // const { default: Command } = await import(this.commands[command]);
51
+ const { default: Command } = await import(this.commands[command]);
52
52
 
53
53
  // eslint-disable-next-line import/no-dynamic-require, global-require
54
- const Command = require(this.commands[command]);
54
+ // const Command = require(this.commands[command]);
55
55
 
56
56
  const c = new Command(this.app, this.commands, args);
57
57
  let result = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptivestone/framework",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "engines": {
@@ -56,13 +56,14 @@
56
56
  "nodemailer-sendmail-transport": "^1.0.2",
57
57
  "nodemailer-stub-transport": "^1.1.0",
58
58
  "pug": "^3.0.2",
59
- "rate-limiter-flexible": "^2.2.4",
59
+ "rate-limiter-flexible": "^3.0.0",
60
60
  "redis": "^4.3.1",
61
61
  "winston": "^3.3.3",
62
62
  "winston-transport-sentry-node": "^2.0.0",
63
63
  "yup": "^1.0.0"
64
64
  },
65
65
  "devDependencies": {
66
+ "@babel/preset-env": "^7.22.15",
66
67
  "eslint": "^8.0.0",
67
68
  "eslint-config-airbnb-base": "^15.0.0",
68
69
  "eslint-config-prettier": "^9.0.0",
package/server.js CHANGED
@@ -52,17 +52,17 @@ class Server {
52
52
  */
53
53
  async startServer(callbackBefore404 = async () => Promise.resolve()) {
54
54
  // eslint-disable-next-line global-require
55
- const HttpServer = require('./services/http/HttpServer');
55
+ // const HttpServer = require('./services/http/HttpServer');
56
56
  // eslint-disable-next-line global-require
57
- const ControllerManager = require('./controllers/index');
57
+ // const ControllerManager = require('./controllers/index');
58
58
  // TODO wait until https://github.com/nodejs/node/issues/35889
59
- // const [{ default: HttpServer }, { default: ControllerManager }] =
60
- // await Promise.all([
61
- // // eslint-disable-next-line import/extensions
62
- // import('./services/http/HttpServer.js'), // Speed optimisation
63
- // // eslint-disable-next-line import/extensions
64
- // import('./controllers/index.js'), // Speed optimisation
65
- // ]);
59
+ const [{ default: HttpServer }, { default: ControllerManager }] =
60
+ await Promise.all([
61
+ // eslint-disable-next-line import/extensions
62
+ import('./services/http/HttpServer.js'), // Speed optimisation
63
+ // eslint-disable-next-line import/extensions
64
+ import('./controllers/index.js'), // Speed optimisation
65
+ ]);
66
66
 
67
67
  this.addErrorHandling();
68
68
 
@@ -16,10 +16,9 @@ class RateLimiter extends AbstractMiddleware {
16
16
 
17
17
  constructor(app, params) {
18
18
  super(app, params);
19
- const routeParams = params;
20
19
  const limiterOptions = this.app.getConfig('rateLimiter');
21
20
 
22
- this.finalOptions = merge(limiterOptions, routeParams);
21
+ this.finalOptions = merge(limiterOptions, params);
23
22
  this.limiter = null;
24
23
 
25
24
  switch (this.finalOptions.driver) {
@@ -32,7 +31,7 @@ class RateLimiter extends AbstractMiddleware {
32
31
  break;
33
32
 
34
33
  case 'mongo':
35
- this.limiter = RateLimiterMongo({
34
+ this.limiter = new RateLimiterMongo({
36
35
  storeClient: mongoose.connection,
37
36
  ...this.finalOptions.limiterOptions,
38
37
  });
@@ -50,7 +49,6 @@ class RateLimiter extends AbstractMiddleware {
50
49
  const redisConfig = this.app.getConfig('redis');
51
50
  const redisClient = redis.createClient({
52
51
  url: redisConfig.url,
53
- legacyMode: true,
54
52
  });
55
53
 
56
54
  // TODO: change it
@@ -71,6 +69,7 @@ class RateLimiter extends AbstractMiddleware {
71
69
 
72
70
  return new RateLimiterRedis({
73
71
  storeClient: redisClient,
72
+ useRedisPackage: true,
74
73
  ...this.finalOptions.limiterOptions,
75
74
  });
76
75
  }
@@ -91,7 +90,7 @@ class RateLimiter extends AbstractMiddleware {
91
90
 
92
91
  if (request && request.length) {
93
92
  request.forEach((val) => {
94
- if (req.body[val]) {
93
+ if (req.body && req.body[val]) {
95
94
  key.push(req.body[val]);
96
95
  }
97
96
  // if (req.appInfo.request && req.appInfo.request[val]) {
@@ -108,6 +107,7 @@ class RateLimiter extends AbstractMiddleware {
108
107
  this.logger.info(
109
108
  `RateLimmiter not inited correclty! Please check init logs `,
110
109
  );
110
+ return res.status(500).send('');
111
111
  }
112
112
 
113
113
  const { namespace } = this.app.getConfig('redis');
@@ -119,7 +119,6 @@ class RateLimiter extends AbstractMiddleware {
119
119
  .catch(() => {
120
120
  this.logger.warn(`Too many requests. Consume key: ${consumeKey}`);
121
121
  });
122
-
123
122
  if (consumeResult) {
124
123
  return next();
125
124
  }
@@ -1,6 +1,22 @@
1
+ const { setTimeout } = require('node:timers/promises');
1
2
  const RateLimiter = require('./RateLimiter');
2
3
 
4
+ let mongoRateLimiter;
5
+
3
6
  describe('rate limiter methods', () => {
7
+ beforeAll(() => {
8
+ mongoRateLimiter = new RateLimiter(global.server.app, {
9
+ driver: 'mongo',
10
+ limiterOptions: {
11
+ keyPrefix: `mongo_${Date.now()}`,
12
+ },
13
+ });
14
+ });
15
+
16
+ afterAll(async () => {
17
+ // we need to wait because redis mongo ask mongo to create indexes
18
+ await setTimeout(200);
19
+ });
4
20
  it('have description fields', async () => {
5
21
  expect.assertions(1);
6
22
  const middleware = new RateLimiter(global.server.app, {
@@ -47,4 +63,167 @@ describe('rate limiter methods', () => {
47
63
 
48
64
  expect(res).toBe('192.168.0.0__someId');
49
65
  });
66
+
67
+ it('generateConsumeKey with request works correctly', async () => {
68
+ expect.assertions(1);
69
+
70
+ const redisRateLimiter = new RateLimiter(global.server.app, {
71
+ driver: 'redis',
72
+ consumeKeyComponents: {
73
+ request: ['email'],
74
+ },
75
+ });
76
+
77
+ const res = await redisRateLimiter.gerenateConsumeKey({
78
+ ip: '192.168.0.0',
79
+ body: {
80
+ email: 'foo@example.com',
81
+ },
82
+ });
83
+
84
+ expect(res).toBe('192.168.0.0__foo@example.com');
85
+ });
86
+
87
+ it('middleware without driver should fail', async () => {
88
+ expect.assertions(2);
89
+ const rateLimiter = new RateLimiter(global.server.app, {
90
+ driver: 'unknown',
91
+ });
92
+ const nextFunction = jest.fn(() => {});
93
+ const req = {
94
+ appInfo: {},
95
+ };
96
+ let status;
97
+ let isSend;
98
+ await rateLimiter.middleware(
99
+ req,
100
+ {
101
+ status(statusCode) {
102
+ status = statusCode;
103
+ return this;
104
+ },
105
+ send() {
106
+ isSend = true;
107
+ },
108
+ },
109
+ nextFunction,
110
+ );
111
+ expect(status).toBe(500);
112
+ expect(isSend).toBe(true);
113
+ });
114
+
115
+ const makeOneRequest = async ({ rateLimiter, driver, request }) => {
116
+ let realRateLimiter = rateLimiter;
117
+ if (!realRateLimiter) {
118
+ realRateLimiter = new RateLimiter(global.server.app, {
119
+ driver,
120
+ });
121
+ }
122
+ const req = {
123
+ appInfo: {},
124
+ ...request,
125
+ };
126
+ let status;
127
+ let isSend = false;
128
+ let isNextCalled = false;
129
+ await realRateLimiter.middleware(
130
+ req,
131
+ {
132
+ status(statusCode) {
133
+ status = statusCode;
134
+ return this;
135
+ },
136
+ send() {
137
+ isSend = true;
138
+ },
139
+ },
140
+ () => {
141
+ isNextCalled = true;
142
+ },
143
+ );
144
+ return { status, isSend, isNextCalled };
145
+ };
146
+
147
+ it('middleware should works with a mongo drivers', async () => {
148
+ expect.assertions(1);
149
+ const { isNextCalled } = await makeOneRequest({
150
+ rateLimiter: mongoRateLimiter,
151
+ request: { ip: '10.10.0.1' },
152
+ });
153
+ expect(isNextCalled).toBe(true);
154
+ });
155
+
156
+ it('middleware should works with a memory drivers', async () => {
157
+ expect.assertions(1);
158
+ const { isNextCalled } = await makeOneRequest({
159
+ driver: 'memory',
160
+ request: { ip: '10.10.0.1' },
161
+ });
162
+ expect(isNextCalled).toBe(true);
163
+ });
164
+
165
+ it('middleware should works with a redis drivers', async () => {
166
+ expect.assertions(1);
167
+ const { isNextCalled } = await makeOneRequest({
168
+ driver: 'redis',
169
+ request: { ip: '10.10.0.1' },
170
+ });
171
+ expect(isNextCalled).toBe(true);
172
+ });
173
+
174
+ it('middleware should rate limits for us. mongo driver', async () => {
175
+ expect.assertions(2);
176
+
177
+ const middlewares = Array.from({ length: 20 }, () =>
178
+ makeOneRequest({ rateLimiter: mongoRateLimiter }),
179
+ );
180
+
181
+ const data = await Promise.all(middlewares);
182
+
183
+ const status = data.find((obj) => obj.status === 429);
184
+ const isSend = data.find((obj) => obj.isSend);
185
+
186
+ expect(status.status).toBe(429);
187
+ expect(isSend.isSend).toBe(true);
188
+ });
189
+
190
+ it('middleware should rate limits for us. memory driver', async () => {
191
+ expect.assertions(2);
192
+
193
+ const rateLimiter = new RateLimiter(global.server.app, {
194
+ driver: 'memory',
195
+ });
196
+
197
+ const middlewares = Array.from({ length: 20 }, () =>
198
+ makeOneRequest({ rateLimiter }),
199
+ );
200
+
201
+ const data = await Promise.all(middlewares);
202
+
203
+ const status = data.find((obj) => obj.status === 429);
204
+ const isSend = data.find((obj) => obj.isSend);
205
+
206
+ expect(status.status).toBe(429);
207
+ expect(isSend.isSend).toBe(true);
208
+ });
209
+
210
+ it('middleware should rate limits for us. redis driver', async () => {
211
+ expect.assertions(2);
212
+
213
+ const rateLimiter = new RateLimiter(global.server.app, {
214
+ driver: 'redis',
215
+ });
216
+
217
+ const middlewares = Array.from({ length: 20 }, () =>
218
+ makeOneRequest({ rateLimiter }),
219
+ );
220
+
221
+ const data = await Promise.all(middlewares);
222
+
223
+ const status = data.find((obj) => obj.status === 429);
224
+ const isSend = data.find((obj) => obj.isSend);
225
+
226
+ expect(status.status).toBe(429);
227
+ expect(isSend.isSend).toBe(true);
228
+ });
50
229
  });
package/tests/setup.js CHANGED
@@ -14,7 +14,7 @@ jest.setTimeout(1000000);
14
14
  beforeAll(async () => {
15
15
  mongoMemoryServerInstance = await MongoMemoryReplSet.create({
16
16
  // binary: { version: '4.4.6' },
17
- replSet: { storageEngine: 'wiredTiger' },
17
+ replSet: { count: 1, storageEngine: 'wiredTiger' },
18
18
  });
19
19
  await mongoMemoryServerInstance.waitUntilRunning();
20
20
  process.env.LOGGER_CONSOLE_LEVEL = 'error';