@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +214 -0
  2. package/dist/api/admin/apiAdminRoutesProvider.js +13 -18
  3. package/dist/api/admin/apiToggleMaintenanceModeEndpoint.js +40 -0
  4. package/dist/api/blacklistRequestInspector.js +4 -4
  5. package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +338 -0
  6. package/dist/api/captcha/getImageCaptchaChallenge.js +150 -0
  7. package/dist/api/captcha/getPoWCaptchaChallenge.js +156 -0
  8. package/dist/api/captcha/submitImageCaptchaSolution.js +87 -0
  9. package/dist/api/captcha/submitPoWCaptchaSolution.js +77 -0
  10. package/dist/api/captcha.js +18 -606
  11. package/dist/api/verify.js +24 -1
  12. package/dist/cjs/api/admin/apiAdminRoutesProvider.cjs +13 -18
  13. package/dist/cjs/api/admin/apiRegisterSiteKeyEndpoint.cjs +2 -1
  14. package/dist/cjs/api/admin/apiRemoveDetectorKeyEndpoint.cjs +3 -2
  15. package/dist/cjs/api/admin/apiToggleMaintenanceModeEndpoint.cjs +41 -0
  16. package/dist/cjs/api/blacklistRequestInspector.cjs +3 -3
  17. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +337 -0
  18. package/dist/cjs/api/captcha/getImageCaptchaChallenge.cjs +149 -0
  19. package/dist/cjs/api/captcha/getPoWCaptchaChallenge.cjs +155 -0
  20. package/dist/cjs/api/captcha/submitImageCaptchaSolution.cjs +86 -0
  21. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +76 -0
  22. package/dist/cjs/api/captcha.cjs +17 -605
  23. package/dist/cjs/api/ja4Middleware.cjs +2 -1
  24. package/dist/cjs/api/verify.cjs +24 -1
  25. package/dist/cjs/index.cjs +2 -0
  26. package/dist/cjs/schedulers/setClientEntropy.cjs +36 -0
  27. package/dist/cjs/tasks/captchaManager.cjs +7 -22
  28. package/dist/cjs/tasks/client/clientTasks.cjs +18 -36
  29. package/dist/cjs/tasks/detection/decodePayload.cjs +385 -714
  30. package/dist/cjs/tasks/detection/getBotScore.cjs +15 -2
  31. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +136 -30
  32. package/dist/cjs/tasks/imgCaptcha/imgCaptchaTasks.cjs +25 -13
  33. package/dist/cjs/tasks/powCaptcha/powTasks.cjs +8 -8
  34. package/dist/cjs/tasks/tasks.cjs +1 -0
  35. package/dist/cjs/util.cjs +14 -1
  36. package/dist/cjs/utils/hashUserIp.cjs +9 -0
  37. package/dist/index.js +2 -0
  38. package/dist/schedulers/setClientEntropy.js +36 -0
  39. package/dist/tasks/captchaManager.js +5 -21
  40. package/dist/tasks/client/clientTasks.js +19 -37
  41. package/dist/tasks/detection/decodePayload.js +385 -714
  42. package/dist/tasks/detection/getBotScore.js +17 -4
  43. package/dist/tasks/frictionless/frictionlessTasks.js +137 -31
  44. package/dist/tasks/imgCaptcha/imgCaptchaTasks.js +25 -13
  45. package/dist/tasks/powCaptcha/powTasks.js +8 -8
  46. package/dist/tasks/tasks.js +1 -0
  47. package/dist/util.js +14 -1
  48. package/dist/utils/hashUserIp.js +9 -0
  49. package/package.json +24 -25
package/CHANGELOG.md CHANGED
@@ -1,5 +1,219 @@
1
1
  # @prosopo/provider
2
2
 
