@prosopo/user-access-policy 3.10.1 → 3.10.3

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @prosopo/user-access-policy@3.10.1 build:cjs
2
+ > @prosopo/user-access-policy@3.10.3 build:cjs
3
3
  > NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV
4
4
 
5
5
  ViteCommonJSConfig: .
@@ -67,5 +67,5 @@ rendering chunks...
67
67
  dist/cjs/redis/redisRulesWriter.cjs  3.44 kB
68
68
  dist/cjs/redis/reader/redisRulesQuery.cjs  4.34 kB
69
69
  dist/cjs/api/ruleApiRoutes.cjs  4.55 kB
70
- dist/cjs/redis/reader/redisRulesReader.cjs 10.28 kB
70
+ dist/cjs/redis/reader/redisRulesReader.cjs 11.57 kB
71
71
  ✓ built in 437ms
@@ -1,8 +1,8 @@
1
1
 
2
- > @prosopo/user-access-policy@3.10.1 build:tsc
2
+ > @prosopo/user-access-policy@3.10.3 build:tsc
3
3
  > tsc --build --verbose
4
4
 
5
- 7:45:31 PM - Projects in this build:
5
+ 12:05:45 PM - Projects in this build:
6
6
  * ../../dev/config/tsconfig.json
7
7
  * ../locale/tsconfig.json
8
8
  * ../util/tsconfig.json
@@ -15,27 +15,27 @@
15
15
  * ../api/tsconfig.json
16
16
  * tsconfig.json
17
17
 
18
- 7:45:31 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/webpack/webpack.config.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
18
+ 12:05:45 PM - Project '../../dev/config/tsconfig.json' is up to date because newest input '../../dev/config/src/vite/NodejsPolarsNativeFilePlugin.ts' is older than output '../../dev/config/tsconfig.tsbuildinfo'
19
19
 
20
- 7:45:31 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
20
+ 12:05:45 PM - Project '../locale/tsconfig.json' is up to date because newest input '../locale/src/translationKey.ts' is older than output '../locale/tsconfig.tsbuildinfo'
21
21
 
22
- 7:45:31 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
22
+ 12:05:45 PM - Project '../util/tsconfig.json' is up to date because newest input '../util/src/url.ts' is older than output '../util/tsconfig.tsbuildinfo'
23
23
 
24
- 7:45:31 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/index.ts' is older than output '../logger/tsconfig.tsbuildinfo'
24
+ 12:05:45 PM - Project '../logger/tsconfig.json' is up to date because newest input '../logger/src/logger.ts' is older than output '../logger/tsconfig.tsbuildinfo'
25
25
 
26
- 7:45:31 PM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
26
+ 12:05:45 PM - Project '../util-crypto/tsconfig.json' is up to date because newest input '../util-crypto/src/types.ts' is older than output '../util-crypto/tsconfig.tsbuildinfo'
27
27
 
28
- 7:45:31 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/procaptcha/behavioral.ts' is older than output '../types/tsconfig.tsbuildinfo'
28
+ 12:05:45 PM - Project '../types/tsconfig.json' is up to date because newest input '../types/src/keyring/keyring/types.ts' is older than output '../types/tsconfig.tsbuildinfo'
29
29
 
30
- 7:45:31 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/index.ts' is older than output '../common/tsconfig.tsbuildinfo'
30
+ 12:05:45 PM - Project '../common/tsconfig.json' is up to date because newest input '../common/src/error.ts' is older than output '../common/tsconfig.tsbuildinfo'
31
31
 
32
- 7:45:31 PM - Project '../api-route/tsconfig.json' is up to date because newest input '../api-route/src/apiRoutes.ts' is older than output '../api-route/tsconfig.tsbuildinfo'
32
+ 12:05:45 PM - Project '../api-route/tsconfig.json' is up to date because newest input '../api-route/src/apiRoutes.ts' is older than output '../api-route/tsconfig.tsbuildinfo'
33
33
 
