@prosopo/provider 5.10.0 → 5.10.2

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 (85) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +14 -13
  2. package/.turbo/turbo-build$colon$tsc.log +27 -27
  3. package/.turbo/turbo-build.log +15 -14
  4. package/CHANGELOG.md +126 -0
  5. package/dist/api/captcha/assignDetectorBundle.d.ts.map +1 -1
  6. package/dist/api/captcha/assignDetectorBundle.js +27 -9
  7. package/dist/api/captcha/assignDetectorBundle.js.map +1 -1
  8. package/dist/api/captcha/assignDetectorBundle.unit.test.d.ts +2 -0
  9. package/dist/api/captcha/assignDetectorBundle.unit.test.d.ts.map +1 -0
  10. package/dist/api/captcha/assignDetectorBundle.unit.test.js +153 -0
  11. package/dist/api/captcha/assignDetectorBundle.unit.test.js.map +1 -0
  12. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.d.ts.map +1 -1
  13. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js +6 -2
  14. package/dist/api/captcha/getFrictionlessCaptchaChallenge/handler.js.map +1 -1
  15. package/dist/api/captcha/submitPoWCaptchaSolution.js +51 -11
  16. package/dist/api/captcha/submitPoWCaptchaSolution.js.map +1 -1
  17. package/dist/cjs/api/captcha/assignDetectorBundle.cjs +26 -8
  18. package/dist/cjs/api/captcha/getFrictionlessCaptchaChallenge/handler.cjs +6 -2
  19. package/dist/cjs/api/captcha/submitPoWCaptchaSolution.cjs +51 -11
  20. package/dist/cjs/services/ipComparison.cjs +4 -2
  21. package/dist/cjs/tasks/captchaManager.cjs +34 -3
  22. package/dist/cjs/tasks/detection/assignSecret.cjs +45 -0
  23. package/dist/cjs/tasks/detection/bundlePool.cjs +11 -7
  24. package/dist/cjs/tasks/detection/decodePayload.cjs +1147 -988
  25. package/dist/cjs/tasks/detection/decodePayload.js +1 -2
  26. package/dist/cjs/tasks/detection/getBotScore.cjs +30 -1
  27. package/dist/cjs/tasks/frictionless/frictionlessTasks.cjs +95 -82
  28. package/dist/cjs/util.cjs +4 -9
  29. package/dist/services/ipComparison.d.ts.map +1 -1
  30. package/dist/services/ipComparison.js +4 -2
  31. package/dist/services/ipComparison.js.map +1 -1
  32. package/dist/tasks/captchaManager.d.ts +1 -0
  33. package/dist/tasks/captchaManager.d.ts.map +1 -1
  34. package/dist/tasks/captchaManager.js +34 -3
  35. package/dist/tasks/captchaManager.js.map +1 -1
  36. package/dist/tasks/detection/assignSecret.d.ts +8 -0
  37. package/dist/tasks/detection/assignSecret.d.ts.map +1 -0
  38. package/dist/tasks/detection/assignSecret.js +45 -0
  39. package/dist/tasks/detection/assignSecret.js.map +1 -0
  40. package/dist/tasks/detection/bundlePool.d.ts +2 -1
  41. package/dist/tasks/detection/bundlePool.d.ts.map +1 -1
  42. package/dist/tasks/detection/bundlePool.js +11 -8
  43. package/dist/tasks/detection/bundlePool.js.map +1 -1
  44. package/dist/tasks/detection/bundlePool.test.js +4 -3
  45. package/dist/tasks/detection/bundlePool.test.js.map +1 -1
  46. package/dist/tasks/detection/decodePayload.d.ts +2 -2
  47. package/dist/tasks/detection/decodePayload.d.ts.map +1 -1
  48. package/dist/tasks/detection/decodePayload.js +1 -2
  49. package/dist/tasks/detection/decodePayload.js.map +1 -1
  50. package/dist/tasks/detection/getBotScore.d.ts +11 -0
  51. package/dist/tasks/detection/getBotScore.d.ts.map +1 -1
  52. package/dist/tasks/detection/getBotScore.js +32 -4
  53. package/dist/tasks/detection/getBotScore.js.map +1 -1
  54. package/dist/tasks/frictionless/frictionlessTasks.d.ts +7 -3
  55. package/dist/tasks/frictionless/frictionlessTasks.d.ts.map +1 -1
  56. package/dist/tasks/frictionless/frictionlessTasks.js +95 -82
  57. package/dist/tasks/frictionless/frictionlessTasks.js.map +1 -1
  58. package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js +26 -2
  59. package/dist/tests/integration/escalationPeekBeforeConsume.integration.test.js.map +1 -1
  60. package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js +49 -38
  61. package/dist/tests/unit/api/captcha/submitPoWCaptchaSolution.escalation.unit.test.js.map +1 -1
  62. package/dist/tests/unit/services/ipComparison.unit.test.js +27 -0
  63. package/dist/tests/unit/services/ipComparison.unit.test.js.map +1 -1
  64. package/dist/tests/unit/tasks/captchaManager.unit.test.js +46 -0
  65. package/dist/tests/unit/tasks/captchaManager.unit.test.js.map +1 -1
  66. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js +44 -1
  67. package/dist/tests/unit/tasks/detection/getBotScore.unit.test.js.map +1 -1
  68. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js +4 -0
  69. package/dist/tests/unit/tasks/frictionless/decryptPayload.unit.test.js.map +1 -1
  70. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js +78 -2
  71. package/dist/tests/unit/tasks/frictionless/frictionlessTasks.unit.test.js.map +1 -1
  72. package/dist/tests/unit/util.deepValidateIpAddress.rules.unit.test.d.ts +2 -0
  73. package/dist/tests/unit/util.deepValidateIpAddress.rules.unit.test.d.ts.map +1 -0
  74. package/dist/tests/unit/util.deepValidateIpAddress.rules.unit.test.js +115 -0
  75. package/dist/tests/unit/util.deepValidateIpAddress.rules.unit.test.js.map +1 -0
  76. package/dist/tests/unit/util.evaluateIpValidationRules.unit.test.js +70 -0
  77. package/dist/tests/unit/util.evaluateIpValidationRules.unit.test.js.map +1 -1
  78. package/dist/util.d.ts.map +1 -1
  79. package/dist/util.js +4 -9
  80. package/dist/util.js.map +1 -1
  81. package/package.json +16 -16
  82. package/dist/api/captcha/assignDetectorBundle.test.d.ts +0 -2
  83. package/dist/api/captcha/assignDetectorBundle.test.d.ts.map +0 -1
  84. package/dist/api/captcha/assignDetectorBundle.test.js +0 -71
  85. package/dist/api/captcha/assignDetectorBundle.test.js.map +0 -1
