@prosopo/provider 3.13.0 → 3.13.7
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/.turbo/turbo-build$colon$cjs.log +103 -0
- package/.turbo/turbo-build.log +103 -0
- package/CHANGELOG.md +120 -0
- package/dist/api/captcha/contextAwareValidation.js +20 -0
- package/dist/api/captcha/getFrictionlessCaptchaChallenge.js +83 -55
- package/dist/cjs/api/captcha/contextAwareValidation.cjs +20 -0
- package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge.cjs +82 -54
- package/dist/cjs/tasks/client/clientTasks.cjs +72 -8
- package/dist/cjs/tasks/detection/decodePayload.cjs +731 -385
- package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +9 -4
- package/dist/cjs/tasks/frictionless/frictionlessTasksUtils.cjs +4 -1
- package/dist/tasks/client/clientTasks.js +73 -9
- package/dist/tasks/detection/decodePayload.js +731 -385
- package/dist/tasks/detection/getBotScore.js +2 -2
- package/dist/tasks/frictionless/frictionlessTasks.js +9 -4
- package/dist/tasks/frictionless/frictionlessTasksUtils.js +4 -1
- package/package.json +20 -20
|
@@ -10,6 +10,7 @@ const hashUserAgent = require("../../utils/hashUserAgent.cjs");
|
|
|
10
10
|
const hashUserIp = require("../../utils/hashUserIp.cjs");
|
|
11
11
|
const apiToggleMaintenanceModeEndpoint = require("../admin/apiToggleMaintenanceModeEndpoint.cjs");
|
|
12
12
|
const blacklistRequestInspector = require("../blacklistRequestInspector.cjs");
|
|
13
|
+
const contextAwareValidation = require("./contextAwareValidation.cjs");
|
|
13
14
|
const tasks = require("../../tasks/tasks.cjs");
|
|
14
15
|
const DEFAULT_FRICTIONLESS_THRESHOLD = 0.5;
|
|
15
16
|
const getRoundsFromSimScore = (simScore) => {
|
|
@@ -91,7 +92,8 @@ const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (
|
|
|
91
92
|
userAgent,
|
|
92
93
|
webView,
|
|
93
94
|
iFrame,
|
|
94
|
-
decryptedHeadHash
|
|
95
|
+
decryptedHeadHash,
|
|
96
|
+
decryptionFailed
|
|
95
97
|
} = await tasks$1.frictionlessManager.decryptPayload(token, headHash);
|
|
96
98
|
req.logger.debug(() => ({
|
|
97
99
|
msg: "Decrypted payload",
|
|
@@ -161,29 +163,6 @@ const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (
|
|
|
161
163
|
dapp,
|
|
162
164
|
userScope
|
|
163
165
|
))[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
166
|
if (userAccessPolicy) {
|
|
188
167
|
const scoreUpdate = tasks$1.frictionlessManager.scoreIncreaseAccessPolicy(
|
|
189
168
|
userAccessPolicy,
|
|
@@ -212,37 +191,83 @@ const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (
|
|
|
212
191
|
);
|
|
213
192
|
}
|
|
214
193
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
siteKey: dapp,
|
|
229
|
-
user
|
|
230
|
-
},
|
|
231
|
-
i18n: req.i18n,
|
|
232
|
-
logger: req.logger
|
|
233
|
-
})
|
|
234
|
-
);
|
|
194
|
+
const headersUserAgent = req.headers["user-agent"];
|
|
195
|
+
const hashedHeadersUserAgent = headersUserAgent ? hashUserAgent.hashUserAgent(headersUserAgent) : "";
|
|
196
|
+
const headersProsopoUser = req.headers["prosopo-user"];
|
|
197
|
+
if (hashedHeadersUserAgent !== userAgent || headersProsopoUser !== userId) {
|
|
198
|
+
req.logger.info(() => ({
|
|
199
|
+
msg: "User agent or user id does not match",
|
|
200
|
+
data: {
|
|
201
|
+
headersUserAgent,
|
|
202
|
+
hashedHeadersUserAgent,
|
|
203
|
+
userAgent,
|
|
204
|
+
// This is the hashed user agent from the token
|
|
205
|
+
headersProsopoUser,
|
|
206
|
+
userId
|
|
235
207
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
208
|
+
}));
|
|
209
|
+
return res.json(
|
|
210
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
211
|
+
solvedImagesCount: frictionlessTasksUtils.timestampDecayFunction(
|
|
212
|
+
timestamp,
|
|
213
|
+
decryptionFailed
|
|
214
|
+
),
|
|
215
|
+
userSitekeyIpHash,
|
|
216
|
+
reason: frictionlessTasks.FrictionlessReason.USER_AGENT_MISMATCH
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (clientRecord.settings.contextAware?.enabled) {
|
|
221
|
+
const contexts = clientRecord.settings.contextAware?.contexts || {};
|
|
222
|
+
const hasDefault = contexts[types.ContextType.Default] !== void 0;
|
|
223
|
+
const hasWebview = contexts[types.ContextType.Webview] !== void 0;
|
|
224
|
+
let contextType;
|
|
225
|
+
if (hasDefault && hasWebview) {
|
|
226
|
+
contextType = contextAwareValidation.determineContextType(webView);
|
|
227
|
+
} else if (hasDefault) {
|
|
228
|
+
contextType = types.ContextType.Default;
|
|
229
|
+
} else if (hasWebview) {
|
|
230
|
+
contextType = types.ContextType.Webview;
|
|
231
|
+
} else {
|
|
232
|
+
contextType = void 0;
|
|
233
|
+
}
|
|
234
|
+
if (contextType) {
|
|
235
|
+
const clientEntropy = await tasks$1.frictionlessManager.getClientContextEntropy(
|
|
236
|
+
clientRecord.account,
|
|
237
|
+
contextType
|
|
238
|
+
);
|
|
239
|
+
if (clientEntropy) {
|
|
240
|
+
if (!decryptedHeadHash) {
|
|
241
|
+
tasks$1.logger.info(() => ({
|
|
242
|
+
msg: "No decryptedHeadHash in session for context aware client"
|
|
243
|
+
}));
|
|
244
|
+
return next(
|
|
245
|
+
new common.ProsopoApiError("API.BAD_REQUEST", {
|
|
246
|
+
context: {
|
|
247
|
+
code: 400,
|
|
248
|
+
siteKey: dapp,
|
|
249
|
+
user
|
|
250
|
+
},
|
|
251
|
+
i18n: req.i18n,
|
|
252
|
+
logger: req.logger
|
|
253
|
+
})
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const threshold = contextAwareValidation.getContextThreshold(
|
|
257
|
+
clientRecord.settings,
|
|
258
|
+
contextType
|
|
245
259
|
);
|
|
260
|
+
const sim = util.compareBinaryStrings(decryptedHeadHash, clientEntropy);
|
|
261
|
+
const isValidContext = sim >= threshold;
|
|
262
|
+
if (!isValidContext) {
|
|
263
|
+
return res.json(
|
|
264
|
+
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
265
|
+
solvedImagesCount: getRoundsFromSimScore(sim),
|
|
266
|
+
userSitekeyIpHash,
|
|
267
|
+
reason: frictionlessTasks.FrictionlessReason.CONTEXT_AWARE_VALIDATION_FAILED
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
}
|
|
246
271
|
}
|
|
247
272
|
}
|
|
248
273
|
}
|
|
@@ -278,7 +303,10 @@ const getFrictionlessCaptchaChallenge = (env, userAccessRulesStorage) => async (
|
|
|
278
303
|
tasks$1.frictionlessManager.updateScore(botScore, scoreComponents);
|
|
279
304
|
return res.json(
|
|
280
305
|
await tasks$1.frictionlessManager.sendImageCaptcha({
|
|
281
|
-
solvedImagesCount: frictionlessTasksUtils.timestampDecayFunction(
|
|
306
|
+
solvedImagesCount: frictionlessTasksUtils.timestampDecayFunction(
|
|
307
|
+
timestamp,
|
|
308
|
+
decryptionFailed
|
|
309
|
+
),
|
|
282
310
|
userSitekeyIpHash,
|
|
283
311
|
reason: frictionlessTasks.FrictionlessReason.OLD_TIMESTAMP
|
|
284
312
|
})
|
|
@@ -6,6 +6,7 @@ const database = require("@prosopo/database");
|
|
|
6
6
|
const types = require("@prosopo/types");
|
|
7
7
|
const util = require("@prosopo/util");
|
|
8
8
|
const validateAddress = require("../../api/validateAddress.cjs");
|
|
9
|
+
const SAMPLE_SIZE = 100;
|
|
9
10
|
const isValidPrivateKey = (privateKeyString) => {
|
|
10
11
|
const privateKey = Buffer.from(privateKeyString, "base64").toString("ascii");
|
|
11
12
|
try {
|
|
@@ -163,7 +164,13 @@ class ClientTaskManager {
|
|
|
163
164
|
this.logger
|
|
164
165
|
);
|
|
165
166
|
const tenMinuteWindow = 10 * 60 * 1e3;
|
|
166
|
-
const updatedAtTimestamp =
|
|
167
|
+
const updatedAtTimestamp = (() => {
|
|
168
|
+
const raw = lastTask?.updated;
|
|
169
|
+
if (!raw) return 0;
|
|
170
|
+
const ts = raw instanceof Date ? raw.getTime() : Date.parse(String(raw));
|
|
171
|
+
if (Number.isNaN(ts)) return 0;
|
|
172
|
+
return Math.max(ts - tenMinuteWindow, 0);
|
|
173
|
+
})();
|
|
167
174
|
this.logger.info(() => ({
|
|
168
175
|
msg: `Getting updated client records since ${new Date(updatedAtTimestamp).toDateString()}`
|
|
169
176
|
}));
|
|
@@ -201,14 +208,71 @@ class ClientTaskManager {
|
|
|
201
208
|
* @returns Promise<void>
|
|
202
209
|
*/
|
|
203
210
|
async calculateClientEntropy() {
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
const taskID = await this.providerDB.createScheduledTaskStatus(
|
|
212
|
+
types.ScheduledTaskNames.SetClientEntropy,
|
|
213
|
+
types.ScheduledTaskStatus.Running
|
|
214
|
+
);
|
|
215
|
+
try {
|
|
216
|
+
let clients = await this.providerDB.getAllClientRecords();
|
|
217
|
+
clients = clients.filter((client) => client.tier !== types.Tier.Free);
|
|
218
|
+
this.logger.info(() => ({
|
|
219
|
+
msg: `Calculating entropies for ${clients.length} clients`
|
|
220
|
+
}));
|
|
221
|
+
for (const client of clients) {
|
|
222
|
+
if (client.settings?.contextAware?.enabled) {
|
|
223
|
+
const contextTypes = Object.keys(
|
|
224
|
+
client.settings.contextAware.contexts ?? {}
|
|
225
|
+
);
|
|
226
|
+
for (const contextType of contextTypes) {
|
|
227
|
+
const contextSamples = await this.providerDB.sampleContextEntropy(
|
|
228
|
+
SAMPLE_SIZE,
|
|
229
|
+
client.account,
|
|
230
|
+
contextType
|
|
231
|
+
);
|
|
232
|
+
if (contextSamples.length < SAMPLE_SIZE) {
|
|
233
|
+
this.logger.info(() => ({
|
|
234
|
+
msg: `Skipping ${contextType} entropy calculation for client ${client.account} due to insufficient samples (${contextSamples.length}/${SAMPLE_SIZE})`
|
|
235
|
+
}));
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const contextAvgEntropy = util.majorityAverage(contextSamples);
|
|
239
|
+
this.logger.info(() => ({
|
|
240
|
+
msg: `Calculated ${contextType} entropy for client ${client.account}: ${contextAvgEntropy}`
|
|
241
|
+
}));
|
|
242
|
+
await this.providerDB.setClientContextEntropy(
|
|
243
|
+
client.account,
|
|
244
|
+
contextType,
|
|
245
|
+
contextAvgEntropy
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await this.providerDB.updateScheduledTaskStatus(
|
|
251
|
+
taskID,
|
|
252
|
+
types.ScheduledTaskStatus.Completed,
|
|
253
|
+
{
|
|
254
|
+
data: {
|
|
255
|
+
clientRecords: clients.length
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
} catch (e) {
|
|
260
|
+
const calculateClientEntropiesError = new common.ProsopoApiError(
|
|
261
|
+
"DATABASE.UNKNOWN",
|
|
262
|
+
{
|
|
263
|
+
context: { error: e },
|
|
264
|
+
logger: this.logger
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
this.logger.error(() => ({
|
|
268
|
+
err: calculateClientEntropiesError,
|
|
269
|
+
msg: "Error calculating client entropy"
|
|
270
|
+
}));
|
|
271
|
+
await this.providerDB.updateScheduledTaskStatus(
|
|
272
|
+
taskID,
|
|
273
|
+
types.ScheduledTaskStatus.Failed,
|
|
274
|
+
{ error: String(e) }
|
|
209
275
|
);
|
|
210
|
-
const avgEntropy = util.majorityAverage(sampleEntropies);
|
|
211
|
-
await this.providerDB.setClientEntropy(client.account, avgEntropy);
|
|
212
276
|
}
|
|
213
277
|
}
|
|
214
278
|
async registerSiteKey(siteKey, tier, settings) {
|