34
- 7:45:31 PM - Project '../redis-client/tsconfig.json' is up to date because newest input '../redis-client/src/index.ts' is older than output '../redis-client/tsconfig.tsbuildinfo'
34
+ 12:05:45 PM - Project '../redis-client/tsconfig.json' is up to date because newest input '../redis-client/src/index.ts' is older than output '../redis-client/tsconfig.tsbuildinfo'
35
35
 
36
- 7:45:31 PM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/index.ts' is older than output '../api/tsconfig.tsbuildinfo'
36
+ 12:05:45 PM - Project '../api/tsconfig.json' is up to date because newest input '../api/src/index.ts' is older than output '../api/tsconfig.tsbuildinfo'
37
37
 
38
- 7:45:31 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
38
+ 12:05:45 PM - Project 'tsconfig.json' is out of date because output file 'tsconfig.tsbuildinfo' does not exist
39
39
 
40
- 7:45:31 PM - Building project '/home/runner/work/captcha/captcha/packages/user-access-policy/tsconfig.json'...
40
+ 12:05:45 PM - Building project '/home/runner/work/captcha/captcha/packages/user-access-policy/tsconfig.json'...
41
41
 
@@ -1,9 +1,9 @@
1
1
 
2
- > @prosopo/user-access-policy@3.10.1 build
2
+ > @prosopo/user-access-policy@3.10.3 build
3
3
  > npm run build:cross-env -- --mode ${NODE_ENV:-development}
4
4
 
5
5
 
6
- > @prosopo/user-access-policy@3.10.1 build:cross-env
6
+ > @prosopo/user-access-policy@3.10.3 build:cross-env
7
7
  > vite build --config vite.esm.config.ts --mode production
8
8
 
9
9
  ViteEsmConfig: .
@@ -39,37 +39,37 @@ transforming...
39
39
  Build end
40
40
  ✓ 33 modules transformed.
41
41
  rendering chunks...
42
- dist/api/delete/.export.js 0.00 kB
43
- dist/api/read/.export.js 0.00 kB
44
- dist/api/write/.export.js 0.00 kB
45
- dist/mongoose/.export.js 0.11 kB
46
- dist/redis/.export.js 0.20 kB
47
- dist/rulesStorage.js 0.23 kB
48
- dist/rule.js 0.24 kB
49
- dist/ruleInput/.export.js 0.27 kB
50
- dist/api/.export.js 0.37 kB
51
- dist/ruleRecord.js 0.61 kB
52
- dist/api/delete/deleteAllRules.js 0.67 kB
53
- dist/.export.js 0.85 kB
54
- dist/api/read/getMissingIds.js 1.01 kB
55
- dist/api/read/fetchRules.js 1.08 kB
56
- dist/redis/redisRulesStorage.js 1.21 kB
57
- dist/mongoose/mongooseRuleSchema.js 1.37 kB
58
- dist/api/delete/deleteRules.js 1.44 kB
59
- dist/api/delete/deleteRuleGroups.js 1.44 kB
60
- dist/ruleInput/policyInput.js 1.50 kB
61
- dist/api/read/findRuleIds.js 1.54 kB
62
- dist/api/write/rehashRules.js 1.54 kB
63
- dist/ruleInput/ruleInput.js 1.72 kB
64
- dist/redis/redisClient.js 1.90 kB
65
- dist/redis/redisRuleIndex.js 2.01 kB
66
- dist/ruleInput/userScopeInput.js 2.22 kB
67
- dist/api/rulesApiClient.js 2.36 kB
68
- dist/redis/reader/redisAggregate.js 2.46 kB
69
- dist/transformRule.js 2.76 kB
70
- dist/api/write/insertRules.js 3.22 kB
71
- dist/redis/redisRulesWriter.js 3.22 kB
72
- dist/redis/reader/redisRulesQuery.js 4.15 kB
73
- dist/api/ruleApiRoutes.js 4.33 kB
74
- dist/redis/reader/redisRulesReader.js 9.40 kB
75
- ✓ built in 467ms
42
+ dist/api/delete/.export.js  0.00 kB
43
+ dist/api/read/.export.js  0.00 kB
44
+ dist/api/write/.export.js  0.00 kB
45
+ dist/mongoose/.export.js  0.11 kB
46
+ dist/redis/.export.js  0.20 kB
47
+ dist/rulesStorage.js  0.23 kB
48
+ dist/rule.js  0.24 kB
49
+ dist/ruleInput/.export.js  0.27 kB
50
+ dist/api/.export.js  0.37 kB
51
+ dist/ruleRecord.js  0.61 kB
52
+ dist/api/delete/deleteAllRules.js  0.67 kB
53
+ dist/.export.js  0.85 kB
54
+ dist/api/read/getMissingIds.js  1.01 kB
55
+ dist/api/read/fetchRules.js  1.08 kB
56
+ dist/redis/redisRulesStorage.js  1.21 kB
57
+ dist/mongoose/mongooseRuleSchema.js  1.37 kB
58
+ dist/api/delete/deleteRules.js  1.44 kB
59
+ dist/api/delete/deleteRuleGroups.js  1.44 kB
60
+ dist/ruleInput/policyInput.js  1.50 kB
61
+ dist/api/read/findRuleIds.js  1.54 kB
62
+ dist/api/write/rehashRules.js  1.54 kB
63
+ dist/ruleInput/ruleInput.js  1.72 kB
64
+ dist/redis/redisClient.js  1.90 kB
65
+ dist/redis/redisRuleIndex.js  2.01 kB
66
+ dist/ruleInput/userScopeInput.js  2.22 kB
67
+ dist/api/rulesApiClient.js  2.36 kB
68
+ dist/redis/reader/redisAggregate.js  2.46 kB
69
+ dist/transformRule.js  2.76 kB
70
+ dist/api/write/insertRules.js  3.22 kB
71
+ dist/redis/redisRulesWriter.js  3.22 kB
72
+ dist/redis/reader/redisRulesQuery.js  4.15 kB
73
+ dist/api/ruleApiRoutes.js  4.33 kB
74
+ dist/redis/reader/redisRulesReader.js 10.68 kB
75
+ ✓ built in 437ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @prosopo/user-access-policy
2
2
 
