@prosopo/user-access-policy 3.9.0 → 3.10.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 (27) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +35 -35
  2. package/.turbo/turbo-build$colon$tsc.log +14 -14
  3. package/.turbo/turbo-build.log +4 -4
  4. package/CHANGELOG.md +21 -0
  5. package/dist/cjs/redis/reader/redisAggregate.cjs +22 -4
  6. package/dist/cjs/redis/reader/redisRulesReader.cjs +133 -35
  7. package/dist/redis/reader/redisAggregate.d.ts +1 -1
  8. package/dist/redis/reader/redisAggregate.d.ts.map +1 -1
  9. package/dist/redis/reader/redisAggregate.js +22 -4
  10. package/dist/redis/reader/redisAggregate.js.map +1 -1
  11. package/dist/redis/reader/redisRulesReader.d.ts +4 -1
  12. package/dist/redis/reader/redisRulesReader.d.ts.map +1 -1
  13. package/dist/redis/reader/redisRulesReader.js +132 -34
  14. package/dist/redis/reader/redisRulesReader.js.map +1 -1
  15. package/dist/tests/redis/redisRulesReaderRank.benchmark.integration.test.d.ts +2 -0
  16. package/dist/tests/redis/redisRulesReaderRank.benchmark.integration.test.d.ts.map +1 -0
  17. package/dist/tests/redis/redisRulesReaderRank.benchmark.integration.test.js +189 -0
  18. package/dist/tests/redis/redisRulesReaderRank.benchmark.integration.test.js.map +1 -0
  19. package/dist/tests/redis/redisRulesStorage.integration.test.js +31 -0
  20. package/dist/tests/redis/redisRulesStorage.integration.test.js.map +1 -1
  21. package/package.json +3 -3
  22. package/src/redis/reader/redisAggregate.ts +27 -1
  23. package/src/redis/reader/redisRulesReader.ts +197 -37
  24. package/src/tests/redis/redisRulesReaderRank.benchmark.integration.test.ts +338 -0
  25. package/src/tests/redis/redisRulesStorage.integration.test.ts +52 -0
  26. package/tsconfig.tsbuildinfo +1 -1
  27. package/vite.test.config.ts +12 -1
@@ -1115,6 +1115,58 @@ describe("redisAccessRulesStorage", () => {
1115
1115
  expect(foundAccessRules).toEqual([]);
1116
1116
  });
1117
1117
 
1118
+ test("returns all matches when the candidate set exceeds the FT.SEARCH page size", async () => {
1119
+ // Regression: under the production traffic profile of a high-volume
1120
+ // bot attack, the greedy `@field:{X} | @field:{Y}` query returns
1121
+ // thousands of candidate rules sharing the dominant ja4 fingerprint.
1122
+ // FT.SEARCH's LIMIT (1000) silently truncated the candidate set,
1123
+ // dropping less-frequent block rules — they never reached the
1124
+ // JS-side specificity sort, so verify let the bot through.
1125
+ // FT.AGGREGATE WITHCURSOR paginates the result and returns all of
1126
+ // them. This test inserts > 1000 rules so the old code would
1127
+ // truncate; the target block rule must still come back.
1128
+ const clientId = getUniqueString();
1129
+ const popularJa4 = `t13d1516h2_${getUniqueString()}`;
1130
+
1131
+ const targetBlockRule: AccessRule = {
1132
+ type: AccessPolicyType.Block,
1133
+ clientId: clientId,
1134
+ ja4Hash: popularJa4,
1135
+ coords: "[[[867,60]]]",
1136
+ };
1137
+
1138
+ // 1500 noise rules sharing the popular ja4 but with distinct coords.
1139
+ // 1500 > REDIS_BATCH_SIZE (1000) — guarantees the targetBlockRule's
1140
+ // FT index position has at least a 1/3 chance of sitting past the
1141
+ // truncation boundary on any given run. With pagination, it must
1142
+ // always come back regardless of indexed order.
1143
+ const noiseRules: AccessRule[] = Array.from({ length: 1500 }, (_, i) => ({
1144
+ type: AccessPolicyType.Restrict,
1145
+ clientId: clientId,
1146
+ ja4Hash: popularJa4,
1147
+ coords: `[[[${i % 1024},${60 + (i % 32)}]]]`,
1148
+ description: `noise-${i}`,
1149
+ }));
1150
+
1151
+ await insertRules([targetBlockRule, ...noiseRules]);
1152
+
1153
+ const indexRecordsCount = await getIndexRecordsCount(indexName);
1154
+ expect(indexRecordsCount).toBe(1501);
1155
+
1156
+ const found = await accessRulesReader.findRules({
1157
+ policyScope: { clientId: clientId },
1158
+ policyScopeMatch: FilterScopeMatch.Greedy,
1159
+ userScope: {
1160
+ ja4Hash: popularJa4,
1161
+ coords: "[[[867,60]]]",
1162
+ },
1163
+ userScopeMatch: FilterScopeMatch.Greedy,
1164
+ });
1165
+
1166
+ expect(found.length).toBe(1501);
1167
+ expect(found).toContainEqual(targetBlockRule);
1168
+ }, 60_000);
1169
+
1118
1170
  test("finds rules with matchingFieldsOnly when only userId is set and all IP fields are missing", async () => {
1119
1171
  // This is the exact scenario from the production error where the query
1120
1172
  // contained duplicate ismissing(@numericIpMaskMin) ismissing(@numericIpMaskMax)