@prosopo/provider 3.12.3 → 3.13.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 +214 -0
- package/dist/api/admin/apiAdminRoutesProvider.js +13 -18
- package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +40 -0
- package/dist/api/blacklistRequestInspector.js +4 -4
- package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +338 -0
- package/dist/api/captcha/getImageCaptchaChallenge.js +150 -0
- package/dist/api/captcha/getPoWCaptchaChallenge.js +156 -0
- package/dist/api/captcha/submitImageCaptchaSolution.js +87 -0
- package/dist/api/captcha/submitPoWCaptchaSolution.js +77 -0
- package/dist/api/captcha.js +18 -606
- package/dist/api/verify.js +24 -1
- package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +13 -18
- package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +2 -1
- package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +3 -2
- package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +41 -0
- package/dist/cjs/api/blacklistRequestInspector.cjs +3 -3
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +337 -0
- package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +149 -0
- package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +155 -0
- package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +86 -0
- package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +76 -0
- package/dist/cjs/api/captcha.cjs +17 -605
- package/dist/cjs/api/ja4Middleware.cjs +2 -1
- package/dist/cjs/api/verify.cjs +24 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/schedulers/setClientEntropy.cjs +36 -0
- package/dist/cjs/tasks/captchaManager.cjs +7 -22
- package/dist/cjs/tasks/client/clientTasks.cjs +18 -36
- package/dist/cjs/tasks/detection/decodePayload.cjs +385 -714
- package/dist/cjs/tasks/detection/getBotScore.cjs +15 -2
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +136 -30
- package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +25 -13
- package/dist/cjs/tasks/powCaptcha/powTasks.cjs +8 -8
- package/dist/cjs/tasks/tasks.cjs +1 -0
- package/dist/cjs/util.cjs +14 -1
- package/dist/cjs/utils/hashUserIp.cjs +9 -0
- package/dist/index.js +2 -0
- package/dist/schedulers/setClientEntropy.js +36 -0
- package/dist/tasks/captchaManager.js +5 -21
- package/dist/tasks/client/clientTasks.js +19 -37
- package/dist/tasks/detection/decodePayload.js +385 -714
- package/dist/tasks/detection/getBotScore.js +17 -4
- package/dist/tasks/frictionless/frictionlessTasks.js +137 -31
- package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +25 -13
- package/dist/tasks/powCaptcha/powTasks.js +8 -8
- package/dist/tasks/tasks.js +1 -0
- package/dist/util.js +14 -1
- package/dist/utils/hashUserIp.js +9 -0
- package/package.json +24 -25
package/dist/api/captcha.js
CHANGED
|
@@ -1,625 +1,37 @@
|
|
|
1
1
|
import { handleErrors } from "@prosopo/api-express-router";
|
|
2
|
-
import {
|
|
3
|
-
import { parseCaptchaAssets } from "@prosopo/datasets";
|
|
4
|
-
import { ClientApiPaths, CaptchaRequestBody, CaptchaType, ApiParams, CaptchaSolutionBody, GetPowCaptchaChallengeRequestBody, SubmitPowCaptchaSolutionBody, GetFrictionlessCaptchaChallengeRequestBody } from "@prosopo/types";
|
|
5
|
-
import { getIPAddress, flatten } from "@prosopo/util";
|
|
2
|
+
import { ClientApiPaths } from "@prosopo/types";
|
|
6
3
|
import express from "express";
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import { getRequestUserScope } from "./blacklistRequestInspector.js";
|
|
13
|
-
import { validateSiteKey, validateAddr } from "./validateAddress.js";
|
|
14
|
-
const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
|
|
4
|
+
import getFrictionlessCaptchaChallenge from "./captcha/getFrictionlessCaptchaChallenge.js";
|
|
5
|
+
import getImageCaptchaChallenge from "./captcha/getImageCaptchaChallenge.js";
|
|
6
|
+
import getPoWCaptchaChallenge from "./captcha/getPoWCaptchaChallenge.js";
|
|
7
|
+
import submitImageCaptchaSolution from "./captcha/submitImageCaptchaSolution.js";
|
|
8
|
+
import submitPoWCaptchaSolution from "./captcha/submitPoWCaptchaSolution.js";
|
|
15
9
|
function prosopoRouter(env) {
|
|
16
10
|
const router = express.Router();
|
|
17
11
|
const userAccessRulesStorage = env.getDb().getUserAccessRulesStorage();
|
|
18
12
|
router.post(
|
|
19
13
|
ClientApiPaths.GetImageCaptchaChallenge,
|
|
20
|
-
|
|
21
|
-
const tasks = new Tasks(env, req.logger);
|
|
22
|
-
let parsed;
|
|
23
|
-
if (!req.ip) {
|
|
24
|
-
return next(
|
|
25
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
26
|
-
context: { code: 400, error: "IP address not found" },
|
|
27
|
-
i18n: req.i18n,
|
|
28
|
-
logger: req.logger
|
|
29
|
-
})
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
const ipAddress = getIPAddress(req.ip || "");
|
|
33
|
-
try {
|
|
34
|
-
parsed = CaptchaRequestBody.parse(req.body);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
return next(
|
|
37
|
-
new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
|
|
38
|
-
context: { code: 400, error: err },
|
|
39
|
-
i18n: req.i18n,
|
|
40
|
-
logger: req.logger
|
|
41
|
-
})
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
const { datasetId, user, dapp, sessionId } = parsed;
|
|
45
|
-
validateSiteKey(dapp);
|
|
46
|
-
validateAddr(user);
|
|
47
|
-
try {
|
|
48
|
-
const clientRecord = await tasks.db.getClientRecord(dapp);
|
|
49
|
-
if (!clientRecord) {
|
|
50
|
-
return next(
|
|
51
|
-
new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
52
|
-
context: { code: 400, siteKey: dapp },
|
|
53
|
-
i18n: req.i18n,
|
|
54
|
-
logger: req.logger
|
|
55
|
-
})
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
const userScope = getRequestUserScope(
|
|
59
|
-
flatten(req.headers),
|
|
60
|
-
req.ja4,
|
|
61
|
-
req.ip,
|
|
62
|
-
user
|
|
63
|
-
);
|
|
64
|
-
const userAccessPolicy = (await tasks.imgCaptchaManager.getPrioritisedAccessPolicies(
|
|
65
|
-
userAccessRulesStorage,
|
|
66
|
-
dapp,
|
|
67
|
-
userScope
|
|
68
|
-
))[0];
|
|
69
|
-
const { valid, reason, frictionlessTokenId, solvedImagesCount } = await tasks.imgCaptchaManager.isValidRequest(
|
|
70
|
-
clientRecord,
|
|
71
|
-
CaptchaType.image,
|
|
72
|
-
env,
|
|
73
|
-
sessionId,
|
|
74
|
-
userAccessPolicy,
|
|
75
|
-
req.ip
|
|
76
|
-
);
|
|
77
|
-
if (!valid) {
|
|
78
|
-
return next(
|
|
79
|
-
new ProsopoApiError(reason || "API.BAD_REQUEST", {
|
|
80
|
-
context: {
|
|
81
|
-
code: 400,
|
|
82
|
-
siteKey: dapp,
|
|
83
|
-
user
|
|
84
|
-
},
|
|
85
|
-
i18n: req.i18n,
|
|
86
|
-
logger: req.logger
|
|
87
|
-
})
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
const captchaConfig = {
|
|
91
|
-
solved: {
|
|
92
|
-
count: solvedImagesCount || userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
|
|
93
|
-
},
|
|
94
|
-
unsolved: {
|
|
95
|
-
count: userAccessPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
const taskData = await tasks.imgCaptchaManager.getRandomCaptchasAndRequestHash(
|
|
99
|
-
datasetId,
|
|
100
|
-
user,
|
|
101
|
-
ipAddress,
|
|
102
|
-
captchaConfig,
|
|
103
|
-
clientRecord.settings.imageThreshold ?? 0.8,
|
|
104
|
-
frictionlessTokenId
|
|
105
|
-
);
|
|
106
|
-
const captchaResponse = {
|
|
107
|
-
[ApiParams.status]: "ok",
|
|
108
|
-
[ApiParams.captchas]: taskData.captchas.map((captcha) => ({
|
|
109
|
-
...captcha,
|
|
110
|
-
target: req.t(`TARGET.${captcha.target}`),
|
|
111
|
-
items: captcha.items.map(
|
|
112
|
-
(item) => parseCaptchaAssets(item, env.assetsResolver)
|
|
113
|
-
)
|
|
114
|
-
})),
|
|
115
|
-
[ApiParams.requestHash]: taskData.requestHash,
|
|
116
|
-
[ApiParams.timestamp]: taskData.timestamp.toString(),
|
|
117
|
-
[ApiParams.signature]: {
|
|
118
|
-
[ApiParams.provider]: {
|
|
119
|
-
[ApiParams.requestHash]: taskData.signedRequestHash
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
req.logger.info(() => ({
|
|
124
|
-
msg: "Image captcha challenge issued",
|
|
125
|
-
data: {
|
|
126
|
-
captchaType: CaptchaType.image,
|
|
127
|
-
requestHash: taskData.requestHash,
|
|
128
|
-
solvedImagesCount: captchaConfig.solved.count,
|
|
129
|
-
user,
|
|
130
|
-
dapp,
|
|
131
|
-
sessionId
|
|
132
|
-
}
|
|
133
|
-
}));
|
|
134
|
-
return res.json(captchaResponse);
|
|
135
|
-
} catch (err) {
|
|
136
|
-
req.logger.error(() => ({
|
|
137
|
-
err,
|
|
138
|
-
data: req.params,
|
|
139
|
-
msg: "Error in image captcha challenge request"
|
|
140
|
-
}));
|
|
141
|
-
return next(
|
|
142
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
143
|
-
context: {
|
|
144
|
-
error: err,
|
|
145
|
-
code: 500,
|
|
146
|
-
params: req.params,
|
|
147
|
-
context: err
|
|
148
|
-
},
|
|
149
|
-
i18n: req.i18n,
|
|
150
|
-
logger: req.logger
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
14
|
+
(req, res, next) => getImageCaptchaChallenge(env, userAccessRulesStorage)(req, res, next)
|
|
155
15
|
);
|
|
156
16
|
router.post(
|
|
157
17
|
ClientApiPaths.SubmitImageCaptchaSolution,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
} catch (err) {
|
|
164
|
-
return next(
|
|
165
|
-
new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
|
|
166
|
-
context: { code: 400, error: err, body: req.body },
|
|
167
|
-
i18n: req.i18n,
|
|
168
|
-
logger: req.logger
|
|
169
|
-
})
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
const { user, dapp } = parsed;
|
|
173
|
-
validateSiteKey(dapp);
|
|
174
|
-
validateAddr(user);
|
|
175
|
-
try {
|
|
176
|
-
const clientRecord = await tasks.db.getClientRecord(parsed.dapp);
|
|
177
|
-
if (!clientRecord) {
|
|
178
|
-
return next(
|
|
179
|
-
new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
180
|
-
context: { code: 400, siteKey: dapp },
|
|
181
|
-
i18n: req.i18n,
|
|
182
|
-
logger: req.logger
|
|
183
|
-
})
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
const result = await tasks.imgCaptchaManager.dappUserSolution(
|
|
187
|
-
user,
|
|
188
|
-
dapp,
|
|
189
|
-
parsed[ApiParams.requestHash],
|
|
190
|
-
parsed[ApiParams.captchas],
|
|
191
|
-
parsed[ApiParams.signature].user.timestamp,
|
|
192
|
-
Number.parseInt(parsed[ApiParams.timestamp]),
|
|
193
|
-
parsed[ApiParams.signature].provider.requestHash,
|
|
194
|
-
getIPAddress(req.ip || ""),
|
|
195
|
-
flatten(req.headers),
|
|
196
|
-
req.ja4
|
|
197
|
-
);
|
|
198
|
-
const returnValue = {
|
|
199
|
-
status: req.i18n.t(
|
|
200
|
-
result.verified ? "API.CAPTCHA_PASSED" : "API.CAPTCHA_FAILED"
|
|
201
|
-
),
|
|
202
|
-
...result
|
|
203
|
-
};
|
|
204
|
-
return res.json(returnValue);
|
|
205
|
-
} catch (err) {
|
|
206
|
-
req.logger.error(() => ({
|
|
207
|
-
err,
|
|
208
|
-
body: req.body,
|
|
209
|
-
msg: "Error in image captcha solution submission"
|
|
210
|
-
}));
|
|
211
|
-
return next(
|
|
212
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
213
|
-
context: {
|
|
214
|
-
code: 500,
|
|
215
|
-
siteKey: req.body.dapp,
|
|
216
|
-
error: err
|
|
217
|
-
},
|
|
218
|
-
i18n: req.i18n,
|
|
219
|
-
logger: req.logger
|
|
220
|
-
})
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
18
|
+
(req, res, next) => submitImageCaptchaSolution(env, userAccessRulesStorage)(req, res, next)
|
|
19
|
+
);
|
|
20
|
+
router.post(
|
|
21
|
+
ClientApiPaths.GetPowCaptchaChallenge,
|
|
22
|
+
(req, res, next) => getPoWCaptchaChallenge(env, userAccessRulesStorage)(req, res, next)
|
|
224
23
|
);
|
|
225
|
-
router.post(ClientApiPaths.GetPowCaptchaChallenge, async (req, res, next) => {
|
|
226
|
-
let parsed;
|
|
227
|
-
const tasks = new Tasks(env);
|
|
228
|
-
tasks.setLogger(req.logger);
|
|
229
|
-
try {
|
|
230
|
-
parsed = GetPowCaptchaChallengeRequestBody.parse(req.body);
|
|
231
|
-
} catch (err) {
|
|
232
|
-
return next(
|
|
233
|
-
new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
|
|
234
|
-
context: { code: 400, error: err },
|
|
235
|
-
i18n: req.i18n,
|
|
236
|
-
logger: req.logger
|
|
237
|
-
})
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
const { user, dapp, sessionId } = parsed;
|
|
241
|
-
validateSiteKey(dapp);
|
|
242
|
-
validateAddr(user);
|
|
243
|
-
try {
|
|
244
|
-
const clientSettings = await tasks.db.getClientRecord(dapp);
|
|
245
|
-
if (!clientSettings) {
|
|
246
|
-
return next(
|
|
247
|
-
new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
248
|
-
context: { code: 400, siteKey: dapp },
|
|
249
|
-
i18n: req.i18n,
|
|
250
|
-
logger: req.logger
|
|
251
|
-
})
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
const userScope = getRequestUserScope(
|
|
255
|
-
flatten(req.headers),
|
|
256
|
-
req.ja4,
|
|
257
|
-
req.ip,
|
|
258
|
-
user
|
|
259
|
-
);
|
|
260
|
-
const userAccessPolicy = (await tasks.powCaptchaManager.getPrioritisedAccessPolicies(
|
|
261
|
-
userAccessRulesStorage,
|
|
262
|
-
dapp,
|
|
263
|
-
userScope
|
|
264
|
-
))[0];
|
|
265
|
-
const { valid, reason, frictionlessTokenId, powDifficulty } = await tasks.powCaptchaManager.isValidRequest(
|
|
266
|
-
clientSettings,
|
|
267
|
-
CaptchaType.pow,
|
|
268
|
-
env,
|
|
269
|
-
sessionId,
|
|
270
|
-
userAccessPolicy,
|
|
271
|
-
req.ip
|
|
272
|
-
);
|
|
273
|
-
if (!valid) {
|
|
274
|
-
return next(
|
|
275
|
-
new ProsopoApiError(reason || "API.BAD_REQUEST", {
|
|
276
|
-
context: {
|
|
277
|
-
code: 400,
|
|
278
|
-
siteKey: dapp,
|
|
279
|
-
user
|
|
280
|
-
},
|
|
281
|
-
i18n: req.i18n,
|
|
282
|
-
logger: req.logger
|
|
283
|
-
})
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
const origin = req.headers.origin;
|
|
287
|
-
if (!origin) {
|
|
288
|
-
return next(
|
|
289
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
290
|
-
context: {
|
|
291
|
-
error: "Origin header not found",
|
|
292
|
-
code: 400,
|
|
293
|
-
siteKey: dapp,
|
|
294
|
-
user
|
|
295
|
-
},
|
|
296
|
-
i18n: req.i18n,
|
|
297
|
-
logger: req.logger
|
|
298
|
-
})
|
|
299
|
-
);
|
|
300
|
-
}
|
|
301
|
-
const difficulty = powDifficulty || userAccessPolicy?.powDifficulty || clientSettings?.settings?.powDifficulty;
|
|
302
|
-
const challenge = await tasks.powCaptchaManager.getPowCaptchaChallenge(
|
|
303
|
-
user,
|
|
304
|
-
dapp,
|
|
305
|
-
origin,
|
|
306
|
-
difficulty
|
|
307
|
-
);
|
|
308
|
-
await tasks.db.storePowCaptchaRecord(
|
|
309
|
-
challenge.challenge,
|
|
310
|
-
{
|
|
311
|
-
requestedAtTimestamp: challenge.requestedAtTimestamp,
|
|
312
|
-
userAccount: user,
|
|
313
|
-
dappAccount: dapp
|
|
314
|
-
},
|
|
315
|
-
challenge.difficulty,
|
|
316
|
-
challenge.providerSignature,
|
|
317
|
-
getCompositeIpAddress(req.ip || ""),
|
|
318
|
-
flatten(req.headers),
|
|
319
|
-
req.ja4,
|
|
320
|
-
frictionlessTokenId
|
|
321
|
-
);
|
|
322
|
-
const getPowCaptchaResponse = {
|
|
323
|
-
[ApiParams.status]: "ok",
|
|
324
|
-
[ApiParams.challenge]: challenge.challenge,
|
|
325
|
-
[ApiParams.difficulty]: challenge.difficulty,
|
|
326
|
-
[ApiParams.timestamp]: challenge.requestedAtTimestamp.toString(),
|
|
327
|
-
[ApiParams.signature]: {
|
|
328
|
-
[ApiParams.provider]: {
|
|
329
|
-
[ApiParams.challenge]: challenge.providerSignature
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
req.logger.info(() => ({
|
|
334
|
-
msg: "PoW captcha challenge issued",
|
|
335
|
-
data: {
|
|
336
|
-
captchaType: CaptchaType.pow,
|
|
337
|
-
challenge: challenge.challenge,
|
|
338
|
-
difficulty: challenge.difficulty,
|
|
339
|
-
user,
|
|
340
|
-
dapp,
|
|
341
|
-
session: sessionId
|
|
342
|
-
}
|
|
343
|
-
}));
|
|
344
|
-
return res.json(getPowCaptchaResponse);
|
|
345
|
-
} catch (err) {
|
|
346
|
-
req.logger.error(() => ({
|
|
347
|
-
err,
|
|
348
|
-
body: req.body,
|
|
349
|
-
msg: "Error in PoW captcha challenge request"
|
|
350
|
-
}));
|
|
351
|
-
return next(
|
|
352
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
353
|
-
context: {
|
|
354
|
-
code: 500,
|
|
355
|
-
siteKey: req.body.dapp,
|
|
356
|
-
user: req.body.user,
|
|
357
|
-
error: err
|
|
358
|
-
},
|
|
359
|
-
i18n: req.i18n,
|
|
360
|
-
logger: req.logger
|
|
361
|
-
})
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
24
|
router.post(
|
|
366
25
|
ClientApiPaths.SubmitPowCaptchaSolution,
|
|
367
|
-
|
|
368
|
-
let parsed;
|
|
369
|
-
const tasks = new Tasks(env, req.logger);
|
|
370
|
-
try {
|
|
371
|
-
parsed = SubmitPowCaptchaSolutionBody.parse(req.body);
|
|
372
|
-
} catch (err) {
|
|
373
|
-
return next(
|
|
374
|
-
new ProsopoApiError("CAPTCHA.PARSE_ERROR", {
|
|
375
|
-
context: { code: 400, error: err, body: req.body },
|
|
376
|
-
i18n: req.i18n,
|
|
377
|
-
logger: req.logger
|
|
378
|
-
})
|
|
379
|
-
);
|
|
380
|
-
}
|
|
381
|
-
const { challenge, signature, nonce, verifiedTimeout, dapp, user } = parsed;
|
|
382
|
-
validateSiteKey(dapp);
|
|
383
|
-
validateAddr(user);
|
|
384
|
-
try {
|
|
385
|
-
const clientRecord = await tasks.db.getClientRecord(dapp);
|
|
386
|
-
if (!clientRecord) {
|
|
387
|
-
return next(
|
|
388
|
-
new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
389
|
-
context: { code: 400, siteKey: dapp },
|
|
390
|
-
i18n: req.i18n,
|
|
391
|
-
logger: req.logger
|
|
392
|
-
})
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
const verified = await tasks.powCaptchaManager.verifyPowCaptchaSolution(
|
|
396
|
-
challenge,
|
|
397
|
-
signature.provider.challenge,
|
|
398
|
-
nonce,
|
|
399
|
-
verifiedTimeout,
|
|
400
|
-
signature.user.timestamp,
|
|
401
|
-
getIPAddress(req.ip || ""),
|
|
402
|
-
flatten(req.headers)
|
|
403
|
-
);
|
|
404
|
-
const response = { status: "ok", verified };
|
|
405
|
-
return res.json(response);
|
|
406
|
-
} catch (err) {
|
|
407
|
-
req.logger.error(() => ({
|
|
408
|
-
err,
|
|
409
|
-
body: req.body,
|
|
410
|
-
msg: "Error in PoW captcha solution submission"
|
|
411
|
-
}));
|
|
412
|
-
return next(
|
|
413
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
414
|
-
context: {
|
|
415
|
-
code: 500,
|
|
416
|
-
siteKey: req.body.dapp,
|
|
417
|
-
error: err
|
|
418
|
-
},
|
|
419
|
-
i18n: req.i18n,
|
|
420
|
-
logger: req.logger
|
|
421
|
-
})
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
26
|
+
(req, res, next) => submitPoWCaptchaSolution(env)(req, res, next)
|
|
425
27
|
);
|
|
426
28
|
router.post(
|
|
427
29
|
ClientApiPaths.GetFrictionlessCaptchaChallenge,
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (existingToken) {
|
|
434
|
-
req.logger.info(() => ({
|
|
435
|
-
token: existingToken,
|
|
436
|
-
msg: "Token has already been used"
|
|
437
|
-
}));
|
|
438
|
-
return next(
|
|
439
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
440
|
-
context: {
|
|
441
|
-
code: 400,
|
|
442
|
-
siteKey: dapp,
|
|
443
|
-
user
|
|
444
|
-
},
|
|
445
|
-
i18n: req.i18n,
|
|
446
|
-
logger: req.logger
|
|
447
|
-
})
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
const lScore = tasks.frictionlessManager.checkLangRules(
|
|
451
|
-
req.headers["accept-language"] || ""
|
|
452
|
-
);
|
|
453
|
-
const {
|
|
454
|
-
baseBotScore,
|
|
455
|
-
timestamp,
|
|
456
|
-
providerSelectEntropy,
|
|
457
|
-
userId,
|
|
458
|
-
userAgent
|
|
459
|
-
} = await tasks.frictionlessManager.decryptPayload(token);
|
|
460
|
-
req.logger.debug(() => ({
|
|
461
|
-
msg: "Decrypted payload",
|
|
462
|
-
data: {
|
|
463
|
-
baseBotScore,
|
|
464
|
-
timestamp,
|
|
465
|
-
providerSelectEntropy,
|
|
466
|
-
userId,
|
|
467
|
-
userAgent
|
|
468
|
-
}
|
|
469
|
-
}));
|
|
470
|
-
let botScore = baseBotScore + lScore;
|
|
471
|
-
const clientRecord = await tasks.db.getClientRecord(dapp);
|
|
472
|
-
if (!clientRecord) {
|
|
473
|
-
return next(
|
|
474
|
-
new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
475
|
-
context: { code: 400, siteKey: dapp },
|
|
476
|
-
i18n: req.i18n,
|
|
477
|
-
logger: req.logger
|
|
478
|
-
})
|
|
479
|
-
);
|
|
480
|
-
}
|
|
481
|
-
const { valid, reason } = await tasks.frictionlessManager.isValidRequest(
|
|
482
|
-
clientRecord,
|
|
483
|
-
CaptchaType.frictionless,
|
|
484
|
-
env
|
|
485
|
-
);
|
|
486
|
-
if (!valid) {
|
|
487
|
-
return next(
|
|
488
|
-
new ProsopoApiError(reason || "API.BAD_REQUEST", {
|
|
489
|
-
context: {
|
|
490
|
-
code: 400,
|
|
491
|
-
siteKey: dapp,
|
|
492
|
-
user
|
|
493
|
-
},
|
|
494
|
-
i18n: req.i18n,
|
|
495
|
-
logger: req.logger
|
|
496
|
-
})
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
const botThreshold = clientRecord.settings?.frictionlessThreshold || DEFAULT_FRICTIONLESS_THRESHOLD;
|
|
500
|
-
const tokenId = await tasks.db.storeFrictionlessTokenRecord({
|
|
501
|
-
token,
|
|
502
|
-
score: botScore,
|
|
503
|
-
threshold: botThreshold,
|
|
504
|
-
scoreComponents: {
|
|
505
|
-
baseScore: baseBotScore,
|
|
506
|
-
...lScore && { lScore }
|
|
507
|
-
},
|
|
508
|
-
providerSelectEntropy,
|
|
509
|
-
ipAddress: getCompositeIpAddress(req.ip || "")
|
|
510
|
-
});
|
|
511
|
-
const userScope = getRequestUserScope(
|
|
512
|
-
flatten(req.headers),
|
|
513
|
-
req.ja4,
|
|
514
|
-
req.ip,
|
|
515
|
-
user
|
|
516
|
-
);
|
|
517
|
-
const userAccessPolicy = (await tasks.frictionlessManager.getPrioritisedAccessPolicies(
|
|
518
|
-
userAccessRulesStorage,
|
|
519
|
-
dapp,
|
|
520
|
-
userScope
|
|
521
|
-
))[0];
|
|
522
|
-
const headersUserAgent = req.headers["user-agent"];
|
|
523
|
-
const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent(headersUserAgent) : "";
|
|
524
|
-
const headersProsopoUser = req.headers["prosopo-user"];
|
|
525
|
-
if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
|
|
526
|
-
req.logger.info(() => ({
|
|
527
|
-
msg: "User agent or user id does not match",
|
|
528
|
-
data: {
|
|
529
|
-
headersUserAgent,
|
|
530
|
-
hashedHeadersUserAgent,
|
|
531
|
-
userAgent,
|
|
532
|
-
// This is the hashed user agent from the token
|
|
533
|
-
headersProsopoUser,
|
|
534
|
-
userId
|
|
535
|
-
}
|
|
536
|
-
}));
|
|
537
|
-
return res.json(
|
|
538
|
-
await tasks.frictionlessManager.sendImageCaptcha(
|
|
539
|
-
tokenId,
|
|
540
|
-
timestampDecayFunction(timestamp)
|
|
541
|
-
)
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
if (userAccessPolicy) {
|
|
545
|
-
await tasks.frictionlessManager.scoreIncreaseAccessPolicy(
|
|
546
|
-
userAccessPolicy,
|
|
547
|
-
baseBotScore,
|
|
548
|
-
botScore,
|
|
549
|
-
tokenId
|
|
550
|
-
);
|
|
551
|
-
if (userAccessPolicy.captchaType === CaptchaType.image) {
|
|
552
|
-
return res.json(
|
|
553
|
-
await tasks.frictionlessManager.sendImageCaptcha(
|
|
554
|
-
tokenId,
|
|
555
|
-
userAccessPolicy.solvedImagesCount
|
|
556
|
-
)
|
|
557
|
-
);
|
|
558
|
-
}
|
|
559
|
-
if (userAccessPolicy.captchaType === CaptchaType.pow) {
|
|
560
|
-
return res.json(
|
|
561
|
-
await tasks.frictionlessManager.sendPowCaptcha(tokenId)
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
if (FrictionlessManager.timestampTooOld(timestamp)) {
|
|
566
|
-
await tasks.frictionlessManager.scoreIncreaseTimestamp(
|
|
567
|
-
timestamp,
|
|
568
|
-
baseBotScore,
|
|
569
|
-
botScore,
|
|
570
|
-
tokenId
|
|
571
|
-
);
|
|
572
|
-
return res.json(
|
|
573
|
-
await tasks.frictionlessManager.sendImageCaptcha(
|
|
574
|
-
tokenId,
|
|
575
|
-
timestampDecayFunction(timestamp)
|
|
576
|
-
)
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
const hostVerified = await tasks.frictionlessManager.hostVerified(
|
|
580
|
-
providerSelectEntropy
|
|
581
|
-
);
|
|
582
|
-
if (!hostVerified.verified) {
|
|
583
|
-
botScore = await tasks.frictionlessManager.scoreIncreaseUnverifiedHost(
|
|
584
|
-
hostVerified.domain,
|
|
585
|
-
baseBotScore,
|
|
586
|
-
botScore,
|
|
587
|
-
tokenId
|
|
588
|
-
);
|
|
589
|
-
}
|
|
590
|
-
if (Number(botScore) > botThreshold) {
|
|
591
|
-
req.logger.info(() => ({
|
|
592
|
-
msg: "Bot score is greater than threshold",
|
|
593
|
-
data: {
|
|
594
|
-
botScore,
|
|
595
|
-
botThreshold,
|
|
596
|
-
tokenId
|
|
597
|
-
}
|
|
598
|
-
}));
|
|
599
|
-
return res.json(
|
|
600
|
-
await tasks.frictionlessManager.sendImageCaptcha(
|
|
601
|
-
tokenId,
|
|
602
|
-
env.config.captchas.solved.count
|
|
603
|
-
)
|
|
604
|
-
);
|
|
605
|
-
}
|
|
606
|
-
return res.json(
|
|
607
|
-
await tasks.frictionlessManager.sendPowCaptcha(tokenId)
|
|
608
|
-
);
|
|
609
|
-
} catch (err) {
|
|
610
|
-
req.logger.error(() => ({
|
|
611
|
-
err,
|
|
612
|
-
msg: "Error in frictionless captcha challenge"
|
|
613
|
-
}));
|
|
614
|
-
return next(
|
|
615
|
-
new ProsopoApiError("API.BAD_REQUEST", {
|
|
616
|
-
context: { code: 400, error: err },
|
|
617
|
-
i18n: req.i18n,
|
|
618
|
-
logger: req.logger
|
|
619
|
-
})
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
}
|
|
30
|
+
(req, res, next) => getFrictionlessCaptchaChallenge(env, userAccessRulesStorage)(
|
|
31
|
+
req,
|
|
32
|
+
res,
|
|
33
|
+
next
|
|
34
|
+
)
|
|
623
35
|
);
|
|
624
36
|
router.use(handleErrors);
|
|
625
37
|
return router;
|
package/dist/api/verify.js
CHANGED
|
@@ -4,12 +4,23 @@ import { ClientApiPaths, VerifySolutionBody, decodeProcaptchaOutput, ApiParams,
|
|
|
4
4
|
import { validateAddress } from "@prosopo/util-crypto";
|
|
5
5
|
import express from "express";
|
|
6
6
|
import { Tasks } from "../tasks/tasks.js";
|
|
7
|
+
import { getMaintenanceMode } from "./admin/apiToggleMaintenanceModeEndpoint.js";
|
|
7
8
|
function prosopoVerifyRouter(env) {
|
|
8
9
|
const router = express.Router();
|
|
9
10
|
router.post(
|
|
10
11
|
ClientApiPaths.VerifyImageCaptchaSolutionDapp,
|
|
11
12
|
async (req, res, next) => {
|
|
12
13
|
const tasks = new Tasks(env, req.logger);
|
|
14
|
+
if (getMaintenanceMode()) {
|
|
15
|
+
req.logger.info(() => ({
|
|
16
|
+
msg: "Maintenance mode active - returning verified for image captcha verification"
|
|
17
|
+
}));
|
|
18
|
+
const verificationResponse = {
|
|
19
|
+
status: "ok",
|
|
20
|
+
verified: true
|
|
21
|
+
};
|
|
22
|
+
return res.json(verificationResponse);
|
|
23
|
+
}
|
|
13
24
|
let parsed;
|
|
14
25
|
try {
|
|
15
26
|
parsed = VerifySolutionBody.parse(req.body);
|
|
@@ -45,7 +56,9 @@ function prosopoVerifyRouter(env) {
|
|
|
45
56
|
commitmentId,
|
|
46
57
|
env,
|
|
47
58
|
maxVerifiedTime,
|
|
48
|
-
ip
|
|
59
|
+
ip,
|
|
60
|
+
clientRecord.settings.disallowWebView,
|
|
61
|
+
clientRecord.settings.contextAware?.enabled
|
|
49
62
|
);
|
|
50
63
|
req.logger.debug(() => ({ data: { response } }));
|
|
51
64
|
const verificationResponse = tasks.imgCaptchaManager.getVerificationResponse(
|
|
@@ -72,6 +85,16 @@ function prosopoVerifyRouter(env) {
|
|
|
72
85
|
ClientApiPaths.VerifyPowCaptchaSolution,
|
|
73
86
|
async (req, res, next) => {
|
|
74
87
|
const tasks = new Tasks(env, req.logger);
|
|
88
|
+
if (getMaintenanceMode()) {
|
|
89
|
+
req.logger.info(() => ({
|
|
90
|
+
msg: "Maintenance mode active - returning verified for PoW captcha verification"
|
|
91
|
+
}));
|
|
92
|
+
const verificationResponse = {
|
|
93
|
+
status: "ok",
|
|
94
|
+
verified: true
|
|
95
|
+
};
|
|
96
|
+
return res.json(verificationResponse);
|
|
97
|
+
}
|
|
75
98
|
let parsed;
|
|
76
99
|
try {
|
|
77
100
|
parsed = ServerPowCaptchaVerifyRequestBody.parse(req.body);
|