3
+ ## 3.10.3
4
+ ### Patch Changes
5
+
6
+ - Updated dependencies [3a46191]
7
+ - Updated dependencies [dde23e8]
8
+ - @prosopo/types@4.7.0
9
+ - @prosopo/api@3.5.0
10
+
11
+ ## 3.10.2
12
+ ### Patch Changes
13
+
14
+ - 6962179: fix(user-access-policy): make findRulesRanked robust to typeless candidates and IPv6 numericIp
15
+
16
+ Two production-fatal bugs in `findRulesRanked`, both surfacing as `failed to execute ranked search query` with empty results — i.e. no rules match the request and a Block rule that should fire is silently skipped.
17
+
18
+ 1. **Typeless candidates abort the aggregate.** `SEVERITY_EXPR = '(@type == "block")'` dereferenced `@type` directly, so any candidate document missing `type` triggered `Could not find the value for a parameter name, consider using EXISTS if applicable for type`. Sources of typeless candidates in production: stale RediSearch index entries pointing at a hash whose `type` was `HDEL`'d or whose key was `DEL`'d (visible after mass cleanups), and partial-write races in the writer. Fix: `FILTER exists(@type)` step at the start of the pipeline drops malformed candidates before any APPLY runs.
19
+
20
+ 2. **`FT.AGGREGATE LOAD` returns NUMERIC fields as doubles.** RediSearch stores NUMERIC values in the index as 8-byte doubles, so any `numericIp` / `numericIpMaskMin` / `numericIpMaskMax` past `Number.MAX_SAFE_INTEGER` (every IPv6 rule) round-tripped as scientific notation (`5.59112965392e+37`) and `z.coerce.bigint()` threw `Cannot convert … to a BigInt`. The hash itself preserves the full 38-digit string. Fix: use the aggregate purely as a ranker (it returns top-N keys by spec/severity); read the field values via `HGETALL` over those keys, same pattern as `findRulesGreedy`. One extra round-trip over ≤20 keys.
21
+
22
+ Adds three regression tests: two simulate the typeless candidate via `HDEL` (single rule + co-resident valid rule), one inserts an IPv6 `numericIp` past `2**53` and asserts the bigint comes back intact. All three fail without the respective fix.
23
+ - Updated dependencies [4626340]
24
+ - @prosopo/types@4.6.1
25
+ - @prosopo/api@3.4.14
26
+
3
27
  ## 3.10.1
