@prosopo/user-access-policy 3.4.0 → 3.5.19
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 +242 -0
- package/dist/api/accessRulesApiClient.js +38 -0
- package/dist/cjs/api/accessRulesApiClient.cjs +38 -0
- package/dist/cjs/index.cjs +8 -4
- package/dist/cjs/redis/{redisAccessRulesIndex.cjs → redisRulesIndex.cjs} +66 -58
- package/dist/cjs/redis/{redisAccessRules.cjs → redisRulesReader.cjs} +30 -40
- package/dist/cjs/redis/redisRulesStorage.cjs +21 -0
- package/dist/cjs/redis/redisRulesWriter.cjs +73 -0
- package/dist/index.js +7 -3
- package/dist/redis/{redisAccessRulesIndex.js → redisRulesIndex.js} +66 -58
- package/dist/redis/{redisAccessRules.js → redisRulesReader.js} +30 -40
- package/dist/redis/redisRulesStorage.js +21 -0
- package/dist/redis/redisRulesWriter.js +73 -0
- package/package.json +17 -12
- package/dist/cjs/redis/redisIndex.cjs +0 -22
- package/dist/redis/redisIndex.js +0 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,247 @@
|
|
|
1
1
|
# @prosopo/user-access-policy
|
|
2
2
|
|
|
3
|
+
## 3.5.19
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
- @prosopo/api@3.1.24
|
|
7
|
+
- @prosopo/api-route@2.6.28
|
|
8
|
+
- @prosopo/common@3.1.20
|
|
9
|
+
- @prosopo/redis-client@1.0.5
|
|
10
|
+
- @prosopo/types@3.5.3
|
|
11
|
+
- @prosopo/util@3.1.5
|
|
12
|
+
|
|
13
|
+
## 3.5.18
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 5659b24: Release 3.4.4
|
|
17
|
+
- Updated dependencies [f912439]
|
|
18
|
+
- Updated dependencies [5659b24]
|
|
19
|
+
- @prosopo/common@3.1.19
|
|
20
|
+
- @prosopo/redis-client@1.0.4
|
|
21
|
+
- @prosopo/api-route@2.6.27
|
|
22
|
+
- @prosopo/types@3.5.2
|
|
23
|
+
- @prosopo/util@3.1.4
|
|
24
|
+
- @prosopo/api@3.1.23
|
|
25
|
+
|
|
26
|
+
## 3.5.17
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- 50c4120: Release 3.4.3
|
|
30
|
+
- Updated dependencies [52cd544]
|
|
31
|
+
- Updated dependencies [b117ba3]
|
|
32
|
+
- Updated dependencies [50c4120]
|
|
33
|
+
- @prosopo/types@3.5.1
|
|
34
|
+
- @prosopo/redis-client@1.0.3
|
|
35
|
+
- @prosopo/api-route@2.6.26
|
|
36
|
+
- @prosopo/common@3.1.18
|
|
37
|
+
- @prosopo/util@3.1.3
|
|
38
|
+
- @prosopo/api@3.1.22
|
|
39
|
+
|
|
40
|
+
## 3.5.16
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 618703f: Release 3.4.2
|
|
44
|
+
- Updated dependencies [618703f]
|
|
45
|
+
- Updated dependencies [e20ad6b]
|
|
46
|
+
- @prosopo/redis-client@1.0.2
|
|
47
|
+
- @prosopo/api-route@2.6.25
|
|
48
|
+
- @prosopo/common@3.1.17
|
|
49
|
+
- @prosopo/types@3.5.0
|
|
50
|
+
- @prosopo/util@3.1.2
|
|
51
|
+
- @prosopo/api@3.1.21
|
|
52
|
+
|
|
53
|
+
## 3.5.15
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- 11303d9: feat/pluggable-redis
|
|
57
|
+
- 11303d9: Release 3.4.0
|
|
58
|
+
- 18cb28b: Release 3.4.1
|
|
59
|
+
- 11303d9: feat/pluggable-redis
|
|
60
|
+
- Updated dependencies [11303d9]
|
|
61
|
+
- Updated dependencies [11303d9]
|
|
62
|
+
- Updated dependencies [18cb28b]
|
|
63
|
+
- Updated dependencies [11303d9]
|
|
64
|
+
- @prosopo/redis-client@1.0.1
|
|
65
|
+
- @prosopo/api-route@2.6.24
|
|
66
|
+
- @prosopo/common@3.1.16
|
|
67
|
+
- @prosopo/types@3.4.1
|
|
68
|
+
- @prosopo/util@3.1.1
|
|
69
|
+
- @prosopo/api@3.1.20
|
|
70
|
+
|
|
71
|
+
## 3.5.14
|
|
72
|
+
### Patch Changes
|
|
73
|
+
|
|
74
|
+
- f3f7aec: Release 3.4.0
|
|
75
|
+
- Updated dependencies [f3f7aec]
|
|
76
|
+
- Updated dependencies [6768f14]
|
|
77
|
+
- @prosopo/api-route@2.6.23
|
|
78
|
+
- @prosopo/common@3.1.15
|
|
79
|
+
- @prosopo/types@3.4.0
|
|
80
|
+
- @prosopo/util@3.1.0
|
|
81
|
+
- @prosopo/config@3.1.15
|
|
82
|
+
|
|
83
|
+
## 3.5.13
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Release 3.3.1
|
|
87
|
+
- 0824221: Release 3.2.4
|
|
88
|
+
- Updated dependencies [97edf3f]
|
|
89
|
+
- Updated dependencies
|
|
90
|
+
- Updated dependencies [0824221]
|
|
91
|
+
- @prosopo/types@3.3.0
|
|
92
|
+
- @prosopo/api-route@2.6.22
|
|
93
|
+
- @prosopo/common@3.1.14
|
|
94
|
+
- @prosopo/util@3.0.17
|
|
95
|
+
- @prosopo/config@3.1.14
|
|
96
|
+
|
|
97
|
+
## 3.5.12
|
|
98
|
+
### Patch Changes
|
|
99
|
+
|
|
100
|
+
- 008d112: Release 3.3.0
|
|
101
|
+
- Updated dependencies [509be28]
|
|
102
|
+
- Updated dependencies [008d112]
|
|
103
|
+
- @prosopo/types@3.2.1
|
|
104
|
+
- @prosopo/api-route@2.6.21
|
|
105
|
+
- @prosopo/common@3.1.13
|
|
106
|
+
- @prosopo/util@3.0.16
|
|
107
|
+
- @prosopo/config@3.1.13
|
|
108
|
+
|
|
109
|
+
## 3.5.11
|
|
110
|
+
### Patch Changes
|
|
111
|
+
|
|
112
|
+
- 0824221: Release 3.2.4
|
|
113
|
+
- Updated dependencies [cf48565]
|
|
114
|
+
- Updated dependencies [0824221]
|
|
115
|
+
- @prosopo/types@3.2.0
|
|
116
|
+
- @prosopo/api-route@2.6.20
|
|
117
|
+
- @prosopo/common@3.1.12
|
|
118
|
+
- @prosopo/util@3.0.15
|
|
119
|
+
- @prosopo/config@3.1.12
|
|
120
|
+
|
|
121
|
+
## 3.5.10
|
|
122
|
+
### Patch Changes
|
|
123
|
+
|
|
124
|
+
- 1a23649: Release 3.2.3
|
|
125
|
+
- Updated dependencies [0d1a33e]
|
|
126
|
+
- Updated dependencies [0d1a33e]
|
|
127
|
+
- Updated dependencies [1a23649]
|
|
128
|
+
- @prosopo/types@3.1.4
|
|
129
|
+
- @prosopo/api-route@2.6.19
|
|
130
|
+
- @prosopo/common@3.1.11
|
|
131
|
+
- @prosopo/util@3.0.14
|
|
132
|
+
- @prosopo/config@3.1.11
|
|
133
|
+
|
|
134
|
+
## 3.5.9
|
|
135
|
+
### Patch Changes
|
|
136
|
+
|
|
137
|
+
- 657a827: Release 3.2.2
|
|
138
|
+
- Updated dependencies [657a827]
|
|
139
|
+
- @prosopo/api-route@2.6.18
|
|
140
|
+
- @prosopo/common@3.1.10
|
|
141
|
+
- @prosopo/types@3.1.3
|
|
142
|
+
- @prosopo/util@3.0.13
|
|
143
|
+
- @prosopo/config@3.1.10
|
|
144
|
+
|
|
145
|
+
## 3.5.8
|
|
146
|
+
### Patch Changes
|
|
147
|
+
|
|
148
|
+
- 4440947: fix type-only tsc compilation
|
|
149
|
+
- 7bdaca6: Release 3.2.1
|
|
150
|
+
- Updated dependencies [4440947]
|
|
151
|
+
- Updated dependencies [7bdaca6]
|
|
152
|
+
- Updated dependencies [809b984]
|
|
153
|
+
- Updated dependencies [1249ce0]
|
|
154
|
+
- Updated dependencies [809b984]
|
|
155
|
+
- @prosopo/api-route@2.6.17
|
|
156
|
+
- @prosopo/common@3.1.9
|
|
157
|
+
- @prosopo/types@3.1.2
|
|
158
|
+
- @prosopo/util@3.0.12
|
|
159
|
+
- @prosopo/config@3.1.9
|
|
160
|
+
|
|
161
|
+
## 3.5.7
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- 6fe8570: Release 3.2.0
|
|
165
|
+
- Updated dependencies [1f980c4]
|
|
166
|
+
- Updated dependencies [6fe8570]
|
|
167
|
+
- @prosopo/types@3.1.1
|
|
168
|
+
- @prosopo/api-route@2.6.16
|
|
169
|
+
- @prosopo/common@3.1.8
|
|
170
|
+
- @prosopo/util@3.0.11
|
|
171
|
+
- @prosopo/config@3.1.8
|
|
172
|
+
|
|
173
|
+
## 3.5.6
|
|
174
|
+
### Patch Changes
|
|
175
|
+
|
|
176
|
+
- f304be9: Release 3.1.13
|
|
177
|
+
- Updated dependencies [f304be9]
|
|
178
|
+
- Updated dependencies [8bdc7f0]
|
|
179
|
+
- @prosopo/api-route@2.6.15
|
|
180
|
+
- @prosopo/common@3.1.7
|
|
181
|
+
- @prosopo/types@3.1.0
|
|
182
|
+
- @prosopo/util@3.0.10
|
|
183
|
+
- @prosopo/config@3.1.7
|
|
184
|
+
|
|
185
|
+
## 3.5.5
|
|
186
|
+
### Patch Changes
|
|
187
|
+
|
|
188
|
+
- a07db04: Release 3.1.12
|
|
189
|
+
- Updated dependencies [9eed772]
|
|
190
|
+
- Updated dependencies [ebb0168]
|
|
191
|
+
- @prosopo/config@3.1.6
|
|
192
|
+
- @prosopo/util@3.0.9
|
|
193
|
+
- @prosopo/api-route@2.6.14
|
|
194
|
+
- @prosopo/common@3.1.6
|
|
195
|
+
- @prosopo/types@3.0.10
|
|
196
|
+
|
|
197
|
+
## 3.5.4
|
|
198
|
+
### Patch Changes
|
|
199
|
+
|
|
200
|
+
- 553025d: Index
|
|
201
|
+
|
|
202
|
+
## 3.5.3
|
|
203
|
+
### Patch Changes
|
|
204
|
+
|
|
205
|
+
- 6960643: lint detect missing and unneccessary imports
|
|
206
|
+
- Updated dependencies [6960643]
|
|
207
|
+
- @prosopo/api-route@2.6.13
|
|
208
|
+
- @prosopo/common@3.1.5
|
|
209
|
+
- @prosopo/types@3.0.9
|
|
210
|
+
- @prosopo/util@3.0.8
|
|
211
|
+
|
|
212
|
+
## 3.5.2
|
|
213
|
+
### Patch Changes
|
|
214
|
+
|
|
215
|
+
- Updated dependencies [30e7d4d]
|
|
216
|
+
- @prosopo/config@3.1.5
|
|
217
|
+
- @prosopo/api-route@2.6.12
|
|
218
|
+
- @prosopo/common@3.1.4
|
|
219
|
+
- @prosopo/types@3.0.8
|
|
220
|
+
- @prosopo/util@3.0.7
|
|
221
|
+
|
|
222
|
+
## 3.5.1
|
|
223
|
+
### Patch Changes
|
|
224
|
+
|
|
225
|
+
- 1f3a02f: Release 3.1.8
|
|
226
|
+
|
|
227
|
+
## 3.5.0
|
|
228
|
+
### Minor Changes
|
|
229
|
+
|
|
230
|
+
- e0628d9: Make sure rules don't leak between IPs
|
|
231
|
+
|
|
232
|
+
## 3.4.1
|
|
233
|
+
### Patch Changes
|
|
234
|
+
|
|
235
|
+
- a49b538: Extra tests
|
|
236
|
+
- e090e2f: More tests
|
|
237
|
+
- Updated dependencies [44ffda2]
|
|
238
|
+
- Updated dependencies [a49b538]
|
|
239
|
+
- @prosopo/config@3.1.4
|
|
240
|
+
- @prosopo/common@3.1.3
|
|
241
|
+
- @prosopo/api-route@2.6.11
|
|
242
|
+
- @prosopo/types@3.0.7
|
|
243
|
+
- @prosopo/util@3.0.6
|
|
244
|
+
|
|
3
245
|
## 3.4.0
|
|
4
246
|
### Minor Changes
|
|
5
247
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ApiClient } from "@prosopo/api";
|
|
2
|
+
import { accessRuleApiPaths } from "./accessRuleApiRoutes.js";
|
|
3
|
+
class AccessRulesApiClient extends ApiClient {
|
|
4
|
+
insertMany(toInsert, timestamp, signature) {
|
|
5
|
+
return this.post(accessRuleApiPaths.INSERT_MANY, toInsert, {
|
|
6
|
+
headers: {
|
|
7
|
+
"Prosopo-Site-Key": this.account,
|
|
8
|
+
timestamp,
|
|
9
|
+
signature
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
deleteMany(toDelete, timestamp, signature) {
|
|
14
|
+
return this.post(accessRuleApiPaths.DELETE_MANY, toDelete, {
|
|
15
|
+
headers: {
|
|
16
|
+
"Prosopo-Site-Key": this.account,
|
|
17
|
+
timestamp,
|
|
18
|
+
signature
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
deleteAll(timestamp, signature) {
|
|
23
|
+
return this.post(
|
|
24
|
+
accessRuleApiPaths.DELETE_ALL,
|
|
25
|
+
{},
|
|
26
|
+
{
|
|
27
|
+
headers: {
|
|
28
|
+
"Prosopo-Site-Key": this.account,
|
|
29
|
+
timestamp,
|
|
30
|
+
signature
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
AccessRulesApiClient
|
|
38
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const api = require("@prosopo/api");
|
|
4
|
+
const accessRuleApiRoutes = require("./accessRuleApiRoutes.cjs");
|
|
5
|
+
class AccessRulesApiClient extends api.ApiClient {
|
|
6
|
+
insertMany(toInsert, timestamp, signature) {
|
|
7
|
+
return this.post(accessRuleApiRoutes.accessRuleApiPaths.INSERT_MANY, toInsert, {
|
|
8
|
+
headers: {
|
|
9
|
+
"Prosopo-Site-Key": this.account,
|
|
10
|
+
timestamp,
|
|
11
|
+
signature
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
deleteMany(toDelete, timestamp, signature) {
|
|
16
|
+
return this.post(accessRuleApiRoutes.accessRuleApiPaths.DELETE_MANY, toDelete, {
|
|
17
|
+
headers: {
|
|
18
|
+
"Prosopo-Site-Key": this.account,
|
|
19
|
+
timestamp,
|
|
20
|
+
signature
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
deleteAll(timestamp, signature) {
|
|
25
|
+
return this.post(
|
|
26
|
+
accessRuleApiRoutes.accessRuleApiPaths.DELETE_ALL,
|
|
27
|
+
{},
|
|
28
|
+
{
|
|
29
|
+
headers: {
|
|
30
|
+
"Prosopo-Site-Key": this.account,
|
|
31
|
+
timestamp,
|
|
32
|
+
signature
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.AccessRulesApiClient = AccessRulesApiClient;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const accessPolicy = require("./accessPolicy.cjs");
|
|
4
4
|
const accessPolicyResolver = require("./accessPolicyResolver.cjs");
|
|
5
|
+
const accessRules = require("./accessRules.cjs");
|
|
5
6
|
const accessRuleApiRoutes = require("./api/accessRuleApiRoutes.cjs");
|
|
6
7
|
const deleteAllRulesEndpoint = require("./api/deleteAllRulesEndpoint.cjs");
|
|
7
8
|
const deleteRulesEndpoint = require("./api/deleteRulesEndpoint.cjs");
|
|
8
9
|
const insertRulesEndpoint = require("./api/insertRulesEndpoint.cjs");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
10
|
+
const redisRulesStorage = require("./redis/redisRulesStorage.cjs");
|
|
11
|
+
const redisRulesIndex = require("./redis/redisRulesIndex.cjs");
|
|
12
|
+
const accessRulesApiClient = require("./api/accessRulesApiClient.cjs");
|
|
11
13
|
const createApiRuleRoutesProvider = (rulesStorage) => {
|
|
12
14
|
return new accessRuleApiRoutes.AccessRuleApiRoutes(rulesStorage);
|
|
13
15
|
};
|
|
@@ -17,11 +19,13 @@ exports.accessRuleSchemaExtended = accessPolicy.accessRuleSchemaExtended;
|
|
|
17
19
|
exports.policyScopeSchema = accessPolicy.policyScopeSchema;
|
|
18
20
|
exports.userScopeInputSchema = accessPolicy.userScopeInputSchema;
|
|
19
21
|
exports.ScopeMatch = accessPolicyResolver.ScopeMatch;
|
|
22
|
+
exports.accessRuleSchema = accessRules.accessRuleSchema;
|
|
20
23
|
exports.accessRuleApiPaths = accessRuleApiRoutes.accessRuleApiPaths;
|
|
21
24
|
exports.getExpressApiRuleRateLimits = accessRuleApiRoutes.getExpressApiRuleRateLimits;
|
|
22
25
|
exports.deleteAllRulesEndpointSchema = deleteAllRulesEndpoint.deleteAllRulesEndpointSchema;
|
|
23
26
|
exports.deleteRulesEndpointSchema = deleteRulesEndpoint.deleteRulesEndpointSchema;
|
|
24
27
|
exports.insertRulesEndpointSchema = insertRulesEndpoint.insertRulesEndpointSchema;
|
|
25
|
-
exports.createRedisAccessRulesStorage =
|
|
26
|
-
exports.
|
|
28
|
+
exports.createRedisAccessRulesStorage = redisRulesStorage.createRedisAccessRulesStorage;
|
|
29
|
+
exports.redisAccessRulesIndex = redisRulesIndex.redisAccessRulesIndex;
|
|
30
|
+
exports.AccessRulesApiClient = accessRulesApiClient.AccessRulesApiClient;
|
|
27
31
|
exports.createApiRuleRoutesProvider = createApiRuleRoutesProvider;
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
3
|
const search = require("@redis/search");
|
|
4
|
+
const accessPolicy = require("../accessPolicy.cjs");
|
|
5
5
|
const accessPolicyResolver = require("../accessPolicyResolver.cjs");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const DEFAULT_SEARCH_LIMIT = 1e3;
|
|
11
|
-
const accessRulesIndex = {
|
|
12
|
-
name: accessRulesRedisIndexName,
|
|
6
|
+
const redisRulesIndexName = "index:user-access-rules";
|
|
7
|
+
const redisRuleKeyPrefix = "uar:";
|
|
8
|
+
const redisAccessRulesIndex = {
|
|
9
|
+
name: redisRulesIndexName,
|
|
13
10
|
/**
|
|
14
11
|
* Note on the field type decision
|
|
15
12
|
*
|
|
@@ -24,8 +21,8 @@ const accessRulesIndex = {
|
|
|
24
21
|
// necessary to make possible use of the ismissing() function on this field in the search
|
|
25
22
|
INDEXMISSING: true
|
|
26
23
|
},
|
|
27
|
-
numericIpMaskMin: search.SCHEMA_FIELD_TYPE.NUMERIC,
|
|
28
|
-
numericIpMaskMax: search.SCHEMA_FIELD_TYPE.NUMERIC,
|
|
24
|
+
numericIpMaskMin: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
25
|
+
numericIpMaskMax: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
29
26
|
userId: { type: search.SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
|
|
30
27
|
numericIp: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
31
28
|
ja4Hash: { type: search.SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
|
|
@@ -35,43 +32,53 @@ const accessRulesIndex = {
|
|
|
35
32
|
// the satisfy statement is to guarantee that the keys are right
|
|
36
33
|
options: {
|
|
37
34
|
ON: "HASH",
|
|
38
|
-
PREFIX: [
|
|
35
|
+
PREFIX: [redisRuleKeyPrefix]
|
|
39
36
|
}
|
|
40
37
|
};
|
|
41
|
-
const createRedisAccessRulesIndex = async (client, indexName) => {
|
|
42
|
-
if (indexName) {
|
|
43
|
-
accessRulesIndex.name = indexName;
|
|
44
|
-
}
|
|
45
|
-
return redisIndex.createRedisIndex(client, accessRulesIndex);
|
|
46
|
-
};
|
|
47
38
|
const numericIndexFields = [
|
|
48
39
|
"numericIp",
|
|
49
40
|
"numericIpMaskMin",
|
|
50
41
|
"numericIpMaskMax"
|
|
51
42
|
];
|
|
52
43
|
const greedyFieldComparisons = {
|
|
53
|
-
numericIp: (value) =>
|
|
44
|
+
numericIp: (value, scope) => {
|
|
45
|
+
if (value !== void 0) {
|
|
46
|
+
return `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`;
|
|
47
|
+
}
|
|
48
|
+
if (scope.numericIpMaskMin === void 0 && scope.numericIpMaskMax === void 0) {
|
|
49
|
+
return "ismissing(@numericIp) ismissing(@numericIpMaskMin) ismissing(@numericIpMaskMax)";
|
|
50
|
+
}
|
|
51
|
+
return "";
|
|
52
|
+
},
|
|
53
|
+
numericIpMaskMin: (value, scope) => {
|
|
54
|
+
if (scope.numericIp !== void 0) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
return value !== void 0 ? `@numericIpMaskMin:[-inf ${value}]` : "ismissing(@numericIpMaskMin)";
|
|
58
|
+
},
|
|
59
|
+
numericIpMaskMax: (value, scope) => {
|
|
60
|
+
if (scope.numericIp !== void 0) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
return value !== void 0 ? `@numericIpMaskMax:[${value} +inf]` : "ismissing(@numericIpMaskMax)";
|
|
64
|
+
}
|
|
54
65
|
};
|
|
55
|
-
const
|
|
66
|
+
const redisRulesSearchOptions = {
|
|
56
67
|
// #2 is a required option when the 'ismissing()' function is in the query body
|
|
57
68
|
DIALECT: 2
|
|
58
69
|
};
|
|
59
|
-
const
|
|
60
|
-
// #2 is a required option when the 'ismissing()' function is in the query body
|
|
61
|
-
DIALECT: 2,
|
|
62
|
-
LIMIT: {
|
|
63
|
-
from: 0,
|
|
64
|
-
size: DEFAULT_SEARCH_LIMIT
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const getRedisAccessRulesQuery = (filter) => {
|
|
70
|
+
const getRedisRulesQuery = (filter, matchingFieldsOnly) => {
|
|
68
71
|
const { policyScope, userScope } = filter;
|
|
69
72
|
const policyScopeFilter = getPolicyScopeQuery(
|
|
70
73
|
policyScope,
|
|
71
74
|
filter.policyScopeMatch
|
|
72
75
|
);
|
|
73
76
|
if (userScope && Object.keys(userScope).length > 0) {
|
|
74
|
-
const userScopeFilter = getUserScopeQuery(
|
|
77
|
+
const userScopeFilter = getUserScopeQuery(
|
|
78
|
+
userScope,
|
|
79
|
+
filter.userScopeMatch,
|
|
80
|
+
matchingFieldsOnly
|
|
81
|
+
);
|
|
75
82
|
return `${policyScopeFilter} ( ${userScopeFilter} )`;
|
|
76
83
|
}
|
|
77
84
|
return policyScopeFilter ? policyScopeFilter : "*";
|
|
@@ -83,7 +90,7 @@ const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
|
|
|
83
90
|
}
|
|
84
91
|
return accessPolicyResolver.ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
|
|
85
92
|
};
|
|
86
|
-
const getUserScopeQuery = (userScope, scopeMatchType) => {
|
|
93
|
+
const getUserScopeQuery = (userScope, scopeMatchType, matchingFieldsOnly) => {
|
|
87
94
|
let scopeEntries = Object.entries(userScope);
|
|
88
95
|
let scopeJoinType = " ";
|
|
89
96
|
if (scopeMatchType === accessPolicyResolver.ScopeMatch.Greedy) {
|
|
@@ -92,39 +99,40 @@ const getUserScopeQuery = (userScope, scopeMatchType) => {
|
|
|
92
99
|
);
|
|
93
100
|
scopeJoinType = " | ";
|
|
94
101
|
}
|
|
102
|
+
if (matchingFieldsOnly) {
|
|
103
|
+
const scopeMap = new Map(scopeEntries);
|
|
104
|
+
if (scopeMap.has("numericIp") && scopeMap.get("numericIp") === void 0) {
|
|
105
|
+
scopeMap.set("numericIpMaskMin", void 0);
|
|
106
|
+
scopeMap.set("numericIpMaskMax", void 0);
|
|
107
|
+
}
|
|
108
|
+
for (const name of Object.keys(accessPolicy.userScopeSchema.shape)) {
|
|
109
|
+
if (!scopeMap.has(name)) {
|
|
110
|
+
scopeMap.set(name, void 0);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
scopeEntries = [...scopeMap.entries()];
|
|
114
|
+
}
|
|
115
|
+
const scopeObj = Object.fromEntries(scopeEntries);
|
|
95
116
|
return scopeEntries.map(
|
|
96
|
-
([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(
|
|
97
|
-
|
|
117
|
+
([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(
|
|
118
|
+
scopeFieldName,
|
|
119
|
+
scopeFieldValue,
|
|
120
|
+
scopeMatchType,
|
|
121
|
+
scopeObj
|
|
122
|
+
)
|
|
123
|
+
).filter(Boolean).join(scopeJoinType);
|
|
98
124
|
};
|
|
99
|
-
const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
"function" === typeof greedyFieldComparisons[fieldName]
|
|
103
|
-
) {
|
|
104
|
-
return greedyFieldComparisons[fieldName](fieldValue);
|
|
125
|
+
const getUserScopeFieldQuery = (fieldName, fieldValue, matchType, fullScope) => {
|
|
126
|
+
if ("function" === typeof greedyFieldComparisons[fieldName]) {
|
|
127
|
+
return greedyFieldComparisons[fieldName](fieldValue, fullScope);
|
|
105
128
|
}
|
|
106
129
|
if (fieldValue === void 0) {
|
|
107
130
|
return `ismissing(@${fieldName})`;
|
|
108
131
|
}
|
|
109
132
|
return numericIndexFields.includes(fieldName) ? `@${fieldName}:[${fieldValue}]` : `@${fieldName}:{${fieldValue}}`;
|
|
110
133
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"bigint" === typeof value ? value.toString() : value
|
|
117
|
-
)
|
|
118
|
-
)
|
|
119
|
-
).digest("hex");
|
|
120
|
-
const getRedisAccessRuleValue = (rule) => Object.fromEntries(
|
|
121
|
-
Object.entries(rule).map(([key, value]) => [key, String(value)])
|
|
122
|
-
);
|
|
123
|
-
exports.accessRuleRedisKeyPrefix = accessRuleRedisKeyPrefix;
|
|
124
|
-
exports.accessRulesRedisDeleteOptions = accessRulesRedisDeleteOptions;
|
|
125
|
-
exports.accessRulesRedisIndexName = accessRulesRedisIndexName;
|
|
126
|
-
exports.accessRulesRedisSearchOptions = accessRulesRedisSearchOptions;
|
|
127
|
-
exports.createRedisAccessRulesIndex = createRedisAccessRulesIndex;
|
|
128
|
-
exports.getRedisAccessRuleKey = getRedisAccessRuleKey;
|
|
129
|
-
exports.getRedisAccessRuleValue = getRedisAccessRuleValue;
|
|
130
|
-
exports.getRedisAccessRulesQuery = getRedisAccessRulesQuery;
|
|
134
|
+
exports.getRedisRulesQuery = getRedisRulesQuery;
|
|
135
|
+
exports.redisAccessRulesIndex = redisAccessRulesIndex;
|
|
136
|
+
exports.redisRuleKeyPrefix = redisRuleKeyPrefix;
|
|
137
|
+
exports.redisRulesIndexName = redisRulesIndexName;
|
|
138
|
+
exports.redisRulesSearchOptions = redisRulesSearchOptions;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const util = require("node:util");
|
|
4
4
|
const accessRules = require("../accessRules.cjs");
|
|
5
|
-
const
|
|
5
|
+
const redisRulesIndex = require("./redisRulesIndex.cjs");
|
|
6
6
|
function _interopNamespaceDefault(e) {
|
|
7
7
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
8
8
|
if (e) {
|
|
@@ -20,19 +20,19 @@ function _interopNamespaceDefault(e) {
|
|
|
20
20
|
return Object.freeze(n);
|
|
21
21
|
}
|
|
22
22
|
const util__namespace = /* @__PURE__ */ _interopNamespaceDefault(util);
|
|
23
|
-
const
|
|
23
|
+
const createRedisRulesReader = (client, logger) => {
|
|
24
24
|
return {
|
|
25
|
-
findRules: async (filter, skipEmptyUserScopes = true) => {
|
|
26
|
-
const query =
|
|
25
|
+
findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
|
|
26
|
+
const query = redisRulesIndex.getRedisRulesQuery(filter, matchingFieldsOnly);
|
|
27
27
|
if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
|
|
28
28
|
return [];
|
|
29
29
|
}
|
|
30
30
|
let searchReply;
|
|
31
31
|
try {
|
|
32
32
|
searchReply = await client.ft.search(
|
|
33
|
-
|
|
33
|
+
redisRulesIndex.redisRulesIndexName,
|
|
34
34
|
query,
|
|
35
|
-
|
|
35
|
+
redisRulesIndex.redisRulesSearchOptions
|
|
36
36
|
);
|
|
37
37
|
if (searchReply.total > 0) {
|
|
38
38
|
logger.debug(() => ({
|
|
@@ -67,16 +67,16 @@ const createRedisAccessRulesReader = (client, logger) => {
|
|
|
67
67
|
}));
|
|
68
68
|
return [];
|
|
69
69
|
}
|
|
70
|
-
return
|
|
70
|
+
return extractRulesFromSearchReply(searchReply, logger);
|
|
71
71
|
},
|
|
72
|
-
findRuleIds: async (filter) => {
|
|
73
|
-
const query =
|
|
72
|
+
findRuleIds: async (filter, matchingFieldsOnly = false) => {
|
|
73
|
+
const query = redisRulesIndex.getRedisRulesQuery(filter, matchingFieldsOnly);
|
|
74
74
|
let searchReply;
|
|
75
75
|
try {
|
|
76
76
|
searchReply = await client.ft.searchNoContent(
|
|
77
|
-
|
|
77
|
+
redisRulesIndex.redisRulesIndexName,
|
|
78
78
|
query,
|
|
79
|
-
|
|
79
|
+
redisRulesIndex.redisRulesSearchOptions
|
|
80
80
|
);
|
|
81
81
|
} catch (e) {
|
|
82
82
|
logger.error(() => ({
|
|
@@ -100,38 +100,29 @@ const createRedisAccessRulesReader = (client, logger) => {
|
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
102
|
};
|
|
103
|
-
const
|
|
103
|
+
const getDummyRedisRulesReader = (logger) => {
|
|
104
104
|
return {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const expiryDate = new Date(expirationTimestamp);
|
|
111
|
-
if (expiryDate.getUTCFullYear() === 1970) {
|
|
112
|
-
await client.expireAt(ruleKey, expirationTimestamp);
|
|
113
|
-
} else {
|
|
114
|
-
const timestampInSeconds = Math.floor(expirationTimestamp / 1e3);
|
|
115
|
-
await client.expireAt(ruleKey, timestampInSeconds);
|
|
105
|
+
findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
|
|
106
|
+
logger.info(() => ({
|
|
107
|
+
msg: "Dummy findRules() has no effect (redis is not ready)",
|
|
108
|
+
data: {
|
|
109
|
+
filter
|
|
116
110
|
}
|
|
117
|
-
}
|
|
118
|
-
return
|
|
111
|
+
}));
|
|
112
|
+
return [];
|
|
119
113
|
},
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
findRuleIds: async (filter, matchingFieldsOnly = false) => {
|
|
115
|
+
logger.info(() => ({
|
|
116
|
+
msg: "Dummy findRuleIds() has no effect (redis is not ready)",
|
|
117
|
+
data: {
|
|
118
|
+
filter
|
|
119
|
+
}
|
|
120
|
+
}));
|
|
121
|
+
return [];
|
|
125
122
|
}
|
|
126
123
|
};
|
|
127
124
|
};
|
|
128
|
-
const
|
|
129
|
-
return {
|
|
130
|
-
...createRedisAccessRulesReader(client, logger),
|
|
131
|
-
...createRedisAccessRulesWriter(client)
|
|
132
|
-
};
|
|
133
|
-
};
|
|
134
|
-
const extractAccessRulesFromSearchReply = (searchReply, logger) => {
|
|
125
|
+
const extractRulesFromSearchReply = (searchReply, logger) => {
|
|
135
126
|
const accessRules$1 = [];
|
|
136
127
|
searchReply.documents.map(({ id, value: document }) => {
|
|
137
128
|
const parsedDocument = accessRules.accessRuleSchema.safeParse(document);
|
|
@@ -147,6 +138,5 @@ const extractAccessRulesFromSearchReply = (searchReply, logger) => {
|
|
|
147
138
|
});
|
|
148
139
|
return accessRules$1;
|
|
149
140
|
};
|
|
150
|
-
exports.
|
|
151
|
-
exports.
|
|
152
|
-
exports.createRedisAccessRulesWriter = createRedisAccessRulesWriter;
|
|
141
|
+
exports.createRedisRulesReader = createRedisRulesReader;
|
|
142
|
+
exports.getDummyRedisRulesReader = getDummyRedisRulesReader;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const redisRulesReader = require("./redisRulesReader.cjs");
|
|
4
|
+
const redisRulesWriter = require("./redisRulesWriter.cjs");
|
|
5
|
+
const createRedisAccessRulesStorage = (connection, logger) => {
|
|
6
|
+
const storage = {
|
|
7
|
+
...redisRulesReader.getDummyRedisRulesReader(logger),
|
|
8
|
+
...redisRulesWriter.getDummyRedisRulesWriter(logger)
|
|
9
|
+
};
|
|
10
|
+
connection.getClient().then((client) => {
|
|
11
|
+
Object.assign(storage, {
|
|
12
|
+
...redisRulesReader.createRedisRulesReader(client, logger),
|
|
13
|
+
...redisRulesWriter.createRedisRulesWriter(client)
|
|
14
|
+
});
|
|
15
|
+
logger.info(() => ({
|
|
16
|
+
msg: "RedisAccessRules storage got a ready Redis client"
|
|
17
|
+
}));
|
|
18
|
+
});
|
|
19
|
+
return storage;
|
|
20
|
+
};
|
|
21
|
+
exports.createRedisAccessRulesStorage = createRedisAccessRulesStorage;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const redisRulesIndex = require("./redisRulesIndex.cjs");
|
|
5
|
+
const redisRuleContentHashAlgorithm = "md5";
|
|
6
|
+
const createRedisRulesWriter = (client) => {
|
|
7
|
+
return {
|
|
8
|
+
insertRule: async (rule, expirationTimestamp) => {
|
|
9
|
+
const ruleKey = getRedisRuleKey(rule);
|
|
10
|
+
const ruleValue = getRedisRuleValue(rule);
|
|
11
|
+
await client.hSet(ruleKey, ruleValue);
|
|
12
|
+
if (expirationTimestamp) {
|
|
13
|
+
const expiryDate = new Date(expirationTimestamp);
|
|
14
|
+
if (expiryDate.getUTCFullYear() === 1970) {
|
|
15
|
+
await client.expireAt(ruleKey, expirationTimestamp);
|
|
16
|
+
} else {
|
|
17
|
+
const timestampInSeconds = Math.floor(expirationTimestamp / 1e3);
|
|
18
|
+
await client.expireAt(ruleKey, timestampInSeconds);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return ruleKey;
|
|
22
|
+
},
|
|
23
|
+
deleteRules: async (ruleIds) => void await client.del(ruleIds),
|
|
24
|
+
deleteAllRules: async () => {
|
|
25
|
+
const keys = await client.keys(`${redisRulesIndex.redisRuleKeyPrefix}*`);
|
|
26
|
+
if (keys.length === 0) return 0;
|
|
27
|
+
return await client.del(keys);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
const getDummyRedisRulesWriter = (logger) => {
|
|
32
|
+
return {
|
|
33
|
+
insertRule: async (rule, expirationTimestamp) => {
|
|
34
|
+
logger.info(() => ({
|
|
35
|
+
msg: "Dummy insertRule() has no effect (redis is not ready)",
|
|
36
|
+
data: {
|
|
37
|
+
rule
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
return "";
|
|
41
|
+
},
|
|
42
|
+
deleteRules: async (ruleIds) => {
|
|
43
|
+
logger.info(() => ({
|
|
44
|
+
msg: "Dummy deleteRules() has no effect (redis is not ready)",
|
|
45
|
+
data: {
|
|
46
|
+
ruleIds
|
|
47
|
+
}
|
|
48
|
+
}));
|
|
49
|
+
},
|
|
50
|
+
deleteAllRules: async () => {
|
|
51
|
+
logger.info(() => ({
|
|
52
|
+
msg: "Dummy deleteAllRules() has no effect (redis is not ready)"
|
|
53
|
+
}));
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const getRedisRuleKey = (rule) => redisRulesIndex.redisRuleKeyPrefix + crypto.createHash(redisRuleContentHashAlgorithm).update(
|
|
59
|
+
JSON.stringify(
|
|
60
|
+
rule,
|
|
61
|
+
(key, value) => (
|
|
62
|
+
// JSON.stringify can't handle BigInt itself: throws "Do not know how to serialize a BigInt"
|
|
63
|
+
"bigint" === typeof value ? value.toString() : value
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
).digest("hex");
|
|
67
|
+
const getRedisRuleValue = (rule) => Object.fromEntries(
|
|
68
|
+
Object.entries(rule).map(([key, value]) => [key, String(value)])
|
|
69
|
+
);
|
|
70
|
+
exports.createRedisRulesWriter = createRedisRulesWriter;
|
|
71
|
+
exports.getDummyRedisRulesWriter = getDummyRedisRulesWriter;
|
|
72
|
+
exports.getRedisRuleKey = getRedisRuleKey;
|
|
73
|
+
exports.getRedisRuleValue = getRedisRuleValue;
|
package/dist/index.js
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
import { AccessPolicyType, accessPolicySchema, accessRuleSchemaExtended, policyScopeSchema, userScopeInputSchema } from "./accessPolicy.js";
|
|
2
2
|
import { ScopeMatch } from "./accessPolicyResolver.js";
|
|
3
|
+
import { accessRuleSchema } from "./accessRules.js";
|
|
3
4
|
import { AccessRuleApiRoutes } from "./api/accessRuleApiRoutes.js";
|
|
4
5
|
import { accessRuleApiPaths, getExpressApiRuleRateLimits } from "./api/accessRuleApiRoutes.js";
|
|
5
6
|
import { deleteAllRulesEndpointSchema } from "./api/deleteAllRulesEndpoint.js";
|
|
6
7
|
import { deleteRulesEndpointSchema } from "./api/deleteRulesEndpoint.js";
|
|
7
8
|
import { insertRulesEndpointSchema } from "./api/insertRulesEndpoint.js";
|
|
8
|
-
import { createRedisAccessRulesStorage } from "./redis/
|
|
9
|
-
import {
|
|
9
|
+
import { createRedisAccessRulesStorage } from "./redis/redisRulesStorage.js";
|
|
10
|
+
import { redisAccessRulesIndex } from "./redis/redisRulesIndex.js";
|
|
11
|
+
import { AccessRulesApiClient } from "./api/accessRulesApiClient.js";
|
|
10
12
|
const createApiRuleRoutesProvider = (rulesStorage) => {
|
|
11
13
|
return new AccessRuleApiRoutes(rulesStorage);
|
|
12
14
|
};
|
|
13
15
|
export {
|
|
14
16
|
AccessPolicyType,
|
|
17
|
+
AccessRulesApiClient,
|
|
15
18
|
ScopeMatch,
|
|
16
19
|
accessPolicySchema,
|
|
17
20
|
accessRuleApiPaths,
|
|
21
|
+
accessRuleSchema,
|
|
18
22
|
accessRuleSchemaExtended,
|
|
19
23
|
createApiRuleRoutesProvider,
|
|
20
|
-
createRedisAccessRulesIndex,
|
|
21
24
|
createRedisAccessRulesStorage,
|
|
22
25
|
deleteAllRulesEndpointSchema,
|
|
23
26
|
deleteRulesEndpointSchema,
|
|
24
27
|
getExpressApiRuleRateLimits,
|
|
25
28
|
insertRulesEndpointSchema,
|
|
26
29
|
policyScopeSchema,
|
|
30
|
+
redisAccessRulesIndex,
|
|
27
31
|
userScopeInputSchema
|
|
28
32
|
};
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
1
|
import { SCHEMA_FIELD_TYPE } from "@redis/search";
|
|
2
|
+
import { userScopeSchema } from "../accessPolicy.js";
|
|
3
3
|
import { ScopeMatch } from "../accessPolicyResolver.js";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const DEFAULT_SEARCH_LIMIT = 1e3;
|
|
9
|
-
const accessRulesIndex = {
|
|
10
|
-
name: accessRulesRedisIndexName,
|
|
4
|
+
const redisRulesIndexName = "index:user-access-rules";
|
|
5
|
+
const redisRuleKeyPrefix = "uar:";
|
|
6
|
+
const redisAccessRulesIndex = {
|
|
7
|
+
name: redisRulesIndexName,
|
|
11
8
|
/**
|
|
12
9
|
* Note on the field type decision
|
|
13
10
|
*
|
|
@@ -22,8 +19,8 @@ const accessRulesIndex = {
|
|
|
22
19
|
// necessary to make possible use of the ismissing() function on this field in the search
|
|
23
20
|
INDEXMISSING: true
|
|
24
21
|
},
|
|
25
|
-
numericIpMaskMin: SCHEMA_FIELD_TYPE.NUMERIC,
|
|
26
|
-
numericIpMaskMax: SCHEMA_FIELD_TYPE.NUMERIC,
|
|
22
|
+
numericIpMaskMin: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
23
|
+
numericIpMaskMax: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
27
24
|
userId: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
|
|
28
25
|
numericIp: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
|
|
29
26
|
ja4Hash: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
|
|
@@ -33,43 +30,53 @@ const accessRulesIndex = {
|
|
|
33
30
|
// the satisfy statement is to guarantee that the keys are right
|
|
34
31
|
options: {
|
|
35
32
|
ON: "HASH",
|
|
36
|
-
PREFIX: [
|
|
33
|
+
PREFIX: [redisRuleKeyPrefix]
|
|
37
34
|
}
|
|
38
35
|
};
|
|
39
|
-
const createRedisAccessRulesIndex = async (client, indexName) => {
|
|
40
|
-
if (indexName) {
|
|
41
|
-
accessRulesIndex.name = indexName;
|
|
42
|
-
}
|
|
43
|
-
return createRedisIndex(client, accessRulesIndex);
|
|
44
|
-
};
|
|
45
36
|
const numericIndexFields = [
|
|
46
37
|
"numericIp",
|
|
47
38
|
"numericIpMaskMin",
|
|
48
39
|
"numericIpMaskMax"
|
|
49
40
|
];
|
|
50
41
|
const greedyFieldComparisons = {
|
|
51
|
-
numericIp: (value) =>
|
|
42
|
+
numericIp: (value, scope) => {
|
|
43
|
+
if (value !== void 0) {
|
|
44
|
+
return `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`;
|
|
45
|
+
}
|
|
46
|
+
if (scope.numericIpMaskMin === void 0 && scope.numericIpMaskMax === void 0) {
|
|
47
|
+
return "ismissing(@numericIp) ismissing(@numericIpMaskMin) ismissing(@numericIpMaskMax)";
|
|
48
|
+
}
|
|
49
|
+
return "";
|
|
50
|
+
},
|
|
51
|
+
numericIpMaskMin: (value, scope) => {
|
|
52
|
+
if (scope.numericIp !== void 0) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
return value !== void 0 ? `@numericIpMaskMin:[-inf ${value}]` : "ismissing(@numericIpMaskMin)";
|
|
56
|
+
},
|
|
57
|
+
numericIpMaskMax: (value, scope) => {
|
|
58
|
+
if (scope.numericIp !== void 0) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
return value !== void 0 ? `@numericIpMaskMax:[${value} +inf]` : "ismissing(@numericIpMaskMax)";
|
|
62
|
+
}
|
|
52
63
|
};
|
|
53
|
-
const
|
|
64
|
+
const redisRulesSearchOptions = {
|
|
54
65
|
// #2 is a required option when the 'ismissing()' function is in the query body
|
|
55
66
|
DIALECT: 2
|
|
56
67
|
};
|
|
57
|
-
const
|
|
58
|
-
// #2 is a required option when the 'ismissing()' function is in the query body
|
|
59
|
-
DIALECT: 2,
|
|
60
|
-
LIMIT: {
|
|
61
|
-
from: 0,
|
|
62
|
-
size: DEFAULT_SEARCH_LIMIT
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
const getRedisAccessRulesQuery = (filter) => {
|
|
68
|
+
const getRedisRulesQuery = (filter, matchingFieldsOnly) => {
|
|
66
69
|
const { policyScope, userScope } = filter;
|
|
67
70
|
const policyScopeFilter = getPolicyScopeQuery(
|
|
68
71
|
policyScope,
|
|
69
72
|
filter.policyScopeMatch
|
|
70
73
|
);
|
|
71
74
|
if (userScope && Object.keys(userScope).length > 0) {
|
|
72
|
-
const userScopeFilter = getUserScopeQuery(
|
|
75
|
+
const userScopeFilter = getUserScopeQuery(
|
|
76
|
+
userScope,
|
|
77
|
+
filter.userScopeMatch,
|
|
78
|
+
matchingFieldsOnly
|
|
79
|
+
);
|
|
73
80
|
return `${policyScopeFilter} ( ${userScopeFilter} )`;
|
|
74
81
|
}
|
|
75
82
|
return policyScopeFilter ? policyScopeFilter : "*";
|
|
@@ -81,7 +88,7 @@ const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
|
|
|
81
88
|
}
|
|
82
89
|
return ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
|
|
83
90
|
};
|
|
84
|
-
const getUserScopeQuery = (userScope, scopeMatchType) => {
|
|
91
|
+
const getUserScopeQuery = (userScope, scopeMatchType, matchingFieldsOnly) => {
|
|
85
92
|
let scopeEntries = Object.entries(userScope);
|
|
86
93
|
let scopeJoinType = " ";
|
|
87
94
|
if (scopeMatchType === ScopeMatch.Greedy) {
|
|
@@ -90,41 +97,42 @@ const getUserScopeQuery = (userScope, scopeMatchType) => {
|
|
|
90
97
|
);
|
|
91
98
|
scopeJoinType = " | ";
|
|
92
99
|
}
|
|
100
|
+
if (matchingFieldsOnly) {
|
|
101
|
+
const scopeMap = new Map(scopeEntries);
|
|
102
|
+
if (scopeMap.has("numericIp") && scopeMap.get("numericIp") === void 0) {
|
|
103
|
+
scopeMap.set("numericIpMaskMin", void 0);
|
|
104
|
+
scopeMap.set("numericIpMaskMax", void 0);
|
|
105
|
+
}
|
|
106
|
+
for (const name of Object.keys(userScopeSchema.shape)) {
|
|
107
|
+
if (!scopeMap.has(name)) {
|
|
108
|
+
scopeMap.set(name, void 0);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
scopeEntries = [...scopeMap.entries()];
|
|
112
|
+
}
|
|
113
|
+
const scopeObj = Object.fromEntries(scopeEntries);
|
|
93
114
|
return scopeEntries.map(
|
|
94
|
-
([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(
|
|
95
|
-
|
|
115
|
+
([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(
|
|
116
|
+
scopeFieldName,
|
|
117
|
+
scopeFieldValue,
|
|
118
|
+
scopeMatchType,
|
|
119
|
+
scopeObj
|
|
120
|
+
)
|
|
121
|
+
).filter(Boolean).join(scopeJoinType);
|
|
96
122
|
};
|
|
97
|
-
const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
"function" === typeof greedyFieldComparisons[fieldName]
|
|
101
|
-
) {
|
|
102
|
-
return greedyFieldComparisons[fieldName](fieldValue);
|
|
123
|
+
const getUserScopeFieldQuery = (fieldName, fieldValue, matchType, fullScope) => {
|
|
124
|
+
if ("function" === typeof greedyFieldComparisons[fieldName]) {
|
|
125
|
+
return greedyFieldComparisons[fieldName](fieldValue, fullScope);
|
|
103
126
|
}
|
|
104
127
|
if (fieldValue === void 0) {
|
|
105
128
|
return `ismissing(@${fieldName})`;
|
|
106
129
|
}
|
|
107
130
|
return numericIndexFields.includes(fieldName) ? `@${fieldName}:[${fieldValue}]` : `@${fieldName}:{${fieldValue}}`;
|
|
108
131
|
};
|
|
109
|
-
const getRedisAccessRuleKey = (rule) => accessRuleRedisKeyPrefix + crypto.createHash(accessRuleContentHashAlgorithm).update(
|
|
110
|
-
JSON.stringify(
|
|
111
|
-
rule,
|
|
112
|
-
(key, value) => (
|
|
113
|
-
// JSON.stringify can't handle BigInt itself: throws "Do not know how to serialize a BigInt"
|
|
114
|
-
"bigint" === typeof value ? value.toString() : value
|
|
115
|
-
)
|
|
116
|
-
)
|
|
117
|
-
).digest("hex");
|
|
118
|
-
const getRedisAccessRuleValue = (rule) => Object.fromEntries(
|
|
119
|
-
Object.entries(rule).map(([key, value]) => [key, String(value)])
|
|
120
|
-
);
|
|
121
132
|
export {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
getRedisAccessRuleKey,
|
|
128
|
-
getRedisAccessRuleValue,
|
|
129
|
-
getRedisAccessRulesQuery
|
|
133
|
+
getRedisRulesQuery,
|
|
134
|
+
redisAccessRulesIndex,
|
|
135
|
+
redisRuleKeyPrefix,
|
|
136
|
+
redisRulesIndexName,
|
|
137
|
+
redisRulesSearchOptions
|
|
130
138
|
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import * as util from "node:util";
|
|
2
2
|
import { accessRuleSchema } from "../accessRules.js";
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
3
|
+
import { getRedisRulesQuery, redisRulesIndexName, redisRulesSearchOptions } from "./redisRulesIndex.js";
|
|
4
|
+
const createRedisRulesReader = (client, logger) => {
|
|
5
5
|
return {
|
|
6
|
-
findRules: async (filter, skipEmptyUserScopes = true) => {
|
|
7
|
-
const query =
|
|
6
|
+
findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
|
|
7
|
+
const query = getRedisRulesQuery(filter, matchingFieldsOnly);
|
|
8
8
|
if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
|
|
9
9
|
return [];
|
|
10
10
|
}
|
|
11
11
|
let searchReply;
|
|
12
12
|
try {
|
|
13
13
|
searchReply = await client.ft.search(
|
|
14
|
-
|
|
14
|
+
redisRulesIndexName,
|
|
15
15
|
query,
|
|
16
|
-
|
|
16
|
+
redisRulesSearchOptions
|
|
17
17
|
);
|
|
18
18
|
if (searchReply.total > 0) {
|
|
19
19
|
logger.debug(() => ({
|
|
@@ -48,16 +48,16 @@ const createRedisAccessRulesReader = (client, logger) => {
|
|
|
48
48
|
}));
|
|
49
49
|
return [];
|
|
50
50
|
}
|
|
51
|
-
return
|
|
51
|
+
return extractRulesFromSearchReply(searchReply, logger);
|
|
52
52
|
},
|
|
53
|
-
findRuleIds: async (filter) => {
|
|
54
|
-
const query =
|
|
53
|
+
findRuleIds: async (filter, matchingFieldsOnly = false) => {
|
|
54
|
+
const query = getRedisRulesQuery(filter, matchingFieldsOnly);
|
|
55
55
|
let searchReply;
|
|
56
56
|
try {
|
|
57
57
|
searchReply = await client.ft.searchNoContent(
|
|
58
|
-
|
|
58
|
+
redisRulesIndexName,
|
|
59
59
|
query,
|
|
60
|
-
|
|
60
|
+
redisRulesSearchOptions
|
|
61
61
|
);
|
|
62
62
|
} catch (e) {
|
|
63
63
|
logger.error(() => ({
|
|
@@ -81,38 +81,29 @@ const createRedisAccessRulesReader = (client, logger) => {
|
|
|
81
81
|
}
|
|
82
82
|
};
|
|
83
83
|
};
|
|
84
|
-
const
|
|
84
|
+
const getDummyRedisRulesReader = (logger) => {
|
|
85
85
|
return {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const expiryDate = new Date(expirationTimestamp);
|
|
92
|
-
if (expiryDate.getUTCFullYear() === 1970) {
|
|
93
|
-
await client.expireAt(ruleKey, expirationTimestamp);
|
|
94
|
-
} else {
|
|
95
|
-
const timestampInSeconds = Math.floor(expirationTimestamp / 1e3);
|
|
96
|
-
await client.expireAt(ruleKey, timestampInSeconds);
|
|
86
|
+
findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
|
|
87
|
+
logger.info(() => ({
|
|
88
|
+
msg: "Dummy findRules() has no effect (redis is not ready)",
|
|
89
|
+
data: {
|
|
90
|
+
filter
|
|
97
91
|
}
|
|
98
|
-
}
|
|
99
|
-
return
|
|
92
|
+
}));
|
|
93
|
+
return [];
|
|
100
94
|
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
findRuleIds: async (filter, matchingFieldsOnly = false) => {
|
|
96
|
+
logger.info(() => ({
|
|
97
|
+
msg: "Dummy findRuleIds() has no effect (redis is not ready)",
|
|
98
|
+
data: {
|
|
99
|
+
filter
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
return [];
|
|
106
103
|
}
|
|
107
104
|
};
|
|
108
105
|
};
|
|
109
|
-
const
|
|
110
|
-
return {
|
|
111
|
-
...createRedisAccessRulesReader(client, logger),
|
|
112
|
-
...createRedisAccessRulesWriter(client)
|
|
113
|
-
};
|
|
114
|
-
};
|
|
115
|
-
const extractAccessRulesFromSearchReply = (searchReply, logger) => {
|
|
106
|
+
const extractRulesFromSearchReply = (searchReply, logger) => {
|
|
116
107
|
const accessRules = [];
|
|
117
108
|
searchReply.documents.map(({ id, value: document }) => {
|
|
118
109
|
const parsedDocument = accessRuleSchema.safeParse(document);
|
|
@@ -129,7 +120,6 @@ const extractAccessRulesFromSearchReply = (searchReply, logger) => {
|
|
|
129
120
|
return accessRules;
|
|
130
121
|
};
|
|
131
122
|
export {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
createRedisAccessRulesWriter
|
|
123
|
+
createRedisRulesReader,
|
|
124
|
+
getDummyRedisRulesReader
|
|
135
125
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getDummyRedisRulesReader, createRedisRulesReader } from "./redisRulesReader.js";
|
|
2
|
+
import { getDummyRedisRulesWriter, createRedisRulesWriter } from "./redisRulesWriter.js";
|
|
3
|
+
const createRedisAccessRulesStorage = (connection, logger) => {
|
|
4
|
+
const storage = {
|
|
5
|
+
...getDummyRedisRulesReader(logger),
|
|
6
|
+
...getDummyRedisRulesWriter(logger)
|
|
7
|
+
};
|
|
8
|
+
connection.getClient().then((client) => {
|
|
9
|
+
Object.assign(storage, {
|
|
10
|
+
...createRedisRulesReader(client, logger),
|
|
11
|
+
...createRedisRulesWriter(client)
|
|
12
|
+
});
|
|
13
|
+
logger.info(() => ({
|
|
14
|
+
msg: "RedisAccessRules storage got a ready Redis client"
|
|
15
|
+
}));
|
|
16
|
+
});
|
|
17
|
+
return storage;
|
|
18
|
+
};
|
|
19
|
+
export {
|
|
20
|
+
createRedisAccessRulesStorage
|
|
21
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { redisRuleKeyPrefix } from "./redisRulesIndex.js";
|
|
3
|
+
const redisRuleContentHashAlgorithm = "md5";
|
|
4
|
+
const createRedisRulesWriter = (client) => {
|
|
5
|
+
return {
|
|
6
|
+
insertRule: async (rule, expirationTimestamp) => {
|
|
7
|
+
const ruleKey = getRedisRuleKey(rule);
|
|
8
|
+
const ruleValue = getRedisRuleValue(rule);
|
|
9
|
+
await client.hSet(ruleKey, ruleValue);
|
|
10
|
+
if (expirationTimestamp) {
|
|
11
|
+
const expiryDate = new Date(expirationTimestamp);
|
|
12
|
+
if (expiryDate.getUTCFullYear() === 1970) {
|
|
13
|
+
await client.expireAt(ruleKey, expirationTimestamp);
|
|
14
|
+
} else {
|
|
15
|
+
const timestampInSeconds = Math.floor(expirationTimestamp / 1e3);
|
|
16
|
+
await client.expireAt(ruleKey, timestampInSeconds);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return ruleKey;
|
|
20
|
+
},
|
|
21
|
+
deleteRules: async (ruleIds) => void await client.del(ruleIds),
|
|
22
|
+
deleteAllRules: async () => {
|
|
23
|
+
const keys = await client.keys(`${redisRuleKeyPrefix}*`);
|
|
24
|
+
if (keys.length === 0) return 0;
|
|
25
|
+
return await client.del(keys);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
const getDummyRedisRulesWriter = (logger) => {
|
|
30
|
+
return {
|
|
31
|
+
insertRule: async (rule, expirationTimestamp) => {
|
|
32
|
+
logger.info(() => ({
|
|
33
|
+
msg: "Dummy insertRule() has no effect (redis is not ready)",
|
|
34
|
+
data: {
|
|
35
|
+
rule
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
return "";
|
|
39
|
+
},
|
|
40
|
+
deleteRules: async (ruleIds) => {
|
|
41
|
+
logger.info(() => ({
|
|
42
|
+
msg: "Dummy deleteRules() has no effect (redis is not ready)",
|
|
43
|
+
data: {
|
|
44
|
+
ruleIds
|
|
45
|
+
}
|
|
46
|
+
}));
|
|
47
|
+
},
|
|
48
|
+
deleteAllRules: async () => {
|
|
49
|
+
logger.info(() => ({
|
|
50
|
+
msg: "Dummy deleteAllRules() has no effect (redis is not ready)"
|
|
51
|
+
}));
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const getRedisRuleKey = (rule) => redisRuleKeyPrefix + crypto.createHash(redisRuleContentHashAlgorithm).update(
|
|
57
|
+
JSON.stringify(
|
|
58
|
+
rule,
|
|
59
|
+
(key, value) => (
|
|
60
|
+
// JSON.stringify can't handle BigInt itself: throws "Do not know how to serialize a BigInt"
|
|
61
|
+
"bigint" === typeof value ? value.toString() : value
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
).digest("hex");
|
|
65
|
+
const getRedisRuleValue = (rule) => Object.fromEntries(
|
|
66
|
+
Object.entries(rule).map(([key, value]) => [key, String(value)])
|
|
67
|
+
);
|
|
68
|
+
export {
|
|
69
|
+
createRedisRulesWriter,
|
|
70
|
+
getDummyRedisRulesWriter,
|
|
71
|
+
getRedisRuleKey,
|
|
72
|
+
getRedisRuleValue
|
|
73
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/user-access-policy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.19",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -23,23 +23,28 @@
|
|
|
23
23
|
"build": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.esm.config.ts --mode $NODE_ENV",
|
|
24
24
|
"build:tsc": "tsc --build --verbose",
|
|
25
25
|
"build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
|
|
26
|
-
"typecheck": "tsc --
|
|
27
|
-
"test": "NODE_ENV=${NODE_ENV:-test}; npx vitest run --config ./vite.test.config.ts"
|
|
26
|
+
"typecheck": "tsc --project tsconfig.types.json",
|
|
27
|
+
"test:unit": "NODE_ENV=${NODE_ENV:-test}; TEST_TYPE=unit npx vitest run --config ./vite.test.config.ts",
|
|
28
|
+
"test:integration": "NODE_ENV=${NODE_ENV:-test}; NX_PARALLEL=1 TEST_TYPE=integration npx vitest run --config ./vite.test.config.ts",
|
|
29
|
+
"test": "npm run test:unit && npm run test:integration"
|
|
28
30
|
},
|
|
29
31
|
"dependencies": {
|
|
30
|
-
"@prosopo/api
|
|
31
|
-
"@prosopo/
|
|
32
|
-
"@prosopo/
|
|
33
|
-
"@prosopo/
|
|
34
|
-
"
|
|
35
|
-
"
|
|
32
|
+
"@prosopo/api": "3.1.24",
|
|
33
|
+
"@prosopo/api-route": "2.6.28",
|
|
34
|
+
"@prosopo/common": "3.1.20",
|
|
35
|
+
"@prosopo/redis-client": "1.0.5",
|
|
36
|
+
"@prosopo/types": "3.5.3",
|
|
37
|
+
"@prosopo/util": "3.1.5",
|
|
38
|
+
"@redis/search": "5.0.0",
|
|
39
|
+
"dotenv": "16.4.5",
|
|
36
40
|
"ip-address": "10.0.1",
|
|
37
41
|
"redis": "5.0.0",
|
|
38
|
-
"zod": "3.23.8"
|
|
39
|
-
"@prosopo/config": "3.1.3",
|
|
40
|
-
"webpack-dev-server": "5.2.2"
|
|
42
|
+
"zod": "3.23.8"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
45
|
+
"@prosopo/config": "3.1.20",
|
|
46
|
+
"@prosopo/util-crypto": "13.5.22",
|
|
47
|
+
"@types/node": "22.10.2",
|
|
43
48
|
"vite": "6.3.5",
|
|
44
49
|
"vitest": "3.0.9",
|
|
45
50
|
"yargs": "17.7.2"
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const crypto = require("node:crypto");
|
|
4
|
-
const redisIndexHashesRecordKey = "_index_hashes";
|
|
5
|
-
const redisIndexHashAlgorithm = "sha256";
|
|
6
|
-
const createRedisIndex = async (client, index) => {
|
|
7
|
-
const indexHash = createIndexHash(index);
|
|
8
|
-
const existingIndexes = await client.ft._LIST();
|
|
9
|
-
if (existingIndexes.includes(index.name)) {
|
|
10
|
-
const existingIndexHash = await fetchIndexHash(client, index.name);
|
|
11
|
-
if (indexHash === existingIndexHash) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
await client.ft.dropIndex(index.name);
|
|
15
|
-
}
|
|
16
|
-
await client.ft.create(index.name, index.schema, index.options);
|
|
17
|
-
await saveIndexHash(client, index.name, indexHash);
|
|
18
|
-
};
|
|
19
|
-
const createIndexHash = (index) => crypto.createHash(redisIndexHashAlgorithm).update(JSON.stringify(index)).digest("hex");
|
|
20
|
-
const fetchIndexHash = async (client, indexName) => client.hGet(redisIndexHashesRecordKey, indexName);
|
|
21
|
-
const saveIndexHash = async (client, indexName, indexHash) => client.hSet(redisIndexHashesRecordKey, indexName, indexHash);
|
|
22
|
-
exports.createRedisIndex = createRedisIndex;
|
package/dist/redis/redisIndex.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
const redisIndexHashesRecordKey = "_index_hashes";
|
|
3
|
-
const redisIndexHashAlgorithm = "sha256";
|
|
4
|
-
const createRedisIndex = async (client, index) => {
|
|
5
|
-
const indexHash = createIndexHash(index);
|
|
6
|
-
const existingIndexes = await client.ft._LIST();
|
|
7
|
-
if (existingIndexes.includes(index.name)) {
|
|
8
|
-
const existingIndexHash = await fetchIndexHash(client, index.name);
|
|
9
|
-
if (indexHash === existingIndexHash) {
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
await client.ft.dropIndex(index.name);
|
|
13
|
-
}
|
|
14
|
-
await client.ft.create(index.name, index.schema, index.options);
|
|
15
|
-
await saveIndexHash(client, index.name, indexHash);
|
|
16
|
-
};
|
|
17
|
-
const createIndexHash = (index) => crypto.createHash(redisIndexHashAlgorithm).update(JSON.stringify(index)).digest("hex");
|
|
18
|
-
const fetchIndexHash = async (client, indexName) => client.hGet(redisIndexHashesRecordKey, indexName);
|
|
19
|
-
const saveIndexHash = async (client, indexName, indexHash) => client.hSet(redisIndexHashesRecordKey, indexName, indexHash);
|
|
20
|
-
export {
|
|
21
|
-
createRedisIndex
|
|
22
|
-
};
|