Skip to content

Commit a99da46

Browse files
committed
feat: custom phone challenge rate limiter
1 parent f96c611 commit a99da46

File tree

21 files changed

+3123
-2042
lines changed

21 files changed

+3123
-2042
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ RUN \
2424

2525
USER node
2626
COPY --chown=node:node . /src
27-
RUN pnpm install --offline --prod
27+
RUN pnpm install --prefer-offline --prod
2828

2929
CMD [ "./node_modules/.bin/mfleet" ]

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"dependencies": {
3232
"@faker-js/faker": "^9.2.0",
3333
"@fastify/deepmerge": "^2.0.0",
34+
"@google-cloud/recaptcha-enterprise": "^6.0.1",
3435
"@hapi/bell": "^13.0.2",
3536
"@hapi/boom": "^10.0.1",
3637
"@hapi/hapi": "^21.3.10",

pnpm-lock.yaml

Lines changed: 2470 additions & 1883 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schemas/challenge.json

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
{
2-
"$id": "challenge",
3-
"type": "object",
4-
"required": [
5-
"type",
6-
"username"
7-
],
8-
"properties": {
9-
"type": {
10-
"type": "string",
11-
"enum": ["email", "phone"]
12-
},
13-
"username": {
14-
"type": "string",
15-
"minLength": 1
16-
},
17-
"remoteip": {
18-
"type": "string",
19-
"format": "ipv4"
20-
},
21-
"metadata": {
22-
"type": "object"
23-
}
2+
"$id": "challenge",
3+
"type": "object",
4+
"required": [
5+
"type",
6+
"username"
7+
],
8+
"properties": {
9+
"captcha": {
10+
"$ref": "common.json#/definitions/captcha"
11+
},
12+
"metadata": {
13+
"type": "object"
14+
},
15+
"remoteip": {
16+
"$ref": "common.json#/definitions/remoteip"
17+
},
18+
"type": {
19+
"type": "string",
20+
"enum": [
21+
"email",
22+
"phone"
23+
]
24+
},
25+
"username": {
26+
"type": "string",
27+
"minLength": 1
2428
}
29+
}
2530
}

schemas/common.json

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,59 @@
189189
"description": "E-164. Only digits",
190190
"type": "string",
191191
"pattern": "^[0-9]{7,15}$"
192+
},
193+
"remoteip": {
194+
"type": "string",
195+
"oneOf": [
196+
{ "format": "ipv4" },
197+
{ "format": "ipv6" }
198+
]
199+
},
200+
"captcha": {
201+
"oneOf": [
202+
{
203+
"type": "object",
204+
"required": [
205+
"response",
206+
"remoteip"
207+
],
208+
"properties": {
209+
"response": {
210+
"type": "string",
211+
"minLength": 1
212+
},
213+
"remoteip": {
214+
"type": "string",
215+
"oneOf": [{
216+
"format": "ipv4"
217+
},
218+
{
219+
"format": "ipv6"
220+
}
221+
]
222+
},
223+
"secret": {
224+
"type": "string",
225+
"minLength": 1
226+
}
227+
}
228+
},
229+
{
230+
"type": "object",
231+
"required": [
232+
"type",
233+
"token"
234+
],
235+
"properties": {
236+
"type": {
237+
"const": "recaptcha"
238+
},
239+
"token": {
240+
"type": "string"
241+
}
242+
}
243+
}
244+
]
192245
}
193246
}
194247
}

schemas/disposable-password.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66
"challengeType"
77
],
88
"properties": {
9+
"captcha": {
10+
"$ref": "common.json#/definitions/captcha"
11+
},
912
"challengeType": {
1013
"default": "phone",
1114
"const": "phone"
1215
},
1316
"id": {
1417
"type": "string",
15-
"description": "User identificator"
18+
"description": "User identifier"
19+
},
20+
"remoteip": {
21+
"$ref": "common.json#/definitions/remoteip"
1622
}
1723
}
1824
}

