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