3
+ ## 3.13.0
4
+ ### Minor Changes
5
+
6
+ - bb5f41c: Context awareness
7
+
8
+ ### Patch Changes
9
+
10
+ - fdef625: fix maint mode
11
+ - 55a64c6: stop refresh image to pow
12
+ - aa8216a: bump
13
+ - 8ce9205: Change engine requirements
14
+ - 6ac5367: Less drastic reaction to bad sim score
15
+ - b6e98b2: Run npm audit
16
+ - 55a64c6: Persist sessions for user ip combinations
17
+ - Updated dependencies [8ce9205]
18
+ - Updated dependencies [15ae7cf]
19
+ - Updated dependencies [bb5f41c]
20
+ - Updated dependencies [55a64c6]
21
+ - Updated dependencies [8ce9205]
22
+ - Updated dependencies [df79c03]
23
+ - Updated dependencies [8f22479]
24
+ - Updated dependencies [b6e98b2]
25
+ - Updated dependencies [55a64c6]
26
+ - @prosopo/user-access-policy@3.5.28
27
+ - @prosopo/types@3.6.0
28
+ - @prosopo/types-database@4.0.0
29
+ - @prosopo/database@3.5.0
30
+ - @prosopo/util@3.2.0
31
+ - @prosopo/api-express-router@3.0.34
32
+ - @prosopo/load-balancer@2.8.9
33
+ - @prosopo/util-crypto@13.5.24
34
+ - @prosopo/api-route@2.6.30
35
+ - @prosopo/types-env@2.7.47
36
+ - @prosopo/datasets@3.0.43
37
+ - @prosopo/keyring@2.8.36
38
+ - @prosopo/common@3.1.22
39
+ - @prosopo/locale@3.1.22
40
+ - @prosopo/api@3.1.33
41
+ - @prosopo/env@3.2.22
42
+ - @prosopo/config@3.1.22
43
+
44
+ ## 3.12.14
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [8f1773a]
48
+ - @prosopo/types@3.5.11
49
+ - @prosopo/api@3.1.32
50
+ - @prosopo/api-express-router@3.0.33
51
+ - @prosopo/database@3.4.13
52
+ - @prosopo/datasets@3.0.42
53
+ - @prosopo/env@3.2.21
54
+ - @prosopo/keyring@2.8.35
55
+ - @prosopo/load-balancer@2.8.8
56
+ - @prosopo/types-database@3.3.13
57
+ - @prosopo/types-env@2.7.46
58
+ - @prosopo/user-access-policy@3.5.27
59
+
60
+ ## 3.12.13
61
+ ### Patch Changes
62
+
63
+ - cb8ab85: head entropy for bot detection
64
+ - Updated dependencies [cb8ab85]
65
+ - @prosopo/types-database@3.3.12
66
+ - @prosopo/types@3.5.10
67
+ - @prosopo/api@3.1.31
68
+ - @prosopo/database@3.4.12
69
+ - @prosopo/datasets@3.0.41
70
+ - @prosopo/types-env@2.7.45
71
+ - @prosopo/api-express-router@3.0.32
72
+ - @prosopo/env@3.2.20
73
+ - @prosopo/keyring@2.8.34
74
+ - @prosopo/load-balancer@2.8.7
75
+ - @prosopo/user-access-policy@3.5.26
76
+
77
+ ## 3.12.12
78
+ ### Patch Changes
79
+
80
+ - 43907e8: Convert timestamp fields from numbers to Date objects throughout codebase
81
+ - b4639ec: Merge frictionless tokens into sessions
82
+ - 7101036: Force consistent IPs logic
83
+ - Updated dependencies [43907e8]
84
+ - Updated dependencies [b4639ec]
85
+ - Updated dependencies [005ce66]
86
+ - Updated dependencies [b58046d]
87
+ - Updated dependencies [7101036]
88
+ - @prosopo/types-database@3.3.11
89
+ - @prosopo/types@3.5.9
90
+ - @prosopo/database@3.4.11
91
+ - @prosopo/user-access-policy@3.5.25
92
+ - @prosopo/load-balancer@2.8.6
93
+ - @prosopo/util@3.1.7
94
+ - @prosopo/datasets@3.0.40
95
+ - @prosopo/types-env@2.7.44
96
+ - @prosopo/api@3.1.30
97
+ - @prosopo/api-express-router@3.0.31
98
+ - @prosopo/env@3.2.19
99
+ - @prosopo/keyring@2.8.33
100
+
101
+ ## 3.12.11
102
+ ### Patch Changes
103
+
104
+ - 4b6dc9d: Block at verify
105
+ - e5c259d: .
106
+ - 6420187: Save iframe
107
+ - Updated dependencies [b10a65f]
108
+ - Updated dependencies [e5c259d]
109
+ - Updated dependencies [6420187]
110
+ - @prosopo/types-database@3.3.10
111
+ - @prosopo/types@3.5.8
112
+ - @prosopo/database@3.4.10
113
+ - @prosopo/datasets@3.0.39
114
+ - @prosopo/types-env@2.7.43
115
+ - @prosopo/api@3.1.29
116
+ - @prosopo/api-express-router@3.0.30
117
+ - @prosopo/env@3.2.18
118
+ - @prosopo/keyring@2.8.32
119
+ - @prosopo/load-balancer@2.8.5
120
+ - @prosopo/user-access-policy@3.5.24
121
+
122
+ ## 3.12.10
123
+ ### Patch Changes
124
+
125
+ - b8185a4: feat/uap-rules-syncer
126
+ - 3a027ef: Fix session storer
127
+ - 3a027ef: Release cycle
128
+ - Updated dependencies [c9d8fdf]
129
+ - Updated dependencies [b8185a4]
130
+ - Updated dependencies [3a027ef]
131
+ - Updated dependencies [3a027ef]
132
+ - @prosopo/user-access-policy@3.5.23
133
+ - @prosopo/api@3.1.28
134
+ - @prosopo/common@3.1.21
135
+ - @prosopo/api-express-router@3.0.29
136
+ - @prosopo/api-route@2.6.29
137
+ - @prosopo/database@3.4.9
138
+ - @prosopo/config@3.1.21
139
+ - @prosopo/types-database@3.3.9
140
+ - @prosopo/datasets@3.0.38
141
+ - @prosopo/env@3.2.17
142
+ - @prosopo/keyring@2.8.31
143
+ - @prosopo/load-balancer@2.8.4
144
+ - @prosopo/types-env@2.7.42
145
+ - @prosopo/locale@3.1.21
146
+ - @prosopo/types@3.5.7
147
+ - @prosopo/util@3.1.6
148
+ - @prosopo/util-crypto@13.5.23
149
+
150
+ ## 3.12.9
151
+ ### Patch Changes
152
+
153
+ - 8491159: Store webview
154
+
155
+ ## 3.12.8
156
+ ### Patch Changes
157
+
158
+ - 5d11a81: Adding maintenance mode
159
+ - Updated dependencies [5d11a81]
160
+ - @prosopo/types@3.5.6
161
+ - @prosopo/api@3.1.27
162
+ - @prosopo/api-express-router@3.0.28
163
+ - @prosopo/database@3.4.8
164
+ - @prosopo/datasets@3.0.37
165
+ - @prosopo/env@3.2.16
166
+ - @prosopo/keyring@2.8.30
167
+ - @prosopo/load-balancer@2.8.3
168
+ - @prosopo/types-database@3.3.8
169
+ - @prosopo/types-env@2.7.41
170
+ - @prosopo/user-access-policy@3.5.22
171
+
172
+ ## 3.12.7
173
+ ### Patch Changes
174
+
175
+ - cbc5d8e: Additional logging
176
+
177
+ ## 3.12.6
178
+ ### Patch Changes
179
+
180
+ - 494c5a8: Updated payload
181
+ - Updated dependencies [494c5a8]
182
+ - @prosopo/types-database@3.3.7
183
+ - @prosopo/types@3.5.5
184
+ - @prosopo/database@3.4.7
185
+ - @prosopo/datasets@3.0.36
186
+ - @prosopo/types-env@2.7.40
187
+ - @prosopo/api@3.1.26
188
+ - @prosopo/api-express-router@3.0.27
189
+ - @prosopo/env@3.2.15
190
+ - @prosopo/keyring@2.8.29
191
+ - @prosopo/load-balancer@2.8.2
192
+ - @prosopo/user-access-policy@3.5.21
193
+
194
+ ## 3.12.5
195
+ ### Patch Changes
196
+
197
+ - 4ba029e: repo maintainance
198
+
199
+ ## 3.12.4
200
+ ### Patch Changes
201
+
202
+ - 08ff50f: Hot fix country code
203
+ - Updated dependencies [08ff50f]
204
+ - Updated dependencies [08ff50f]
205
+ - @prosopo/types-database@3.3.6
206
+ - @prosopo/types@3.5.4
207
+ - @prosopo/database@3.4.6
208
+ - @prosopo/datasets@3.0.35
209
+ - @prosopo/types-env@2.7.39
210
+ - @prosopo/api@3.1.25
211
+ - @prosopo/api-express-router@3.0.26
212
+ - @prosopo/env@3.2.14
213
+ - @prosopo/keyring@2.8.28
214
+ - @prosopo/load-balancer@2.8.1
215
+ - @prosopo/user-access-policy@3.5.20
216
+
3
217
  ## 3.12.3