schemas/register.json

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,7 @@
2020
"type": "boolean"
2121
},
2222
"captcha": {
23-
"type": "object",
24-
"required": [
25-
"response",
26-
"remoteip"
27-
],
28-
"properties": {
29-
"response": {
30-
"type": "string",
31-
"minLength": 1
32-
},
33-
"remoteip": {
34-
"type": "string",
35-
"oneOf": [{
36-
"format": "ipv4"
37-
},
38-
{
39-
"format": "ipv6"
40-
}
41-
]
42-
},
43-
"secret": {
44-
"type": "string",
45-
"minLength": 1
46-
}
47-
}
23+
"$ref": "common.json#/definitions/captcha"
4824
},
4925
"metadata": {
5026
"type": "object",
@@ -59,14 +35,7 @@
5935
"minLength": 1
6036
},
6137
"ipaddress": {
62-
"type": "string",
63-
"oneOf": [{
64-
"format": "ipv4"
65-
},
66-
{
67-
"format": "ipv6"
68-
}
69-
]
38+
"$ref": "common.json#/definitions/remoteip"
7039
},
7140
"skipChallenge": {
7241
"type": "boolean"

schemas/update-username/request.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
"value"
88
],
99
"properties": {
10+
"captcha": {
11+
"$ref": "common.json#/definitions/captcha"
12+
},
1013
"challengeType": {
1114
"description": "Where to send the secret code. Currently only the `phone` is supported",
1215
"type": "string",
@@ -22,6 +25,9 @@
2225
"minLength": 1,
2326
"type": "string"
2427
},
28+
"remoteip": {
29+
"$ref": "common.json#/definitions/remoteip"
30+
},
2531
"username": {
2632
"description": "User alias, ID or username",
2733
"maxLength": 50,

src/actions/disposable-password.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ const challenge = require('../utils/challenges/challenge');
55
const { getInternalData } = require('../utils/userData');
66
const isActive = require('../utils/is-active');
77
const isBanned = require('../utils/is-banned');
8-
const hasNotPassword = require('../utils/has-no-password');
98
const { USERS_ACTION_DISPOSABLE_PASSWORD, USERS_USERNAME_FIELD } = require('../constants');
109

1110
/**
@@ -14,7 +13,7 @@ const { USERS_ACTION_DISPOSABLE_PASSWORD, USERS_USERNAME_FIELD } = require('../c
1413
* @apiName DisposablePassword
1514
* @apiGroup Users
1615
*
17-
* @apiDescription This method allowes to get disposable password.
16+
* @apiDescription This method allows to get disposable password.
1817
*
1918
* @apiSchema {jsonschema=../../schemas/disposable-password.json} apiParam
2019
*/
@@ -27,7 +26,6 @@ module.exports = function disposablePassword(request) {
2726
.then(getInternalData)
2827
.tap(isActive)
2928
.tap(isBanned)
30-
.tap(hasNotPassword)
3129
.then((data) => ([challengeType, {
3230
id: data[USERS_USERNAME_FIELD],
3331
action: USERS_ACTION_DISPOSABLE_PASSWORD,

src/actions/update-username/request.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
const { ActionTransport } = require('@microfleet/plugin-router');
22

33
const { requestUsernameUpdate } = require('../../utils/update-username');
4-
const { resolveUserId } = require('../../utils/userData');
5-
const { checkMFA } = require('../../utils/mfa');
4+
const { getInternalData, resolveUserId } = require('../../utils/userData');
65
const isActive = require('../../utils/is-active');
76
const isBanned = require('../../utils/is-banned');
87
const {
98
ErrorConflictUserExists,
10-
ErrorUserNotFound,
11-
MFA_TYPE_OPTIONAL,
129
USERS_ID_FIELD,
1310
} = require('../../constants');
1411

@@ -26,12 +23,8 @@ const {
2623
* @apiSuccess (Response) {Object} uid Token UID
2724
*/
2825
module.exports = async function requestUpdateUsernameAction(request) {
29-
const { challengeType, i18nLocale, value } = request.params;
30-
const { internalData } = request.locals;
31-
32-
if (!internalData) {
33-
throw ErrorUserNotFound;
34-
}
26+
const { challengeType, i18nLocale, username, value } = request.params;
27+
const internalData = await getInternalData.call(this, username);
3528

3629
await isActive(internalData);
3730
isBanned(internalData);
@@ -49,6 +42,4 @@ module.exports = async function requestUpdateUsernameAction(request) {
4942
);
5043
};
5144

52-
module.exports.mfa = MFA_TYPE_OPTIONAL;
53-
module.exports.allowed = checkMFA;
5445
module.exports.transports = [ActionTransport.amqp, ActionTransport.internal];

0 commit comments

Comments
 (0)