@@ -355,13 +355,44 @@ var CaptchaManager = class CaptchaManager {
355
355
  * the bundleId too so the caller can promote it onto the session.
356
356
  */
357
357
  async resolveBundleByDetectorSession(detectorSessionId) {
358
- if (!detectorSessionId || !this.writeQueue) return void 0;
358
+ if (!detectorSessionId || !this.writeQueue) {
359
+ this.logUnresolvedDetectorBundle("noDetectorSession");
360
+ return;
361
+ }
359
362
  const bundleId = await this.writeQueue.getDetectorBundle(detectorSessionId);
363
+ if (!bundleId) {
364
+ this.logUnresolvedDetectorBundle("noBinding");
365
+ return;
366
+ }
360
367
  const decrypt = this.resolveBundleById(bundleId);
361
- return bundleId && decrypt ? {
368
+ if (!decrypt) {
369
+ this.logUnresolvedDetectorBundle("bundleNotInPool", bundleId);
370
+ return;
371
+ }
372
+ return {
362
373
  ...decrypt,
363
374
  bundleId
364
- } : void 0;
375
+ };
376
+ }
377
+ /**
378
+ * Returning undefined here leaves the frictionless decrypt with no keys to
379
+ * try, which fails closed: the score is forced to 1 and the caller is
380
+ * challenged despite nothing having been measured about it. The three causes
381
+ * need different fixes — a caller that sent no detector session, a binding
382
+ * that was absent or had expired (see `DETECTOR_BUNDLE_TTL_SECONDS`), and a
383
+ * bundle this provider no longer holds — but they are indistinguishable
384
+ * downstream, where all three surface as the same decrypt failure. Logged at
385
+ * info because that is the level aggregated log search runs at, and only on
386
+ * the failure path, so this costs nothing on the hot path.
387
+ */
388
+ logUnresolvedDetectorBundle(cause, bundleId) {
389
+ this.logger?.info(() => ({
390
+ msg: "Detector bundle not resolved",
391
+ data: {
392
+ cause,
393
+ ...bundleId !== void 0 && { bundleId }
394
+ }
395
+ }));
365
396
  }
366
397
  /**
367
398
  * Resolve the detector pool bundle for a session via the `bundleId` promoted
@@ -0,0 +1,45 @@
1
+ let node_crypto = require("node:crypto");
2
+ let node_fs = require("node:fs");
3
+ let node_path = require("node:path");
4
+ //#region src/tasks/detection/assignSecret.ts
5
+ /**
6
+ * Per-provider key for the client → bundle mapping in `assignDetectorBundle`.
7
+ * Generated once and kept on the pool volume so a restart does not change the
8
+ * mapping. Treat it as a credential.
9
+ */
10
+ var SECRET_BYTES = 32;
11
+ /**
12
+ * Dot-prefixed so a pool replace leaves it alone: `loadFromDir` and
13
+ * `persistDetectorBundlePool` only touch `.js`/`.json`.
14
+ */
15
+ var SECRET_FILENAME = ".assign-secret";
16
+ var cached = null;
17
+ function getAssignSecret(poolDir, logger = {}) {
18
+ const path = (0, node_path.join)(poolDir, SECRET_FILENAME);
19
+ if (cached && cached.path === path) return cached.secret;
20
+ try {
21
+ const existing = (0, node_fs.readFileSync)(path);
22
+ if (existing.length === SECRET_BYTES) {
23
+ cached = {
24
+ path,
25
+ secret: existing
26
+ };
27
+ return existing;
28
+ }
29
+ logger.warn?.("assign secret has unexpected length, regenerating", { bytes: existing.length });
30
+ } catch {}
31
+ const secret = (0, node_crypto.randomBytes)(SECRET_BYTES);
32
+ (0, node_fs.mkdirSync)((0, node_path.dirname)(path), { recursive: true });
33
+ const tmp = `${path}.tmp`;
34
+ (0, node_fs.writeFileSync)(tmp, secret, { mode: 384 });
35
+ (0, node_fs.chmodSync)(tmp, 384);
36
+ (0, node_fs.renameSync)(tmp, path);
37
+ logger.info?.("generated a new assign secret", { path });
38
+ cached = {
39
+ path,
40
+ secret
41
+ };
42
+ return secret;
43
+ }
44
+ //#endregion
45
+ exports.getAssignSecret = getAssignSecret;
@@ -1,4 +1,3 @@
1
- let node_crypto = require("node:crypto");
2
1
  let node_fs = require("node:fs");
3
2
  let node_path = require("node:path");
4
3
  //#region src/tasks/detection/bundlePool.ts
@@ -11,7 +10,7 @@ let node_path = require("node:path");
11
10
  * (`{ privateKey, innerConfig }`, kept server-side) produced by the catcher
12
11
  * `bundle:pool` build script.
13
12
  *
14
- * A bundle is assigned per detector session by {@link DetectorBundlePool.pickRandom},
13
+ * A bundle is assigned per detector session by {@link DetectorBundlePool.at},
15
14
  * served by id, and resolved again at decryption time so the provider uses the
16
15
  * single correct keypair + inner config rather than brute-forcing a key pool.
17
16
  * The session→bundle binding lives in Redis (short TTL); this module only holds
@@ -105,13 +104,13 @@ var DetectorBundlePool = class {
105
104
  return this.ids.length;
106
105
  }
107
106
  /**
108
- * Pick a uniformly-random bundle. Throws if the pool is empty — the caller
109
- * must surface this as a hard failure (no bundle ⇒ no valid payload can be
110
- * produced).
107
+ * Select by position. `ids` is sorted by {@link replace}, so an index means
108
+ * the same bundle until the pool contents change. Reduced modulo the size, so
109
+ * callers need not know how many bundles are loaded.
111
110
  */
112
- pickRandom() {
111
+ at(index) {
113
112
  if (this.ids.length === 0) throw new Error("detector bundle pool is empty");
114
- const bundleId = this.ids[(0, node_crypto.randomInt)(this.ids.length)];
113
+ const bundleId = this.ids[index % this.ids.length];
115
114
  const bundle = bundleId !== void 0 ? this.bundles.get(bundleId) : void 0;
116
115
  if (bundleId === void 0 || !bundle) throw new Error("detector bundle pool selection failed");
117
116
  return {
@@ -183,6 +182,10 @@ function persistDetectorBundlePool(bundles, dir = globalPoolDir) {
183
182
  function getDetectorBundlePool() {
184
183
  return globalPool;
185
184
  }
185
+ /** Where the global pool was last loaded from — the host-mounted volume. */
186
+ function getDetectorBundlePoolDir() {
187
+ return globalPoolDir;
188
+ }
186
189
  /**
187
190
  * Hot-swap the global pool's contents (admin "emergency push" channel) and
188
191
  * write it to the pool directory so it survives a restart. Creates the global
@@ -215,6 +218,7 @@ function replaceDetectorBundlePool(bundles, logger = {}) {
215
218
  exports.DEFAULT_DETECTOR_POOL_DIR = DEFAULT_DETECTOR_POOL_DIR;
216
219
  exports.DetectorBundlePool = DetectorBundlePool;
217
220
  exports.getDetectorBundlePool = getDetectorBundlePool;
221
+ exports.getDetectorBundlePoolDir = getDetectorBundlePoolDir;
218
222
  exports.initDetectorBundlePool = initDetectorBundlePool;
219
223
  exports.persistDetectorBundlePool = persistDetectorBundlePool;
220
224
  exports.replaceDetectorBundlePool = replaceDetectorBundlePool;