4
28
  ### Patch Changes
5
29
 
@@ -109,6 +109,19 @@ class RedisRulesReader {
109
109
  DIALECT: redisRulesQuery.REDIS_QUERY_DIALECT,
110
110
  LOAD: [...RULE_LOAD_FIELDS],
111
111
  STEPS: [
112
+ // Drop candidates whose hash is missing `type` before
113
+ // any APPLY runs. SEVERITY_EXPR dereferences `@type`
114
+ // directly, and RediSearch throws
115
+ // "Could not find the value for a parameter name,
116
+ // consider using EXISTS if applicable for type"
117
+ // when the LOAD pulls an undefined @type, aborting
118
+ // the whole aggregate. The undefined @type can come
119
+ // from a partial-write race in the writer or a
120
+ // stale RediSearch index entry pointing at a hash
121
+ // whose `type` field has been removed; in both
122
+ // cases the doc is malformed and not a valid
123
+ // AccessRule, so dropping it is correct.
124
+ { type: "FILTER", expression: "exists(@type)" },
112
125
  { type: "APPLY", expression: SPECIFICITY_EXPR, AS: "_spec" },
113
126
  { type: "APPLY", expression: SEVERITY_EXPR, AS: "_sev" },
114
127
  { type: "APPLY", expression: RANK_EXPR, AS: "_rank" },
@@ -145,7 +158,25 @@ class RedisRulesReader {
145
158
  )
146
159
  }
147
160
  }));