4
218
  ### Patch Changes
5
219
 
@@ -1,30 +1,25 @@
1
1
  import { AdminApiPaths } from "@prosopo/types";
2
2
  import { ApiRegisterSiteKeyEndpoint } from "./apiRegisterSiteKeyEndpoint.js";
3
3
  import { ApiRemoveDetectorKeyEndpoint } from "./apiRemoveDetectorKeyEndpoint.js";
4
+ import { ApiToggleMaintenanceModeEndpoint } from "./apiToggleMaintenanceModeEndpoint.js";
4
5
  import { ApiUpdateDetectorKeyEndpoint } from "./apiUpdateDetectorKeyEndpoint.js";
5
6
  class ApiAdminRoutesProvider {
6
7
  constructor(tasks) {
7
8
  this.tasks = tasks;
8
9
  }
9
10
  getRoutes() {
10
- return [
11
- {
12
- path: AdminApiPaths.SiteKeyRegister,
13
- endpoint: new ApiRegisterSiteKeyEndpoint(this.tasks.clientTaskManager)
14
- },
15
- {
16
- path: AdminApiPaths.UpdateDetectorKey,
17
- endpoint: new ApiUpdateDetectorKeyEndpoint(
18
- this.tasks.clientTaskManager
19
- )
20
- },
21
- {
22
- path: AdminApiPaths.RemoveDetectorKey,
23
- endpoint: new ApiRemoveDetectorKeyEndpoint(
24
- this.tasks.clientTaskManager
25
- )
26
- }
27
- ];
11
+ return {
12
+ [AdminApiPaths.SiteKeyRegister]: new ApiRegisterSiteKeyEndpoint(
13
+ this.tasks.clientTaskManager
14
+ ),
15
+ [AdminApiPaths.UpdateDetectorKey]: new ApiUpdateDetectorKeyEndpoint(
16
+ this.tasks.clientTaskManager
17
+ ),
18
+ [AdminApiPaths.RemoveDetectorKey]: new ApiRemoveDetectorKeyEndpoint(
19
+ this.tasks.clientTaskManager
20
+ ),
21
+ [AdminApiPaths.ToggleMaintenanceMode]: new ApiToggleMaintenanceModeEndpoint()
22
+ };
28
23
  }
29
24
  }
