@adaptivestone/framework 4.3.1 → 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 +10 -0
- package/babel.config.js +3 -0
- package/controllers/Auth.test.js +258 -132
- package/controllers/Home.test.js +3 -4
- package/controllers/index.js +20 -17
- package/models/User.js +8 -8
- package/modules/AbstractModel.js +1 -0
- package/modules/BaseCli.js +2 -2
- package/package.json +6 -5
- package/server.js +9 -9
- package/services/http/middleware/RateLimiter.js +5 -6
- package/services/http/middleware/RateLimiter.test.js +179 -0
- package/tests/setup.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
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
|
+
|
|
7
|
+
### 4.4.0
|
|
8
|
+
|
|
9
|
+
[NEW] New method to grab url of server it testing enviroument global.server.testingGetUrl("/some/endpoint")
|
|
10
|
+
|
|
1
11
|
### 4.3.1
|
|
2
12
|
|
|
3
13
|
[UPDATE] Yup file validator update. As formidable now return all fields as an array
|
package/babel.config.js
ADDED
package/controllers/Auth.test.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const request = require('supertest');
|
|
2
|
-
|
|
3
1
|
const userEmail = 'testing@test.com';
|
|
4
2
|
const userPassword = 'SuperNiceSecret123$';
|
|
5
3
|
|
|
@@ -9,85 +7,136 @@ describe('auth', () => {
|
|
|
9
7
|
describe('registration', () => {
|
|
10
8
|
it('code NOT able to create user with wrong email', async () => {
|
|
11
9
|
expect.assertions(1);
|
|
12
|
-
const { status } = await
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
const { status } = await fetch(
|
|
11
|
+
global.server.testingGetUrl('/auth/register'),
|
|
12
|
+
{
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
body: JSON.stringify({
|
|
18
|
+
email: 'bad email',
|
|
19
|
+
password: userPassword,
|
|
20
|
+
nickName: 'test',
|
|
21
|
+
}),
|
|
22
|
+
},
|
|
23
|
+
).catch(() => {});
|
|
24
|
+
|
|
19
25
|
expect(status).toBe(400);
|
|
20
26
|
});
|
|
21
27
|
|
|
22
28
|
it('can create user', async () => {
|
|
23
29
|
expect.assertions(1);
|
|
24
|
-
const { status } = await
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
const { status } = await fetch(
|
|
31
|
+
global.server.testingGetUrl('/auth/register'),
|
|
32
|
+
{
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
body: JSON.stringify({
|
|
38
|
+
email: userEmail,
|
|
39
|
+
password: userPassword,
|
|
40
|
+
nickName: 'test',
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
);
|
|
31
44
|
expect(status).toBe(201);
|
|
32
45
|
});
|
|
33
46
|
|
|
34
47
|
it('can not create user with the same nickname', async () => {
|
|
35
48
|
expect.assertions(1);
|
|
36
|
-
await
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
await fetch(global.server.testingGetUrl('/auth/register'), {
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: {
|
|
52
|
+
'Content-type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify({
|
|
39
55
|
email: userEmail,
|
|
40
56
|
password: userPassword,
|
|
41
57
|
nickName: 'test',
|
|
42
|
-
})
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const { status } = await fetch(
|
|
62
|
+
global.server.testingGetUrl('/auth/register'),
|
|
63
|
+
{
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: {
|
|
66
|
+
'Content-type': 'application/json',
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
email: userEmail2,
|
|
70
|
+
password: '123',
|
|
71
|
+
nickName: 'test',
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
).catch(() => {});
|
|
43
75
|
|
|
44
|
-
const { status } = await request(global.server.app.httpServer.express)
|
|
45
|
-
.post('/auth/register')
|
|
46
|
-
.send({
|
|
47
|
-
email: userEmail2,
|
|
48
|
-
password: '123',
|
|
49
|
-
nickName: 'test',
|
|
50
|
-
});
|
|
51
76
|
expect(status).toBe(400);
|
|
52
77
|
});
|
|
53
78
|
|
|
54
79
|
it('can NOT create SAME user', async () => {
|
|
55
80
|
expect.assertions(1);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
81
|
+
|
|
82
|
+
const { status } = await fetch(
|
|
83
|
+
global.server.testingGetUrl('/auth/register'),
|
|
84
|
+
{
|
|
85
|
+
method: 'POST',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-type': 'application/json',
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
email: userEmail,
|
|
91
|
+
password: userPassword,
|
|
92
|
+
nickName: 'test',
|
|
93
|
+
}),
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
63
97
|
expect(status).toBe(400);
|
|
64
98
|
});
|
|
65
99
|
});
|
|
66
100
|
|
|
67
101
|
describe('login', () => {
|
|
68
|
-
it('can NOT login with normal creds and not
|
|
102
|
+
it('can NOT login with normal creds and not verified email', async () => {
|
|
69
103
|
expect.assertions(1);
|
|
70
|
-
const { status } = await
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
const { status } = await fetch(
|
|
105
|
+
global.server.testingGetUrl('/auth/login'),
|
|
106
|
+
{
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-type': 'application/json',
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
email: userEmail,
|
|
113
|
+
password: userPassword,
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
).catch(() => {});
|
|
117
|
+
|
|
76
118
|
expect(status).toBe(400);
|
|
77
119
|
});
|
|
78
120
|
|
|
79
121
|
it('can NOT login with WRONG creds', async () => {
|
|
80
122
|
expect.assertions(1);
|
|
81
|
-
const { status } = await
|
|
82
|
-
.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
const { status } = await fetch(
|
|
124
|
+
global.server.testingGetUrl('/auth/login'),
|
|
125
|
+
{
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: {
|
|
128
|
+
'Content-type': 'application/json',
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
email: 'test@test.by',
|
|
132
|
+
password: 'noPassword$',
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
).catch(() => {});
|
|
87
136
|
expect(status).toBe(400);
|
|
88
137
|
});
|
|
89
138
|
|
|
90
|
-
it('can login with normal creds and
|
|
139
|
+
it('can login with normal creds and verified email', async () => {
|
|
91
140
|
expect.assertions(2);
|
|
92
141
|
|
|
93
142
|
const user = await global.server.app
|
|
@@ -96,16 +145,21 @@ describe('auth', () => {
|
|
|
96
145
|
user.isVerified = true;
|
|
97
146
|
await user.save();
|
|
98
147
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
const response = await fetch(global.server.testingGetUrl('/auth/login'), {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: {
|
|
151
|
+
'Content-type': 'application/json',
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify({
|
|
104
154
|
email: userEmail,
|
|
105
155
|
password: userPassword,
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const responseBody = await response.json();
|
|
160
|
+
|
|
161
|
+
expect(response.status).toBe(200);
|
|
162
|
+
expect(responseBody.token).toBeDefined();
|
|
109
163
|
});
|
|
110
164
|
});
|
|
111
165
|
|
|
@@ -126,9 +180,14 @@ describe('auth', () => {
|
|
|
126
180
|
|
|
127
181
|
await user.save();
|
|
128
182
|
|
|
129
|
-
const { status } = await
|
|
130
|
-
global.server.
|
|
131
|
-
|
|
183
|
+
const { status } = await fetch(
|
|
184
|
+
`${global.server.testingGetUrl(
|
|
185
|
+
'/auth/verify',
|
|
186
|
+
)}?verification_token=testToken`,
|
|
187
|
+
{
|
|
188
|
+
method: 'POST',
|
|
189
|
+
},
|
|
190
|
+
);
|
|
132
191
|
|
|
133
192
|
const { isVerified } = await global.server.app.getModel('User').findOne({
|
|
134
193
|
email: 'Test@gmail.com',
|
|
@@ -154,9 +213,14 @@ describe('auth', () => {
|
|
|
154
213
|
|
|
155
214
|
await user.save();
|
|
156
215
|
|
|
157
|
-
const { status } = await
|
|
158
|
-
global.server.
|
|
159
|
-
|
|
216
|
+
const { status } = await fetch(
|
|
217
|
+
`${global.server.testingGetUrl(
|
|
218
|
+
'/auth/verify',
|
|
219
|
+
)}?verification_token=testToken123wrong`,
|
|
220
|
+
{
|
|
221
|
+
method: 'POST',
|
|
222
|
+
},
|
|
223
|
+
);
|
|
160
224
|
|
|
161
225
|
const { isVerified } = await global.server.app.getModel('User').findOne({
|
|
162
226
|
email: 'Test423@gmail.com',
|
|
@@ -168,21 +232,37 @@ describe('auth', () => {
|
|
|
168
232
|
|
|
169
233
|
it('can NOT send recovery to not exist email', async () => {
|
|
170
234
|
expect.assertions(1);
|
|
171
|
-
const { status } = await
|
|
172
|
-
.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
235
|
+
const { status } = await fetch(
|
|
236
|
+
global.server.testingGetUrl('/auth/send-recovery-email'),
|
|
237
|
+
{
|
|
238
|
+
method: 'POST',
|
|
239
|
+
headers: {
|
|
240
|
+
'Content-type': 'application/json',
|
|
241
|
+
},
|
|
242
|
+
body: JSON.stringify({
|
|
243
|
+
email: 'notExists@gmail.com',
|
|
244
|
+
}),
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
176
248
|
expect(status).toBe(400);
|
|
177
249
|
});
|
|
178
250
|
|
|
179
251
|
it('can send recovery to exist email', async () => {
|
|
180
252
|
expect.assertions(1);
|
|
181
|
-
const { status } = await
|
|
182
|
-
.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
253
|
+
const { status } = await fetch(
|
|
254
|
+
global.server.testingGetUrl('/auth/send-recovery-email'),
|
|
255
|
+
{
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: {
|
|
258
|
+
'Content-type': 'application/json',
|
|
259
|
+
},
|
|
260
|
+
body: JSON.stringify({
|
|
261
|
+
email: userEmail,
|
|
262
|
+
}),
|
|
263
|
+
},
|
|
264
|
+
);
|
|
265
|
+
|
|
186
266
|
expect(status).toBe(200);
|
|
187
267
|
});
|
|
188
268
|
|
|
@@ -203,12 +283,19 @@ describe('auth', () => {
|
|
|
203
283
|
|
|
204
284
|
await user.save();
|
|
205
285
|
|
|
206
|
-
const { status } = await
|
|
207
|
-
.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
286
|
+
const { status } = await fetch(
|
|
287
|
+
global.server.testingGetUrl('/auth/recover-password'),
|
|
288
|
+
{
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: {
|
|
291
|
+
'Content-type': 'application/json',
|
|
292
|
+
},
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
password: 'newPass',
|
|
295
|
+
passwordRecoveryToken: 'superPassword',
|
|
296
|
+
}),
|
|
297
|
+
},
|
|
298
|
+
);
|
|
212
299
|
|
|
213
300
|
expect(status).toBe(200);
|
|
214
301
|
});
|
|
@@ -230,94 +317,133 @@ describe('auth', () => {
|
|
|
230
317
|
|
|
231
318
|
await user.save();
|
|
232
319
|
|
|
233
|
-
const { status } = await
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
320
|
+
const { status } = await fetch(
|
|
321
|
+
global.server.testingGetUrl('/auth/recover-password'),
|
|
322
|
+
{
|
|
323
|
+
method: 'POST',
|
|
324
|
+
headers: {
|
|
325
|
+
'Content-type': 'application/json',
|
|
326
|
+
},
|
|
327
|
+
body: JSON.stringify({
|
|
328
|
+
password: 'newPass',
|
|
329
|
+
passwordRecoveryToken: '13123',
|
|
330
|
+
}),
|
|
331
|
+
},
|
|
332
|
+
);
|
|
239
333
|
|
|
240
334
|
expect(status).toBe(400);
|
|
241
335
|
});
|
|
242
336
|
|
|
243
|
-
it('can login with normal creds and
|
|
337
|
+
it('can login with normal creds and NOT verifyed email if option isAuthWithVefificationFlow is set', async () => {
|
|
244
338
|
expect.assertions(4);
|
|
245
339
|
|
|
246
|
-
const { status } = await
|
|
247
|
-
.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
340
|
+
const { status } = await fetch(
|
|
341
|
+
global.server.testingGetUrl('/auth/register'),
|
|
342
|
+
{
|
|
343
|
+
method: 'POST',
|
|
344
|
+
headers: {
|
|
345
|
+
'Content-type': 'application/json',
|
|
346
|
+
},
|
|
347
|
+
body: JSON.stringify({
|
|
348
|
+
email: userEmail2,
|
|
349
|
+
password: userPassword,
|
|
350
|
+
}),
|
|
351
|
+
},
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const { status: status2 } = await fetch(
|
|
355
|
+
global.server.testingGetUrl('/auth/login'),
|
|
356
|
+
{
|
|
357
|
+
method: 'POST',
|
|
358
|
+
headers: {
|
|
359
|
+
'Content-type': 'application/json',
|
|
360
|
+
},
|
|
361
|
+
body: JSON.stringify({
|
|
362
|
+
email: userEmail2,
|
|
363
|
+
password: userPassword,
|
|
364
|
+
}),
|
|
365
|
+
},
|
|
366
|
+
);
|
|
261
367
|
|
|
262
368
|
global.server.app.updateConfig('auth', {
|
|
263
369
|
isAuthWithVefificationFlow: false,
|
|
264
370
|
});
|
|
265
371
|
|
|
266
|
-
const
|
|
267
|
-
global.server.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
372
|
+
const response3 = await fetch(
|
|
373
|
+
global.server.testingGetUrl('/auth/login'),
|
|
374
|
+
{
|
|
375
|
+
method: 'POST',
|
|
376
|
+
headers: {
|
|
377
|
+
'Content-type': 'application/json',
|
|
378
|
+
},
|
|
379
|
+
body: JSON.stringify({
|
|
380
|
+
email: userEmail2,
|
|
381
|
+
password: userPassword,
|
|
382
|
+
}),
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const responseBody3 = await response3.json();
|
|
274
387
|
|
|
275
388
|
expect(status).toBe(201);
|
|
276
389
|
expect(status2).toBe(400);
|
|
277
|
-
expect(
|
|
278
|
-
expect(
|
|
390
|
+
expect(response3.status).toBe(200);
|
|
391
|
+
expect(responseBody3.token).toBeDefined();
|
|
279
392
|
});
|
|
280
393
|
});
|
|
281
394
|
|
|
282
395
|
it('can user send verification', async () => {
|
|
283
396
|
expect.assertions(1);
|
|
284
397
|
|
|
285
|
-
const { status } = await
|
|
286
|
-
.
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
398
|
+
const { status } = await fetch(
|
|
399
|
+
global.server.testingGetUrl('/auth/send-verification'),
|
|
400
|
+
{
|
|
401
|
+
method: 'POST',
|
|
402
|
+
headers: {
|
|
403
|
+
'Content-type': 'application/json',
|
|
404
|
+
},
|
|
405
|
+
body: JSON.stringify({
|
|
406
|
+
email: userEmail2,
|
|
407
|
+
}),
|
|
408
|
+
},
|
|
409
|
+
);
|
|
410
|
+
|
|
290
411
|
expect(status).toBe(200);
|
|
291
412
|
});
|
|
292
413
|
|
|
293
414
|
it('can not user send verification to wrong email', async () => {
|
|
294
415
|
expect.assertions(1);
|
|
295
416
|
|
|
296
|
-
const { status } = await
|
|
297
|
-
.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
417
|
+
const { status } = await fetch(
|
|
418
|
+
global.server.testingGetUrl('/auth/send-verification'),
|
|
419
|
+
{
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers: {
|
|
422
|
+
'Content-type': 'application/json',
|
|
423
|
+
},
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
email: 'wrong@gmail.com',
|
|
426
|
+
}),
|
|
427
|
+
},
|
|
428
|
+
);
|
|
429
|
+
|
|
301
430
|
expect(status).toBe(400);
|
|
302
431
|
});
|
|
303
432
|
|
|
304
433
|
describe('rate limiter', () => {
|
|
305
|
-
it('
|
|
434
|
+
it('should receive 429 on rate limit exceeded', async () => {
|
|
306
435
|
expect.assertions(1);
|
|
307
|
-
const resultsPromise = [];
|
|
308
436
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
);
|
|
315
|
-
}
|
|
437
|
+
const requests = Array.from({ length: 11 }, () =>
|
|
438
|
+
fetch(global.server.testingGetUrl('/auth/logout'), {
|
|
439
|
+
method: 'POST',
|
|
440
|
+
}),
|
|
441
|
+
);
|
|
316
442
|
|
|
317
|
-
const
|
|
318
|
-
const
|
|
443
|
+
const responses = await Promise.all(requests);
|
|
444
|
+
const statusCodes = responses.map((response) => response.status);
|
|
319
445
|
|
|
320
|
-
expect(
|
|
446
|
+
expect(statusCodes).toContain(429);
|
|
321
447
|
});
|
|
322
448
|
});
|
|
323
449
|
});
|
package/controllers/Home.test.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
const request = require('supertest');
|
|
2
|
-
|
|
3
1
|
describe('home', () => {
|
|
4
2
|
it('can open home have', async () => {
|
|
5
3
|
expect.assertions(1);
|
|
6
|
-
const { status } = await
|
|
7
|
-
|
|
4
|
+
const { status } = await fetch(global.server.testingGetUrl('/')).catch(
|
|
5
|
+
() => {},
|
|
8
6
|
);
|
|
7
|
+
|
|
9
8
|
expect(status).toBe(200);
|
|
10
9
|
});
|
|
11
10
|
});
|
package/controllers/index.js
CHANGED
|
@@ -28,26 +28,29 @@ class ControllerManager extends Base {
|
|
|
28
28
|
}
|
|
29
29
|
return 0;
|
|
30
30
|
});
|
|
31
|
-
|
|
31
|
+
const controllers = [];
|
|
32
32
|
for (const controller of controllersToLoad) {
|
|
33
33
|
// TODO wait until https://github.com/nodejs/node/issues/35889
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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.
|
|
92
|
-
this.
|
|
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.
|
|
142
|
-
userMongoose.
|
|
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.
|
|
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.
|
|
198
|
-
userMongoose.
|
|
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.
|
|
246
|
+
this.getSuper().app,
|
|
247
247
|
'verification',
|
|
248
248
|
{
|
|
249
249
|
link: `${i18n.language}/auth/login?verification_token=${verificationToken.token}`,
|
package/modules/AbstractModel.js
CHANGED
|
@@ -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,
|
package/modules/BaseCli.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "Adaptive stone node js framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"engines": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"prepare": "husky install",
|
|
23
23
|
"cli": "node cliCommand",
|
|
24
24
|
"benchmark": "h2load -n 10000 -c 50 -p 'http/1.1' http://localhost:3300/",
|
|
25
|
-
"benchmark2": "h2load -n 10000 -c 50 https://localhost:3300/"
|
|
25
|
+
"benchmark2": "h2load -n 10000 -c 50 https://localhost:3300/",
|
|
26
|
+
"redis:docker": "docker run --rm -p 6379:6379 redis"
|
|
26
27
|
},
|
|
27
28
|
"jest": {
|
|
28
29
|
"setupFilesAfterEnv": [
|
|
@@ -55,13 +56,14 @@
|
|
|
55
56
|
"nodemailer-sendmail-transport": "^1.0.2",
|
|
56
57
|
"nodemailer-stub-transport": "^1.1.0",
|
|
57
58
|
"pug": "^3.0.2",
|
|
58
|
-
"rate-limiter-flexible": "^
|
|
59
|
+
"rate-limiter-flexible": "^3.0.0",
|
|
59
60
|
"redis": "^4.3.1",
|
|
60
61
|
"winston": "^3.3.3",
|
|
61
62
|
"winston-transport-sentry-node": "^2.0.0",
|
|
62
63
|
"yup": "^1.0.0"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
66
|
+
"@babel/preset-env": "^7.22.15",
|
|
65
67
|
"eslint": "^8.0.0",
|
|
66
68
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
67
69
|
"eslint-config-prettier": "^9.0.0",
|
|
@@ -71,8 +73,7 @@
|
|
|
71
73
|
"lint-staged": "^14.0.0",
|
|
72
74
|
"mongodb-memory-server": "^8.0.2",
|
|
73
75
|
"nodemon": "^3.0.1",
|
|
74
|
-
"prettier": "^3.0.0"
|
|
75
|
-
"supertest": "^6.1.4"
|
|
76
|
+
"prettier": "^3.0.0"
|
|
76
77
|
},
|
|
77
78
|
"lint-staged": {
|
|
78
79
|
"**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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,
|
|
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';
|
|
@@ -46,6 +46,8 @@ beforeAll(async () => {
|
|
|
46
46
|
if (!global.testSetup) {
|
|
47
47
|
global.testSetup = {};
|
|
48
48
|
}
|
|
49
|
+
global.server.testingGetUrl = (urlPart) =>
|
|
50
|
+
`http://127.0.0.1:${global.server.getConfig('http').port}${urlPart}`;
|
|
49
51
|
if (!global.testSetup.disableUserCreate) {
|
|
50
52
|
const User = global.server.app.getModel('User');
|
|
51
53
|
global.user = await User.create({
|