148
- return redisClient.parseRedisRecords(reply.results, ruleInput.accessRuleInput, this.logger);
161
+ const ruleKeys = [];
162
+ for (const result of reply.results) {
163
+ const key = result.__key;
164
+ if (typeof key === "string") {
165
+ ruleKeys.push(key);
166
+ }
167
+ }
168
+ if (ruleKeys.length === 0) {
169
+ return [];
170
+ }
171
+ const { records } = await redisClient.fetchRedisHashRecords(
172
+ this.client,
173
+ ruleKeys,
174
+ this.logger
175
+ );
176
+ const nonEmptyRecords = records.filter(
177
+ (record) => Object.keys(record).length > 0
178
+ );
179
+ return redisClient.parseRedisRecords(nonEmptyRecords, ruleInput.accessRuleInput, this.logger);
149
180
  } catch (e) {
150
181
  this.logger.error(() => ({
151
182
  err: e,
@@ -1 +1 @@
1
- {"version":3,"file":"redisRulesReader.d.ts","sourceRoot":"","sources":["../../../src/redis/reader/redisRulesReader.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAe7C,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,KAAK,EACX,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,MAAM,yBAAyB,CAAC;AAoBjC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAoEzC,qBAAa,gBAAiB,YAAW,iBAAiB;IAExD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM;IAG1B,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAcvD,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAazD,SAAS,CACd,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,EAC1B,mBAAmB,UAAO,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC;YAuBV,eAAe;YA2Ef,eAAe;IAuDvB,WAAW,CAChB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC;IAgDd,eAAe,CACpB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;cAYA,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B5E,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;CAGlD;AAED,qBAAa,qBAAsB,YAAW,iBAAiB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAErC,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWvD,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAWzD,SAAS,CACd,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,EAC1B,mBAAmB,UAAO,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC;IAWlB,WAAW,CAChB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC;IAWd,eAAe,CACpB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;CAKhB"}
1
+ {"version":3,"file":"redisRulesReader.d.ts","sourceRoot":"","sources":["../../../src/redis/reader/redisRulesReader.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAe7C,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,KAAK,EACX,eAAe,EACf,iBAAiB,EACjB,iBAAiB,EACjB,MAAM,yBAAyB,CAAC;AAoBjC,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAoEzC,qBAAa,gBAAiB,YAAW,iBAAiB;IAExD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,eAAe,EACvB,MAAM,EAAE,MAAM;IAG1B,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAcvD,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAazD,SAAS,CACd,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,EAC1B,mBAAmB,UAAO,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC;YAuBV,eAAe;YA0Hf,eAAe;IAuDvB,WAAW,CAChB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC;IAgDd,eAAe,CACpB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;cAYA,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8B5E,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;CAGlD;AAED,qBAAa,qBAAsB,YAAW,iBAAiB;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAErC,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAWvD,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAWzD,SAAS,CACd,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,EAC1B,mBAAmB,UAAO,GACxB,OAAO,CAAC,UAAU,EAAE,CAAC;IAWlB,WAAW,CAChB,MAAM,EAAE,iBAAiB,EACzB,kBAAkB,UAAQ,GACxB,OAAO,CAAC,MAAM,EAAE,CAAC;IAWd,eAAe,CACpB,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAChD,OAAO,CAAC,IAAI,CAAC;CAKhB"}
@@ -1,7 +1,7 @@
1
1
  import * as util from "node:util";
2
2
  import { chunkIntoBatches, executeBatchesSequentially } from "@prosopo/common";
3
3
  import { getRulesRedisQuery, REDIS_QUERY_DIALECT } from "./redisRulesQuery.js";
4
- import { REDIS_BATCH_SIZE, getMissingRedisKeys, parseRedisRecords, fetchRedisHashRecords } from "../redisClient.js";
4
+ import { REDIS_BATCH_SIZE, getMissingRedisKeys, fetchRedisHashRecords, parseRedisRecords } from "../redisClient.js";
5
5
  import { ACCESS_RULE_REDIS_KEY_PREFIX, ACCESS_RULES_REDIS_INDEX_NAME } from "../redisRuleIndex.js";
6
6
  import { AccessPolicyType } from "../../rule.js";
7
7
  import { accessRuleInput } from "../../ruleInput/ruleInput.js";
@@ -90,6 +90,19 @@ class RedisRulesReader {
90
90
  DIALECT: REDIS_QUERY_DIALECT,
91
91
  LOAD: [...RULE_LOAD_FIELDS],
92
92
  STEPS: [
93
+ // Drop candidates whose hash is missing `type` before
94
+ // any APPLY runs. SEVERITY_EXPR dereferences `@type`
95
+ // directly, and RediSearch throws
96
+ // "Could not find the value for a parameter name,
97
+ // consider using EXISTS if applicable for type"
98
+ // when the LOAD pulls an undefined @type, aborting
99
+ // the whole aggregate. The undefined @type can come
100
+ // from a partial-write race in the writer or a
101
+ // stale RediSearch index entry pointing at a hash
102
+ // whose `type` field has been removed; in both
103
+ // cases the doc is malformed and not a valid
104
+ // AccessRule, so dropping it is correct.
105
+ { type: "FILTER", expression: "exists(@type)" },
93
106
  { type: "APPLY", expression: SPECIFICITY_EXPR, AS: "_spec" },
94
107
  { type: "APPLY", expression: SEVERITY_EXPR, AS: "_sev" },
95
108
  { type: "APPLY", expression: RANK_EXPR, AS: "_rank" },
@@ -126,7 +139,25 @@ class RedisRulesReader {
126
139
  )
127
140
  }
128
141
  }));
129
- return parseRedisRecords(reply.results, accessRuleInput, this.logger);
142
+ const ruleKeys = [];
143
+ for (const result of reply.results) {
144
+ const key = result.__key;
145
+ if (typeof key === "string") {
146
+ ruleKeys.push(key);
147
+ }
148
+ }
149
+ if (ruleKeys.length === 0) {
150
+ return [];
151
+ }
152
+ const { records } = await fetchRedisHashRecords(
153
+ this.client,
154
+ ruleKeys,
155
+ this.logger
156
+ );
157
+ const nonEmptyRecords = records.filter(
158
+ (record) => Object.keys(record).length > 0
159
+ );
160
+ return parseRedisRecords(nonEmptyRecords, accessRuleInput, this.logger);
130
161
  } catch (e) {
131
162
  this.logger.error(() => ({
132
163
  err: e,
@@ -1 +1 @@
1
- {"version":3,"file":"redisRulesReader.js","sourceRoot":"","sources":["../../../src/redis/reader/redisRulesReader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redisRulesReader.js","sourceRoot":"","sources":["../../../src/redis/reader/redisRulesReader.ts"],"names":[],"mappings":"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"}
@@ -854,6 +854,165 @@ describe("redisAccessRulesStorage", () => {
854
854
  }, true);
855
855
  expect(foundAccessRules).toEqual([accessRule]);
856
856
  });
857
+ test("findRulesRanked does not throw when a matched candidate is missing @type", async () => {
858
+ const clientId = getUniqueString();
859
+ const userId = getUniqueString();
860
+ const accessRule = {
861
+ type: AccessPolicyType.Block,
862
+ clientId: clientId,
863
+ userId: userId,
864
+ };
865
+ await insertRules([accessRule]);
866
+ const ruleKey = getAccessRuleRedisKey(accessRule);
867
+ await redisClient.hDel(ruleKey, "type");
868
+ const foundAccessRules = await accessRulesReader.findRules({
869
+ policyScope: { clientId: clientId },
870
+ policyScopeMatch: FilterScopeMatch.Exact,
871
+ userScope: { userId: userId },
872
+ userScopeMatch: FilterScopeMatch.Exact,
873
+ }, true);
874
+ expect(foundAccessRules).toEqual([]);
875
+ });
876
+ test("findRulesRanked returns the valid rule and skips a typeless ghost candidate", async () => {
877
+ const clientId = getUniqueString();
878
+ const userId = getUniqueString();
879
+ const validRule = {
880
+ type: AccessPolicyType.Block,
881
+ clientId: clientId,
882
+ userId: userId,
883
+ ja4Hash: "t13d1313h2_valid",
884
+ };
885
+ const ghostRule = {
886
+ type: AccessPolicyType.Block,
887
+ clientId: clientId,
888
+ userId: userId,
889
+ ja4Hash: "t13d1313h2_ghost",
890
+ };
891
+ await insertRules([validRule, ghostRule]);
892
+ const ghostKey = getAccessRuleRedisKey(ghostRule);
893
+ await redisClient.hDel(ghostKey, "type");
894
+ const foundAccessRules = await accessRulesReader.findRules({
895
+ policyScope: { clientId: clientId },
896
+ policyScopeMatch: FilterScopeMatch.Exact,
897
+ userScope: { userId: userId },
898
+ userScopeMatch: FilterScopeMatch.Greedy,
899
+ }, true);
900
+ expect(foundAccessRules).toEqual([validRule]);
901
+ });
902
+ test("findRulesRanked preserves bigint precision for IPv6 numericIp values", async () => {
903
+ const clientId = getUniqueString();
904
+ const userId = getUniqueString();
905
+ const ipv6NumericIp = 55878094658432211238406371356040233102n;
906
+ const accessRule = {
907
+ type: AccessPolicyType.Block,
908
+ clientId: clientId,
909
+ userId: userId,
910
+ numericIp: ipv6NumericIp,
911
+ };
912
+ await insertRules([accessRule]);
913
+ const foundAccessRules = await accessRulesReader.findRules({
914
+ policyScope: { clientId: clientId },
915
+ policyScopeMatch: FilterScopeMatch.Exact,
916
+ userScope: { userId: userId, numericIp: ipv6NumericIp },
917
+ userScopeMatch: FilterScopeMatch.Greedy,
918
+ }, true);
919
+ expect(foundAccessRules).toEqual([accessRule]);
920
+ expect(foundAccessRules[0]?.numericIp).toBe(ipv6NumericIp);
921
+ });
922
+ test("findRulesRanked preserves rank order over multiple candidates", async () => {
923
+ const clientId = getUniqueString();
924
+ const userId = getUniqueString();
925
+ const ja4 = `t13d1313h2_${getUniqueString()}`;
926
+ const moreSpecificRule = {
927
+ type: AccessPolicyType.Restrict,
928
+ clientId: clientId,
929
+ userId: userId,
930
+ ja4Hash: ja4,
931
+ countryCode: "GB",
932
+ };
933
+ const lessSpecificRule = {
934
+ type: AccessPolicyType.Restrict,
935
+ clientId: clientId,
936
+ ja4Hash: ja4,
937
+ };
938
+ await insertRules([lessSpecificRule, moreSpecificRule]);
939
+ const foundAccessRules = await accessRulesReader.findRules({
940
+ policyScope: { clientId: clientId },
941
+ policyScopeMatch: FilterScopeMatch.Exact,
942
+ userScope: {
943
+ userId: userId,
944
+ ja4Hash: ja4,
945
+ countryCode: "GB",
946
+ },
947
+ userScopeMatch: FilterScopeMatch.Greedy,
948
+ }, true);
949
+ expect(foundAccessRules).toHaveLength(2);
950
+ expect(foundAccessRules[0]).toEqual(moreSpecificRule);
951
+ expect(foundAccessRules[1]).toEqual(lessSpecificRule);
952
+ });
953
+ test("findRulesRanked ranks Block over Restrict at equal specificity", async () => {
954
+ const clientId = getUniqueString();
955
+ const userId = getUniqueString();
956
+ const blockRule = {
957
+ type: AccessPolicyType.Block,
958
+ clientId: clientId,
959
+ userId: userId,
960
+ };
961
+ const restrictRule = {
962
+ type: AccessPolicyType.Restrict,
963
+ clientId: clientId,
964
+ userId: userId,
965
+ description: "tiebreaker-restrict",
966
+ };
967
+ await insertRules([restrictRule, blockRule]);
968
+ const foundAccessRules = await accessRulesReader.findRules({
969
+ policyScope: { clientId: clientId },
970
+ policyScopeMatch: FilterScopeMatch.Exact,
971
+ userScope: { userId: userId },
972
+ userScopeMatch: FilterScopeMatch.Greedy,
973
+ }, true);
974
+ expect(foundAccessRules).toHaveLength(2);
975
+ expect(foundAccessRules[0]?.type).toBe(AccessPolicyType.Block);
976
+ expect(foundAccessRules[1]?.type).toBe(AccessPolicyType.Restrict);
977
+ });
978
+ test("findRulesRanked: keys DEL'd between aggregate and HGETALL drop out without reordering survivors", async () => {
979
+ const clientId = getUniqueString();
980
+ const userId = getUniqueString();
981
+ const topRule = {
982
+ type: AccessPolicyType.Block,
983
+ clientId: clientId,
984
+ userId: userId,
985
+ ja4Hash: "t13d_top",
986
+ countryCode: "GB",
987
+ };
988
+ const middleRule = {
989
+ type: AccessPolicyType.Restrict,
990
+ clientId: clientId,
991
+ userId: userId,
992
+ ja4Hash: "t13d_mid",
993
+ };
994
+ const bottomRule = {
995
+ type: AccessPolicyType.Restrict,
996
+ clientId: clientId,
997
+ userId: userId,
998
+ };
999
+ await insertRules([topRule, middleRule, bottomRule]);
1000
+ const middleKey = getAccessRuleRedisKey(middleRule);
1001
+ await redisClient.del(middleKey);
1002
+ const foundAccessRules = await accessRulesReader.findRules({
1003
+ policyScope: { clientId: clientId },
1004
+ policyScopeMatch: FilterScopeMatch.Exact,
1005
+ userScope: {
1006
+ userId: userId,
1007
+ ja4Hash: "t13d_top",
1008
+ countryCode: "GB",
1009
+ },
1010
+ userScopeMatch: FilterScopeMatch.Greedy,
1011
+ }, true);
1012
+ expect(foundAccessRules).toHaveLength(2);
1013
+ expect(foundAccessRules[0]).toEqual(topRule);
1014
+ expect(foundAccessRules[1]).toEqual(bottomRule);
1015
+ });
857
1016
  });
858
1017
  afterAll(async () => {
859
1018
  await redisClient.flushAll();