30
25
  export {
@@ -0,0 +1,40 @@
1
+ import { ApiEndpointResponseStatus } from "@prosopo/api-route";
2
+ import { getLogger } from "@prosopo/common";
3
+ import { ToggleMaintenanceModeBody } from "@prosopo/types";
4
+ function getMaintenanceMode() {
5
+ return process.env.MAINTENANCE_MODE?.toLowerCase() === "true";
6
+ }
7
+ function setMaintenanceMode(enabled) {
8
+ process.env.MAINTENANCE_MODE = enabled ? "true" : "false";
9
+ }
10
+ class ApiToggleMaintenanceModeEndpoint {
11
+ async processRequest(args, logger) {
12
+ const { enabled } = args;
13
+ logger = logger || getLogger("info", import.meta.url);
14
+ const previousMode = getMaintenanceMode();
15
+ logger.info(() => ({
16
+ data: { enabled, previous: previousMode },
17
+ msg: "Toggling maintenance mode"
18
+ }));
19
+ setMaintenanceMode(enabled);
20
+ const currentMode = getMaintenanceMode();
21
+ logger.info(() => ({
22
+ data: { enabled: currentMode },
23
+ msg: "Maintenance mode updated"
24
+ }));
25
+ return {
26
+ status: ApiEndpointResponseStatus.SUCCESS,
27
+ data: {
28
+ maintenanceMode: currentMode
29
+ }
30
+ };
31
+ }
32
+ getRequestArgsSchema() {
33
+ return ToggleMaintenanceModeBody;
34
+ }
35
+ }
36
+ export {
37
+ ApiToggleMaintenanceModeEndpoint,
38
+ getMaintenanceMode,
39
+ setMaintenanceMode
40
+ };
@@ -1,5 +1,5 @@
1
1
  import { ApiPrefix } from "@prosopo/types";
2
- import { userScopeInputSchema, ScopeMatch, AccessPolicyType } from "@prosopo/user-access-policy";
2
+ import { userScopeInput, FilterScopeMatch, AccessPolicyType } from "@prosopo/user-access-policy";
3
3
  import { uniqueSubsets } from "@prosopo/util";
4
4
  const getRequestUserScope = (requestHeaders, ja4, ip, user) => {
5
5
  const userAgent = requestHeaders["user-agent"] ? requestHeaders["user-agent"].toString() : void 0;
@@ -32,16 +32,16 @@ const getPrioritisedAccessRule = async (userAccessRulesStorage, userScope, clien
32
32
  if (Object.values(scope).every((value) => value === void 0)) {
33
33
  continue;
34
34
  }
35
- const parsedUserScope = userScopeInputSchema.parse(scope);
35
+ const parsedUserScope = userScopeInput.parse(scope);
36
36
  const filter = {
37
37
  ...clientOrUndefined && {
38
38
  policyScope: {
39
39
  clientId: clientOrUndefined
40
40
  }
41
41
  },
42
- policyScopeMatch: ScopeMatch.Exact,
42
+ policyScopeMatch: FilterScopeMatch.Exact,
43
43
  userScope: parsedUserScope,
44
- userScopeMatch: ScopeMatch.Exact
44
+ userScopeMatch: FilterScopeMatch.Exact
45
45
  };
46
46
  policyPromises.push(userAccessRulesStorage.findRules(filter, true, true));
47
47
  }
@@ -0,0 +1,338 @@
1
+ import { ProsopoApiError } from "@prosopo/common";
2
+ import { GetFrictionlessCaptchaChallengeRequestBody, ApiParams, CaptchaType } from "@prosopo/types";
3
+ import { flatten, compareBinaryStrings } from "@prosopo/util";
4
+ import { getCompositeIpAddress } from "../../compositeIpAddress.js";
5
+ import { FrictionlessReason, FrictionlessManager } from "../../tasks/frictionless/frictionlessTasks.js";
6
+ import { timestampDecayFunction } from "../../tasks/frictionless/frictionlessTasksUtils.js";
7
+ import "../../tasks/index.js";
8
+ import { hashUserAgent } from "../../utils/hashUserAgent.js";
9
+ import { hashUserIp } from "../../utils/hashUserIp.js";
10
+ import { getMaintenanceMode } from "../admin/apiToggleMaintenanceModeEndpoint.js";
11
+ import { getRequestUserScope } from "../blacklistRequestInspector.js";
12
+ import { Tasks } from "../../tasks/tasks.js";
13
+ const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
14
+ const getRoundsFromSimScore = (simScore) => {
15
+ if (simScore >= 0.9) return 0;
16
+ if (simScore >= 0.8) return 3;
17
+ if (simScore >= 0.7) return 4;
18
+ if (simScore >= 0.6) return 6;
19
+ if (simScore >= 0.5) return 7;
20
+ return 8;
21
+ };
22
+ const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (req, res, next) => {
23
+ try {
24
+ const tasks = new Tasks(env, req.logger);
25
+ const { token, headHash, dapp, user } = GetFrictionlessCaptchaChallengeRequestBody.parse(req.body);
26
+ if (getMaintenanceMode()) {
27
+ req.logger.info(() => ({
28
+ msg: "Maintenance mode active - storing dummy token and sending PoW captcha",
29
+ data: { dapp, user }
30
+ }));
31
+ return res.json(
32
+ await tasks.frictionlessManager.sendPowCaptcha({
33
+ token,
34
+ score: 0,
35
+ threshold: 0.5,
36
+ scoreComponents: {
37
+ baseScore: 0
38
+ },
39
+ providerSelectEntropy: 0,
40
+ ipAddress: getCompositeIpAddress(req.ip || ""),
41
+ webView: false,
42
+ iFrame: false,
43
+ decryptedHeadHash: ""
44
+ })
45
+ );
46
+ }
47
+ const existingToken = await tasks.db.getSessionRecordByToken(token);
48
+ if (existingToken) {
49
+ req.logger.info(() => ({
50
+ token: existingToken,
51
+ msg: "Token has already been used"
52
+ }));
53
+ return next(
54
+ new ProsopoApiError("API.BAD_REQUEST", {
55
+ context: {
56
+ code: 400,
57
+ siteKey: dapp,
58
+ user
59
+ },
60
+ i18n: req.i18n,
61
+ logger: req.logger
62
+ })
63
+ );
64
+ }
65
+ const userSitekeyIpHash = hashUserIp(user, req.ip || "", dapp);
66
+ const existingSession = await tasks.db.getSessionByuserSitekeyIpHash(userSitekeyIpHash);
67
+ if (existingSession) {
68
+ req.logger.info(() => ({
69
+ msg: "Reusing existing session for user-IP-sitekey combination",
70
+ data: {
71
+ userSitekeyIpHash,
72
+ sessionId: existingSession.sessionId,
73
+ captchaType: existingSession.captchaType
74
+ }
75
+ }));
76
+ return res.json({
77
+ [ApiParams.captchaType]: existingSession.captchaType,
78
+ [ApiParams.sessionId]: existingSession.sessionId,
79
+ [ApiParams.status]: "ok"
80
+ });
81
+ }
82
+ const lScore = tasks.frictionlessManager.checkLangRules(
83
+ req.headers["accept-language"] || ""
84
+ );
85
+ const {
86
+ baseBotScore,
87
+ timestamp,
88
+ providerSelectEntropy,
89
+ userId,
90
+ userAgent,
91
+ webView,
92
+ iFrame,
93
+ decryptedHeadHash
94
+ } = await tasks.frictionlessManager.decryptPayload(token, headHash);
95
+ req.logger.debug(() => ({
96
+ msg: "Decrypted payload",
97
+ data: {
98
+ baseBotScore,
99
+ timestamp,
100
+ providerSelectEntropy,
101
+ userId,
102
+ userAgent,
103
+ webView
104
+ }
105
+ }));
106
+ let botScore = baseBotScore + lScore;
107
+ const clientRecord = await tasks.db.getClientRecord(dapp);
108
+ if (!clientRecord) {
109
+ return next(
110
+ new ProsopoApiError("API.SITE_KEY_NOT_REGISTERED", {
111
+ context: { code: 400, siteKey: dapp },
112
+ i18n: req.i18n,
113
+ logger: req.logger
114
+ })
115
+ );
116
+ }
117
+ const { valid, reason } = await tasks.frictionlessManager.isValidRequest(
118
+ clientRecord,
119
+ CaptchaType.frictionless,
120
+ env
121
+ );
122
+ if (!valid) {
123
+ return next(
124
+ new ProsopoApiError(reason || "API.BAD_REQUEST", {
125
+ context: {
126
+ code: 400,
127
+ siteKey: dapp,
128
+ user
129
+ },
130
+ i18n: req.i18n,
131
+ logger: req.logger
132
+ })
133
+ );
134
+ }
135
+ const botThreshold = clientRecord.settings?.frictionlessThreshold || DEFAULT_FRICTIONLESS_THRESHOLD;
136
+ let scoreComponents = {
137
+ baseScore: baseBotScore,
138
+ ...lScore && { lScore }
139
+ };
140
+ const ipAddress = getCompositeIpAddress(req.ip || "");
141
+ tasks.frictionlessManager.setSessionParams({
142
+ token,
143
+ score: botScore,
144
+ threshold: botThreshold,
145
+ scoreComponents,
146
+ providerSelectEntropy,
147
+ ipAddress,
148
+ webView,
149
+ iFrame,
150
+ decryptedHeadHash
151
+ });
152
+ const userScope = getRequestUserScope(
153
+ flatten(req.headers),
154
+ req.ja4,
155
+ req.ip,
156
+ user
157
+ );
158
+ const userAccessPolicy = (await tasks.frictionlessManager.getPrioritisedAccessPolicies(
159
+ userAccessRulesStorage,
160
+ dapp,
161
+ userScope
162
+ ))[0];
163
+ const headersUserAgent = req.headers["user-agent"];
164
+ const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent(headersUserAgent) : "";
165
+ const headersProsopoUser = req.headers["prosopo-user"];
166
+ if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
167
+ req.logger.info(() => ({
168
+ msg: "User agent or user id does not match",
169
+ data: {
170
+ headersUserAgent,
171
+ hashedHeadersUserAgent,
172
+ userAgent,
173
+ // This is the hashed user agent from the token
174
+ headersProsopoUser,
175
+ userId
176
+ }
177
+ }));
178
+ return res.json(
179
+ await tasks.frictionlessManager.sendImageCaptcha({
180
+ solvedImagesCount: timestampDecayFunction(timestamp),
181
+ userSitekeyIpHash,
182
+ reason: FrictionlessReason.USER_AGENT_MISMATCH
183
+ })
184
+ );
185
+ }
186
+ if (userAccessPolicy) {
187
+ const scoreUpdate = tasks.frictionlessManager.scoreIncreaseAccessPolicy(
188
+ userAccessPolicy,
189
+ baseBotScore,
190
+ botScore,
191
+ scoreComponents
192
+ );
193
+ botScore = scoreUpdate.score;
194
+ scoreComponents = scoreUpdate.scoreComponents;
195
+ tasks.frictionlessManager.updateScore(botScore, scoreComponents);
196
+ if (userAccessPolicy.captchaType === CaptchaType.image) {
197
+ return res.json(
198
+ await tasks.frictionlessManager.sendImageCaptcha({
199
+ solvedImagesCount: userAccessPolicy.solvedImagesCount,
200
+ userSitekeyIpHash,
201
+ reason: FrictionlessReason.USER_ACCESS_POLICY
202
+ })
203
+ );
204
+ }
205
+ if (userAccessPolicy.captchaType === CaptchaType.pow) {
206
+ return res.json(
207
+ await tasks.frictionlessManager.sendPowCaptcha({
208
+ userSitekeyIpHash,
209
+ reason: FrictionlessReason.USER_ACCESS_POLICY
210
+ })
211
+ );
212
+ }
213
+ }
214
+ if (clientRecord.settings.contextAware?.enabled) {
215
+ const clientEntropy = await tasks.frictionlessManager.getClientEntropy(
216
+ clientRecord.account
217
+ );
218
+ if (clientEntropy) {
219
+ if (!decryptedHeadHash) {
220
+ tasks.logger.info(() => ({
221
+ msg: "No decryptedHeadHash in session for context aware client"
222
+ }));
223
+ return next(
224
+ new ProsopoApiError("API.BAD_REQUEST", {
225
+ context: {
226
+ code: 400,
227
+ siteKey: dapp,
228
+ user
229
+ },
230
+ i18n: req.i18n,
231
+ logger: req.logger
232
+ })
233
+ );
234
+ }
235
+ const sim = compareBinaryStrings(decryptedHeadHash, clientEntropy);
236
+ const isValidContext = sim >= clientRecord.settings.contextAware.threshold;
237
+ if (!isValidContext) {
238
+ return res.json(
239
+ await tasks.frictionlessManager.sendImageCaptcha({
240
+ solvedImagesCount: getRoundsFromSimScore(sim),
241
+ userSitekeyIpHash,
242
+ reason: FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED
243
+ })
244
+ );
245
+ }
246
+ }
247
+ }
248
+ if (clientRecord.settings.disallowWebView && webView) {
249
+ tasks.logger.info(() => ({
250
+ msg: "WebView detected"
251
+ }));
252
+ const scoreUpdate = tasks.frictionlessManager.scoreIncreaseWebView(
253
+ baseBotScore,
254
+ botScore,
255
+ scoreComponents
256
+ );
257
+ botScore = scoreUpdate.score;
258
+ scoreComponents = scoreUpdate.scoreComponents;
259
+ tasks.frictionlessManager.updateScore(botScore, scoreComponents);
260
+ return res.json(
261
+ await tasks.frictionlessManager.sendImageCaptcha({
262
+ solvedImagesCount: env.config.captchas.solved.count * 2,
263
+ userSitekeyIpHash,
264
+ reason: FrictionlessReason.WEBVIEW_DETECTED
265
+ })
266
+ );
267
+ }
268
+ if (FrictionlessManager.timestampTooOld(timestamp)) {
269
+ const scoreUpdate = tasks.frictionlessManager.scoreIncreaseTimestamp(
270
+ timestamp,
271
+ baseBotScore,
272
+ botScore,
273
+ scoreComponents
274
+ );
275
+ botScore = scoreUpdate.score;
276
+ scoreComponents = scoreUpdate.scoreComponents;
277
+ tasks.frictionlessManager.updateScore(botScore, scoreComponents);
278
+ return res.json(
279
+ await tasks.frictionlessManager.sendImageCaptcha({
280
+ solvedImagesCount: timestampDecayFunction(timestamp),
281
+ userSitekeyIpHash,
282
+ reason: FrictionlessReason.OLD_TIMESTAMP
283
+ })
284
+ );
285
+ }
286
+ const hostVerified = await tasks.frictionlessManager.hostVerified(
287
+ providerSelectEntropy
288
+ );
289
+ if (!hostVerified.verified) {
290
+ const scoreUpdate = tasks.frictionlessManager.scoreIncreaseUnverifiedHost(
291
+ hostVerified.domain,
292
+ baseBotScore,
293
+ botScore,
294
+ scoreComponents
295
+ );
296
+ botScore = scoreUpdate.score;
297
+ scoreComponents = scoreUpdate.scoreComponents;
298
+ tasks.frictionlessManager.updateScore(botScore, scoreComponents);
299
+ }
300
+ if (Number(botScore) > botThreshold) {
301
+ req.logger.info(() => ({
302
+ msg: "Bot score is greater than threshold",
303
+ data: {
304
+ botScore,
305
+ botThreshold,
306
+ token
307
+ }
308
+ }));
309
+ return res.json(
310
+ await tasks.frictionlessManager.sendImageCaptcha({
311
+ solvedImagesCount: env.config.captchas.solved.count,
312
+ userSitekeyIpHash,
313
+ reason: FrictionlessReason.BOT_SCORE_ABOVE_THRESHOLD
314
+ })
315
+ );
316
+ }
317
+ return res.json(
318
+ await tasks.frictionlessManager.sendPowCaptcha({
319
+ userSitekeyIpHash
320
+ })
321
+ );
322
+ } catch (err) {
323
+ req.logger.error(() => ({
324
+ err,
325
+ msg: "Error in frictionless captcha challenge"
326
+ }));
327
+ return next(
328
+ new ProsopoApiError("API.BAD_REQUEST", {
329
+ context: { code: 400, error: err },
330
+ i18n: req.i18n,
331
+ logger: req.logger
332
+ })
333
+ );
334
+ }
335
+ };
336
+ export {
337
+ getFrictionlessCaptchaChallenge as default
338
+ };