@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/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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 {
|
|
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 =
|
|
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:
|
|
42
|
+
policyScopeMatch: FilterScopeMatch.Exact,
|
|
43
43
|
userScope: parsedUserScope,
|
|
44
|
-
userScopeMatch:
|
|
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
|
+
};
|