@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
|
@@ -3,30 +3,25 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const types = require("@prosopo/types");
|
|
4
4
|
const apiRegisterSiteKeyEndpoint = require("./apiRegisterSiteKeyEndpoint.cjs");
|
|
5
5
|
const apiRemoveDetectorKeyEndpoint = require("./apiRemoveDetectorKeyEndpoint.cjs");
|
|
6
|
+
const apiToggleMaintenanceModeEndpoint = require("./apiToggleMaintenanceModeEndpoint.cjs");
|
|
6
7
|
const apiUpdateDetectorKeyEndpoint = require("./apiUpdateDetectorKeyEndpoint.cjs");
|
|
7
8
|
class ApiAdminRoutesProvider {
|
|
8
9
|
constructor(tasks) {
|
|
9
10
|
this.tasks = tasks;
|
|
10
11
|
}
|
|
11
12
|
getRoutes() {
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
path: types.AdminApiPaths.RemoveDetectorKey,
|
|
25
|
-
endpoint: new apiRemoveDetectorKeyEndpoint.ApiRemoveDetectorKeyEndpoint(
|
|
26
|
-
this.tasks.clientTaskManager
|
|
27
|
-
)
|
|
28
|
-
}
|
|
29
|
-
];
|
|
13
|
+
return {
|
|
14
|
+
[types.AdminApiPaths.SiteKeyRegister]: new apiRegisterSiteKeyEndpoint.ApiRegisterSiteKeyEndpoint(
|
|
15
|
+
this.tasks.clientTaskManager
|
|
16
|
+
),
|
|
17
|
+
[types.AdminApiPaths.UpdateDetectorKey]: new apiUpdateDetectorKeyEndpoint.ApiUpdateDetectorKeyEndpoint(
|
|
18
|
+
this.tasks.clientTaskManager
|
|
19
|
+
),
|
|
20
|
+
[types.AdminApiPaths.RemoveDetectorKey]: new apiRemoveDetectorKeyEndpoint.ApiRemoveDetectorKeyEndpoint(
|
|
21
|
+
this.tasks.clientTaskManager
|
|
22
|
+
),
|
|
23
|
+
[types.AdminApiPaths.ToggleMaintenanceMode]: new apiToggleMaintenanceModeEndpoint.ApiToggleMaintenanceModeEndpoint()
|
|
24
|
+
};
|
|
30
25
|
}
|
|
31
26
|
}
|
|
32
27
|
exports.ApiAdminRoutesProvider = ApiAdminRoutesProvider;
|
|
@@ -3,13 +3,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const apiRoute = require("@prosopo/api-route");
|
|
4
4
|
const common = require("@prosopo/common");
|
|
5
5
|
const types = require("@prosopo/types");
|
|
6
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
6
7
|
class ApiRegisterSiteKeyEndpoint {
|
|
7
8
|
constructor(clientTaskManager) {
|
|
8
9
|
this.clientTaskManager = clientTaskManager;
|
|
9
10
|
}
|
|
10
11
|
async processRequest(args, logger) {
|
|
11
12
|
const { siteKey, tier, settings } = args;
|
|
12
|
-
logger = logger || common.getLogger("info",
|
|
13
|
+
logger = logger || common.getLogger("info", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("api/admin/apiRegisterSiteKeyEndpoint.cjs", document.baseURI).href);
|
|
13
14
|
const temp = settings || types.ClientSettingsSchema.parse({});
|
|
14
15
|
logger.info(() => ({ data: { siteKey }, msg: "`Registering site key" }));
|
|
15
16
|
await this.clientTaskManager.registerSiteKey(siteKey, tier, temp);
|
|
@@ -3,15 +3,16 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const apiRoute = require("@prosopo/api-route");
|
|
4
4
|
const common = require("@prosopo/common");
|
|
5
5
|
const types = require("@prosopo/types");
|
|
6
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
6
7
|
class ApiRemoveDetectorKeyEndpoint {
|
|
7
8
|
constructor(clientTaskManager) {
|
|
8
9
|
this.clientTaskManager = clientTaskManager;
|
|
9
10
|
}
|
|
10
11
|
async processRequest(args, logger) {
|
|
11
|
-
logger = logger || common.getLogger("info",
|
|
12
|
+
logger = logger || common.getLogger("info", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("api/admin/apiRemoveDetectorKeyEndpoint.cjs", document.baseURI).href);
|
|
12
13
|
try {
|
|
13
14
|
const { detectorKey, expirationInSeconds } = args;
|
|
14
|
-
logger = logger || common.getLogger("info",
|
|
15
|
+
logger = logger || common.getLogger("info", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("api/admin/apiRemoveDetectorKeyEndpoint.cjs", document.baseURI).href);
|
|
15
16
|
logger.info(() => ({ msg: "Removing detector key" }));
|
|
16
17
|
await this.clientTaskManager.removeDetectorKey(
|
|
17
18
|
detectorKey,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const apiRoute = require("@prosopo/api-route");
|
|
4
|
+
const common = require("@prosopo/common");
|
|
5
|
+
const types = require("@prosopo/types");
|
|
6
|
+
var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
|
|
7
|
+
function getMaintenanceMode() {
|
|
8
|
+
return process.env.MAINTENANCE_MODE?.toLowerCase() === "true";
|
|
9
|
+
}
|
|
10
|
+
function setMaintenanceMode(enabled) {
|
|
11
|
+
process.env.MAINTENANCE_MODE = enabled ? "true" : "false";
|
|
12
|
+
}
|
|
13
|
+
class ApiToggleMaintenanceModeEndpoint {
|
|
14
|
+
async processRequest(args, logger) {
|
|
15
|
+
const { enabled } = args;
|
|
16
|
+
logger = logger || common.getLogger("info", typeof document === "undefined" ? require("url").pathToFileURL(__filename).href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("api/admin/apiToggleMaintenanceModeEndpoint.cjs", document.baseURI).href);
|
|
17
|
+
const previousMode = getMaintenanceMode();
|
|
18
|
+
logger.info(() => ({
|
|
19
|
+
data: { enabled, previous: previousMode },
|
|
20
|
+
msg: "Toggling maintenance mode"
|
|
21
|
+
}));
|
|
22
|
+
setMaintenanceMode(enabled);
|
|
23
|
+
const currentMode = getMaintenanceMode();
|
|
24
|
+
logger.info(() => ({
|
|
25
|
+
data: { enabled: currentMode },
|
|
26
|
+
msg: "Maintenance mode updated"
|
|
27
|
+
}));
|
|
28
|
+
return {
|
|
29
|
+
status: apiRoute.ApiEndpointResponseStatus.SUCCESS,
|
|
30
|
+
data: {
|
|
31
|
+
maintenanceMode: currentMode
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
getRequestArgsSchema() {
|
|
36
|
+
return types.ToggleMaintenanceModeBody;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.ApiToggleMaintenanceModeEndpoint = ApiToggleMaintenanceModeEndpoint;
|
|
40
|
+
exports.getMaintenanceMode = getMaintenanceMode;
|
|
41
|
+
exports.setMaintenanceMode = setMaintenanceMode;
|
|
@@ -34,16 +34,16 @@ const getPrioritisedAccessRule = async (userAccessRulesStorage, userScope, clien
|
|
|
34
34
|
if (Object.values(scope).every((value) => value === void 0)) {
|
|
35
35
|
continue;
|
|
36
36
|
}
|
|
37
|
-
const parsedUserScope = userAccessPolicy.
|
|
37
|
+
const parsedUserScope = userAccessPolicy.userScopeInput.parse(scope);
|
|
38
38
|
const filter = {
|
|
39
39
|
...clientOrUndefined && {
|
|
40
40
|
policyScope: {
|
|
41
41
|
clientId: clientOrUndefined
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
|
-
policyScopeMatch: userAccessPolicy.
|
|
44
|
+
policyScopeMatch: userAccessPolicy.FilterScopeMatch.Exact,
|
|
45
45
|
userScope: parsedUserScope,
|
|
46
|
-
userScopeMatch: userAccessPolicy.
|
|
46
|
+
userScopeMatch: userAccessPolicy.FilterScopeMatch.Exact
|
|
47
47
|
};
|
|
48
48
|
policyPromises.push(userAccessRulesStorage.findRules(filter, true, true));
|
|
49
49
|
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const common = require("@prosopo/common");
|
|
3
|
+
const types = require("@prosopo/types");
|
|
4
|
+
const util = require("@prosopo/util");
|
|
5
|
+
const compositeIpAddress = require("../../compositeIpAddress.cjs");
|
|
6
|
+
const frictionlessTasks = require("../../tasks/frictionless/frictionlessTasks.cjs");
|
|
7
|
+
const frictionlessTasksUtils = require("../../tasks/frictionless/frictionlessTasksUtils.cjs");
|
|
8
|
+
require("../../tasks/index.cjs");
|
|
9
|
+
const hashUserAgent = require("../../utils/hashUserAgent.cjs");
|
|
10
|
+
const hashUserIp = require("../../utils/hashUserIp.cjs");
|
|
11
|
+
const apiToggleMaintenanceModeEndpoint = require("../admin/apiToggleMaintenanceModeEndpoint.cjs");
|
|
12
|
+
const blacklistRequestInspector = require("../blacklistRequestInspector.cjs");
|
|
13
|
+
const tasks = require("../../tasks/tasks.cjs");
|
|
14
|
+
const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
|
|
15
|
+
const getRoundsFromSimScore = (simScore) => {
|
|
16
|
+
if (simScore >= 0.9) return 0;
|
|
17
|
+
if (simScore >= 0.8) return 3;
|
|
18
|
+
if (simScore >= 0.7) return 4;
|
|
19
|
+
if (simScore >= 0.6) return 6;
|
|
20
|
+
if (simScore >= 0.5) return 7;
|
|
21
|
+
return 8;
|
|
22
|
+
};
|
|
23
|
+
const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (req, res, next) => {
|
|
24
|
+
try {
|
|
25
|
+
const tasks$1 = new tasks.Tasks(env, req.logger);
|
|
26
|
+
const { token, headHash, dapp, user } = types.GetFrictionlessCaptchaChallengeRequestBody.parse(req.body);
|
|
27
|
+
if (apiToggleMaintenanceModeEndpoint.getMaintenanceMode()) {
|
|
28
|
+
req.logger.info(() => ({
|
|
29
|
+
msg: "Maintenance mode active - storing dummy token and sending PoW captcha",
|
|
30
|
+
data: { dapp, user }
|
|
31
|
+
}));
|
|
32
|
+
return res.json(
|
|
33
|
+
await tasks$1.frictionlessManager.sendPowCaptcha({
|
|
34
|
+
token,
|
|
35
|
+
score: 0,
|
|
36
|
+
threshold: 0.5,
|
|
37
|
+
scoreComponents: {
|
|
38
|
+
baseScore: 0
|
|
39
|
+
},
|
|
40
|
+
providerSelectEntropy: 0,
|
|
41
|
+
ipAddress: compositeIpAddress.getCompositeIpAddress(req.ip || ""),
|
|
42
|
+
webView: false,
|
|
43
|
+
iFrame: false,
|
|
44
|
+
decryptedHeadHash: ""
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const existingToken = await tasks$1.db.getSessionRecordByToken(token);
|
|
49
|
+
if (existingToken) {
|
|
50
|
+
req.logger.info(() => ({
|
|
51
|
+
token: existingToken,
|
|
52
|
+
msg: "Token has already been used"
|
|
53
|
+
}));
|
|
54
|
+
return next(
|
|
55
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
56
|
+
context: {
|
|
57
|
+
code: 400,
|
|
58
|
+
siteKey: dapp,
|
|
59
|
+
user
|
|
60
|
+
},
|
|
61
|
+
i18n: req.i18n,
|
|
62
|
+
logger: req.logger
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const userSitekeyIpHash = hashUserIp.hashUserIp(user, req.ip || "", dapp);
|
|
67
|
+
const existingSession = await tasks$1.db.getSessionByuserSitekeyIpHash(userSitekeyIpHash);
|
|
68
|
+
if (existingSession) {
|
|
69
|
+
req.logger.info(() => ({
|
|
70
|
+
msg: "Reusing existing session for user-IP-sitekey combination",
|
|
71
|
+
data: {
|
|
72
|
+
userSitekeyIpHash,
|
|
73
|
+
sessionId: existingSession.sessionId,
|
|
74
|
+
captchaType: existingSession.captchaType
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
77
|
+
return res.json({
|
|
78
|
+
[types.ApiParams.captchaType]: existingSession.captchaType,
|
|
79
|
+
[types.ApiParams.sessionId]: existingSession.sessionId,
|
|
80
|
+
[types.ApiParams.status]: "ok"
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const lScore = tasks$1.frictionlessManager.checkLangRules(
|
|
84
|
+
req.headers["accept-language"] || ""
|
|
85
|
+
);
|
|
86
|
+
const {
|
|
87
|
+
baseBotScore,
|
|
88
|
+
timestamp,
|
|
89
|
+
providerSelectEntropy,
|
|
90
|
+
userId,
|
|
91
|
+
userAgent,
|
|
92
|
+
webView,
|
|
93
|
+
iFrame,
|
|
94
|
+
decryptedHeadHash
|
|
95
|
+
} = await tasks$1.frictionlessManager.decryptPayload(token, headHash);
|
|
96
|
+
req.logger.debug(() => ({
|
|
97
|
+
msg: "Decrypted payload",
|
|
98
|
+
data: {
|
|
99
|
+
baseBotScore,
|
|
100
|
+
timestamp,
|
|
101
|
+
providerSelectEntropy,
|
|
102
|
+
userId,
|
|
103
|
+
userAgent,
|
|
104
|
+
webView
|
|
105
|
+
}
|
|
106
|
+
}));
|
|
107
|
+
let botScore = baseBotScore + lScore;
|
|
108
|
+
const clientRecord = await tasks$1.db.getClientRecord(dapp);
|
|
109
|
+
if (!clientRecord) {
|
|
110
|
+
return next(
|
|
111
|
+
new common.ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
112
|
+
context: { code: 400, siteKey: dapp },
|
|
113
|
+
i18n: req.i18n,
|
|
114
|
+
logger: req.logger
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const { valid, reason } = await tasks$1.frictionlessManager.isValidRequest(
|
|
119
|
+
clientRecord,
|
|
120
|
+
types.CaptchaType.frictionless,
|
|
121
|
+
env
|
|
122
|
+
);
|
|
123
|
+
if (!valid) {
|
|
124
|
+
return next(
|
|
125
|
+
new common.ProsopoApiError(reason || "API.BAD_REQUEST", {
|
|
126
|
+
context: {
|
|
127
|
+
code: 400,
|
|
128
|
+
siteKey: dapp,
|
|
129
|
+
user
|
|
130
|
+
},
|
|
131
|
+
i18n: req.i18n,
|
|
132
|
+
logger: req.logger
|
|
133
|
+
})
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const botThreshold = clientRecord.settings?.frictionlessThreshold || DEFAULT_FRICTIONLESS_THRESHOLD;
|
|
137
|
+
let scoreComponents = {
|
|
138
|
+
baseScore: baseBotScore,
|
|
139
|
+
...lScore && { lScore }
|
|
140
|
+
};
|
|
141
|
+
const ipAddress = compositeIpAddress.getCompositeIpAddress(req.ip || "");
|
|
142
|
+
tasks$1.frictionlessManager.setSessionParams({
|
|
143
|
+
token,
|
|
144
|
+
score: botScore,
|
|
145
|
+
threshold: botThreshold,
|
|
146
|
+
scoreComponents,
|
|
147
|
+
providerSelectEntropy,
|
|
148
|
+
ipAddress,
|
|
149
|
+
webView,
|
|
150
|
+
iFrame,
|
|
151
|
+
decryptedHeadHash
|
|
152
|
+
});
|
|
153
|
+
const userScope = blacklistRequestInspector.getRequestUserScope(
|
|
154
|
+
util.flatten(req.headers),
|
|
155
|
+
req.ja4,
|
|
156
|
+
req.ip,
|
|
157
|
+
user
|
|
158
|
+
);
|
|
159
|
+
const userAccessPolicy = (await tasks$1.frictionlessManager.getPrioritisedAccessPolicies(
|
|
160
|
+
userAccessRulesStorage,
|
|
161
|
+
dapp,
|
|
162
|
+
userScope
|
|
163
|
+
))[0];
|
|
164
|
+
const headersUserAgent = req.headers["user-agent"];
|
|
165
|
+
const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent.hashUserAgent(headersUserAgent) : "";
|
|
166
|
+
const headersProsopoUser = req.headers["prosopo-user"];
|
|
167
|
+
if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
|
|
168
|
+
req.logger.info(() => ({
|
|
169
|
+
msg: "User agent or user id does not match",
|
|
170
|
+
data: {
|
|
171
|
+
headersUserAgent,
|
|
172
|
+
hashedHeadersUserAgent,
|
|
173
|
+
userAgent,
|
|
174
|
+
// This is the hashed user agent from the token
|
|
175
|
+
headersProsopoUser,
|
|
176
|
+
userId
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
return res.json(
|
|
180
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
181
|
+
solvedImagesCount: frictionlessTasksUtils.timestampDecayFunction(timestamp),
|
|
182
|
+
userSitekeyIpHash,
|
|
183
|
+
reason: frictionlessTasks.FrictionlessReason.USER_AGENT_MISMATCH
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (userAccessPolicy) {
|
|
188
|
+
const scoreUpdate = tasks$1.frictionlessManager.scoreIncreaseAccessPolicy(
|
|
189
|
+
userAccessPolicy,
|
|
190
|
+
baseBotScore,
|
|
191
|
+
botScore,
|
|
192
|
+
scoreComponents
|
|
193
|
+
);
|
|
194
|
+
botScore = scoreUpdate.score;
|
|
195
|
+
scoreComponents = scoreUpdate.scoreComponents;
|
|
196
|
+
tasks$1.frictionlessManager.updateScore(botScore, scoreComponents);
|
|
197
|
+
if (userAccessPolicy.captchaType === types.CaptchaType.image) {
|
|
198
|
+
return res.json(
|
|
199
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
200
|
+
solvedImagesCount: userAccessPolicy.solvedImagesCount,
|
|
201
|
+
userSitekeyIpHash,
|
|
202
|
+
reason: frictionlessTasks.FrictionlessReason.USER_ACCESS_POLICY
|
|
203
|
+
})
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (userAccessPolicy.captchaType === types.CaptchaType.pow) {
|
|
207
|
+
return res.json(
|
|
208
|
+
await tasks$1.frictionlessManager.sendPowCaptcha({
|
|
209
|
+
userSitekeyIpHash,
|
|
210
|
+
reason: frictionlessTasks.FrictionlessReason.USER_ACCESS_POLICY
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (clientRecord.settings.contextAware?.enabled) {
|
|
216
|
+
const clientEntropy = await tasks$1.frictionlessManager.getClientEntropy(
|
|
217
|
+
clientRecord.account
|
|
218
|
+
);
|
|
219
|
+
if (clientEntropy) {
|
|
220
|
+
if (!decryptedHeadHash) {
|
|
221
|
+
tasks$1.logger.info(() => ({
|
|
222
|
+
msg: "No decryptedHeadHash in session for context aware client"
|
|
223
|
+
}));
|
|
224
|
+
return next(
|
|
225
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
226
|
+
context: {
|
|
227
|
+
code: 400,
|
|
228
|
+
siteKey: dapp,
|
|
229
|
+
user
|
|
230
|
+
},
|
|
231
|
+
i18n: req.i18n,
|
|
232
|
+
logger: req.logger
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const sim = util.compareBinaryStrings(decryptedHeadHash, clientEntropy);
|
|
237
|
+
const isValidContext = sim >= clientRecord.settings.contextAware.threshold;
|
|
238
|
+
if (!isValidContext) {
|
|
239
|
+
return res.json(
|
|
240
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
241
|
+
solvedImagesCount: getRoundsFromSimScore(sim),
|
|
242
|
+
userSitekeyIpHash,
|
|
243
|
+
reason: frictionlessTasks.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED
|
|
244
|
+
})
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (clientRecord.settings.disallowWebView && webView) {
|
|
250
|
+
tasks$1.logger.info(() => ({
|
|
251
|
+
msg: "WebView detected"
|
|
252
|
+
}));
|
|
253
|
+
const scoreUpdate = tasks$1.frictionlessManager.scoreIncreaseWebView(
|
|
254
|
+
baseBotScore,
|
|
255
|
+
botScore,
|
|
256
|
+
scoreComponents
|
|
257
|
+
);
|
|
258
|
+
botScore = scoreUpdate.score;
|
|
259
|
+
scoreComponents = scoreUpdate.scoreComponents;
|
|
260
|
+
tasks$1.frictionlessManager.updateScore(botScore, scoreComponents);
|
|
261
|
+
return res.json(
|
|
262
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
263
|
+
solvedImagesCount: env.config.captchas.solved.count * 2,
|
|
264
|
+
userSitekeyIpHash,
|
|
265
|
+
reason: frictionlessTasks.FrictionlessReason.WEBVIEW_DETECTED
|
|
266
|
+
})
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (frictionlessTasks.FrictionlessManager.timestampTooOld(timestamp)) {
|
|
270
|
+
const scoreUpdate = tasks$1.frictionlessManager.scoreIncreaseTimestamp(
|
|
271
|
+
timestamp,
|
|
272
|
+
baseBotScore,
|
|
273
|
+
botScore,
|
|
274
|
+
scoreComponents
|
|
275
|
+
);
|
|
276
|
+
botScore = scoreUpdate.score;
|
|
277
|
+
scoreComponents = scoreUpdate.scoreComponents;
|
|
278
|
+
tasks$1.frictionlessManager.updateScore(botScore, scoreComponents);
|
|
279
|
+
return res.json(
|
|
280
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
281
|
+
solvedImagesCount: frictionlessTasksUtils.timestampDecayFunction(timestamp),
|
|
282
|
+
userSitekeyIpHash,
|
|
283
|
+
reason: frictionlessTasks.FrictionlessReason.OLD_TIMESTAMP
|
|
284
|
+
})
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const hostVerified = await tasks$1.frictionlessManager.hostVerified(
|
|
288
|
+
providerSelectEntropy
|
|
289
|
+
);
|
|
290
|
+
if (!hostVerified.verified) {
|
|
291
|
+
const scoreUpdate = tasks$1.frictionlessManager.scoreIncreaseUnverifiedHost(
|
|
292
|
+
hostVerified.domain,
|
|
293
|
+
baseBotScore,
|
|
294
|
+
botScore,
|
|
295
|
+
scoreComponents
|
|
296
|
+
);
|
|
297
|
+
botScore = scoreUpdate.score;
|
|
298
|
+
scoreComponents = scoreUpdate.scoreComponents;
|
|
299
|
+
tasks$1.frictionlessManager.updateScore(botScore, scoreComponents);
|
|
300
|
+
}
|
|
301
|
+
if (Number(botScore) > botThreshold) {
|
|
302
|
+
req.logger.info(() => ({
|
|
303
|
+
msg: "Bot score is greater than threshold",
|
|
304
|
+
data: {
|
|
305
|
+
botScore,
|
|
306
|
+
botThreshold,
|
|
307
|
+
token
|
|
308
|
+
}
|
|
309
|
+
}));
|
|
310
|
+
return res.json(
|
|
311
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
312
|
+
solvedImagesCount: env.config.captchas.solved.count,
|
|
313
|
+
userSitekeyIpHash,
|
|
314
|
+
reason: frictionlessTasks.FrictionlessReason.BOT_SCORE_ABOVE_THRESHOLD
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
return res.json(
|
|
319
|
+
await tasks$1.frictionlessManager.sendPowCaptcha({
|
|
320
|
+
userSitekeyIpHash
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
req.logger.error(() => ({
|
|
325
|
+
err,
|
|
326
|
+
msg: "Error in frictionless captcha challenge"
|
|
327
|
+
}));
|
|
328
|
+
return next(
|
|
329
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
330
|
+
context: { code: 400, error: err },
|
|
331
|
+
i18n: req.i18n,
|
|
332
|
+
logger: req.logger
|
|
333
|
+
})
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
module.exports = getFrictionlessCaptchaChallenge;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const common = require("@prosopo/common");
|
|
3
|
+
const datasets = require("@prosopo/datasets");
|
|
4
|
+
const types = require("@prosopo/types");
|
|
5
|
+
const util = require("@prosopo/util");
|
|
6
|
+
require("../../tasks/index.cjs");
|
|
7
|
+
const blacklistRequestInspector = require("../blacklistRequestInspector.cjs");
|
|
8
|
+
const validateAddress = require("../validateAddress.cjs");
|
|
9
|
+
const tasks = require("../../tasks/tasks.cjs");
|
|
10
|
+
const getImageCaptchaChallenge = (env, userAccessRulesStorage) => async (req, res, next) => {
|
|
11
|
+
const tasks$1 = new tasks.Tasks(env, req.logger);
|
|
12
|
+
let parsed;
|
|
13
|
+
if (!req.ip) {
|
|
14
|
+
return next(
|
|
15
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
16
|
+
context: { code: 400, error: "IP address not found" },
|
|
17
|
+
i18n: req.i18n,
|
|
18
|
+
logger: req.logger
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
const ipAddress = util.getIPAddress(req.ip || "");
|
|
23
|
+
try {
|
|
24
|
+
parsed = types.CaptchaRequestBody.parse(req.body);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return next(
|
|
27
|
+
new common.ProsopoApiError("CAPTCHA.PARSE_ERROR", {
|
|
28
|
+
context: { code: 400, error: err },
|
|
29
|
+
i18n: req.i18n,
|
|
30
|
+
logger: req.logger
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const { datasetId, user, dapp, sessionId } = parsed;
|
|
35
|
+
validateAddress.validateSiteKey(dapp);
|
|
36
|
+
validateAddress.validateAddr(user);
|
|
37
|
+
try {
|
|
38
|
+
const clientRecord = await tasks$1.db.getClientRecord(dapp);
|
|
39
|
+
if (!clientRecord) {
|
|
40
|
+
return next(
|
|
41
|
+
new common.ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
|
|
42
|
+
context: { code: 400, siteKey: dapp },
|
|
43
|
+
i18n: req.i18n,
|
|
44
|
+
logger: req.logger
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const userScope = blacklistRequestInspector.getRequestUserScope(
|
|
49
|
+
util.flatten(req.headers),
|
|
50
|
+
req.ja4,
|
|
51
|
+
req.ip,
|
|
52
|
+
user
|
|
53
|
+
);
|
|
54
|
+
const userAccessPolicy = (await tasks$1.imgCaptchaManager.getPrioritisedAccessPolicies(
|
|
55
|
+
userAccessRulesStorage,
|
|
56
|
+
dapp,
|
|
57
|
+
userScope
|
|
58
|
+
))[0];
|
|
59
|
+
const {
|
|
60
|
+
valid,
|
|
61
|
+
reason,
|
|
62
|
+
sessionId: validSessionId,
|
|
63
|
+
solvedImagesCount
|
|
64
|
+
} = await tasks$1.imgCaptchaManager.isValidRequest(
|
|
65
|
+
clientRecord,
|
|
66
|
+
types.CaptchaType.image,
|
|
67
|
+
env,
|
|
68
|
+
sessionId,
|
|
69
|
+
userAccessPolicy,
|
|
70
|
+
req.ip
|
|
71
|
+
);
|
|
72
|
+
if (!valid) {
|
|
73
|
+
return next(
|
|
74
|
+
new common.ProsopoApiError(reason || "API.BAD_REQUEST", {
|
|
75
|
+
context: {
|
|
76
|
+
code: 400,
|
|
77
|
+
siteKey: dapp,
|
|
78
|
+
user
|
|
79
|
+
},
|
|
80
|
+
i18n: req.i18n,
|
|
81
|
+
logger: req.logger
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const captchaConfig = {
|
|
86
|
+
solved: {
|
|
87
|
+
count: solvedImagesCount || userAccessPolicy?.solvedImagesCount || env.config.captchas.solved.count
|
|
88
|
+
},
|
|
89
|
+
unsolved: {
|
|
90
|
+
count: userAccessPolicy?.unsolvedImagesCount || env.config.captchas.unsolved.count
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const taskData = await tasks$1.imgCaptchaManager.getRandomCaptchasAndRequestHash(
|
|
94
|
+
datasetId,
|
|
95
|
+
user,
|
|
96
|
+
ipAddress,
|
|
97
|
+
captchaConfig,
|
|
98
|
+
clientRecord.settings.imageThreshold ?? 0.8,
|
|
99
|
+
validSessionId
|
|
100
|
+
);
|
|
101
|
+
const captchaResponse = {
|
|
102
|
+
[types.ApiParams.status]: "ok",
|
|
103
|
+
[types.ApiParams.captchas]: taskData.captchas.map((captcha) => ({
|
|
104
|
+
...captcha,
|
|
105
|
+
target: req.t(`TARGET.${captcha.target}`),
|
|
106
|
+
items: captcha.items.map(
|
|
107
|
+
(item) => datasets.parseCaptchaAssets(item, env.assetsResolver)
|
|
108
|
+
)
|
|
109
|
+
})),
|
|
110
|
+
[types.ApiParams.requestHash]: taskData.requestHash,
|
|
111
|
+
[types.ApiParams.timestamp]: taskData.timestamp.toString(),
|
|
112
|
+
[types.ApiParams.signature]: {
|
|
113
|
+
[types.ApiParams.provider]: {
|
|
114
|
+
[types.ApiParams.requestHash]: taskData.signedRequestHash
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
req.logger.info(() => ({
|
|
119
|
+
msg: "Image captcha challenge issued",
|
|
120
|
+
data: {
|
|
121
|
+
captchaType: types.CaptchaType.image,
|
|
122
|
+
requestHash: taskData.requestHash,
|
|
123
|
+
solvedImagesCount: captchaConfig.solved.count,
|
|
124
|
+
user,
|
|
125
|
+
dapp,
|
|
126
|
+
sessionId
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
return res.json(captchaResponse);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
req.logger.error(() => ({
|
|
132
|
+
err,
|
|
133
|
+
data: req.params,
|
|
134
|
+
msg: "Error in image captcha challenge request"
|
|
135
|
+
}));
|
|
136
|
+
return next(
|
|
137
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
138
|
+
context: {
|
|
139
|
+
error: err,
|
|
140
|
+
code: 500,
|
|
141
|
+
params: req.params
|
|
142
|
+
},
|
|
143
|
+
i18n: req.i18n,
|
|
144
|
+
logger: req.logger
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
module.exports = getImageCaptchaChallenge;
|