@prosopo/user-access-policy 3.12.8 → 3.12.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$cjs.log +45 -41
- package/.turbo/turbo-build$colon$tsc.log +14 -14
- package/.turbo/turbo-build.log +46 -42
- package/CHANGELOG.md +24 -0
- package/dist/.export.js +7 -21
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/api/.export.js +2 -9
- package/dist/api/accessRulesApiClient.js +1 -1
- package/dist/api/delete/.export.js +2 -1
- package/dist/api/delete/deleteAllRules.js +22 -24
- package/dist/api/delete/deleteRuleGroups.js +39 -51
- package/dist/api/delete/deleteRules.js +37 -41
- package/dist/api/read/.export.js +2 -1
- package/dist/api/read/fetchRules.js +34 -42
- package/dist/api/read/findRuleIds.js +35 -48
- package/dist/api/read/getMissingIds.js +36 -40
- package/dist/api/ruleApiRoutes.js +62 -120
- package/dist/api/rulesApiClient.js +51 -87
- package/dist/api/write/.export.js +2 -1
- package/dist/api/write/insertRules.js +84 -102
- package/dist/api/write/rehashRules.js +48 -56
- package/dist/cjs/.export.cjs +21 -23
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/cjs/api/.export.cjs +6 -10
- package/dist/cjs/api/accessRulesApiClient.cjs +0 -0
- package/dist/cjs/api/delete/.export.cjs +0 -1
- package/dist/cjs/api/delete/deleteAllRules.cjs +22 -24
- package/dist/cjs/api/delete/deleteRuleGroups.cjs +39 -51
- package/dist/cjs/api/delete/deleteRules.cjs +41 -42
- package/dist/cjs/api/read/.export.cjs +0 -1
- package/dist/cjs/api/read/fetchRules.cjs +34 -41
- package/dist/cjs/api/read/findRuleIds.cjs +37 -48
- package/dist/cjs/api/read/getMissingIds.cjs +36 -39
- package/dist/cjs/api/ruleApiRoutes.cjs +69 -125
- package/dist/cjs/api/rulesApiClient.cjs +54 -90
- package/dist/cjs/api/write/.export.cjs +0 -1
- package/dist/cjs/api/write/insertRules.cjs +85 -103
- package/dist/cjs/api/write/rehashRules.cjs +48 -56
- package/dist/cjs/classifyOs.cjs +46 -26
- package/dist/cjs/mongoose/.export.cjs +2 -3
- package/dist/cjs/mongoose/mongooseRuleSchema.cjs +97 -36
- package/dist/cjs/redis/.export.cjs +4 -5
- package/dist/cjs/redis/reader/redisAggregate.cjs +59 -84
- package/dist/cjs/redis/reader/redisRulesQuery.cjs +74 -116
- package/dist/cjs/redis/reader/redisRulesReader.cjs +322 -503
- package/dist/cjs/redis/reader/redisRulesSplitQuery.cjs +94 -72
- package/dist/cjs/redis/redisClient.cjs +53 -58
- package/dist/cjs/redis/redisRuleIndex.cjs +93 -49
- package/dist/cjs/redis/redisRulesStorage.cjs +21 -30
- package/dist/cjs/redis/redisRulesWriter.cjs +86 -112
- package/dist/cjs/rule.cjs +8 -7
- package/dist/cjs/ruleInput/.export.cjs +7 -8
- package/dist/cjs/ruleInput/policyInput.cjs +35 -58
- package/dist/cjs/ruleInput/ruleInput.cjs +37 -42
- package/dist/cjs/ruleInput/userScopeInput.cjs +47 -55
- package/dist/cjs/ruleRecord.cjs +15 -22
- package/dist/cjs/rulesStorage.cjs +7 -6
- package/dist/cjs/transformRule.cjs +36 -70
- package/dist/classifyOs.js +48 -29
- package/dist/mongoose/.export.js +2 -3
- package/dist/mongoose/mongooseRuleSchema.js +99 -38
- package/dist/redis/.export.js +3 -5
- package/dist/redis/reader/redisAggregate.js +60 -86
- package/dist/redis/reader/redisRulesQuery.js +75 -118
- package/dist/redis/reader/redisRulesReader.js +312 -487
- package/dist/redis/reader/redisRulesSplitQuery.js +96 -75
- package/dist/redis/redisClient.js +54 -62
- package/dist/redis/redisRuleIndex.js +93 -58
- package/dist/redis/redisRulesStorage.js +21 -30
- package/dist/redis/redisRulesWriter.js +82 -113
- package/dist/rule.js +10 -10
- package/dist/ruleInput/.export.js +3 -7
- package/dist/ruleInput/policyInput.js +35 -61
- package/dist/ruleInput/ruleInput.js +36 -44
- package/dist/ruleInput/userScopeInput.js +44 -55
- package/dist/ruleRecord.js +17 -27
- package/dist/rulesStorage.js +9 -8
- package/dist/tests/redis/redisRulesStorage.integration.test.js +1 -3
- package/dist/tests/redis/redisRulesStorage.integration.test.js.map +1 -1
- package/dist/transformRule.js +34 -73
- package/entries.ts +25 -6
- package/package.json +13 -12
- package/src/tests/redis/redisRulesStorage.integration.test.ts +191 -195
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -113,223 +113,219 @@ describe("redisAccessRulesStorage", () => {
|
|
|
113
113
|
redisClient = await result.getClient();
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
describe(
|
|
117
|
-
|
|
118
|
-
() => {
|
|
119
|
-
let accessRulesWriter: AccessRulesWriter;
|
|
116
|
+
describe("writer", () => {
|
|
117
|
+
let accessRulesWriter: AccessRulesWriter;
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
beforeAll(() => {
|
|
120
|
+
accessRulesWriter = new RedisRulesWriter(redisClient, mockLogger);
|
|
121
|
+
});
|
|
124
122
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
test("inserts rule", async () => {
|
|
124
|
+
const testIndexName = indexName;
|
|
125
|
+
// given
|
|
126
|
+
const accessRule: AccessRule = {
|
|
127
|
+
type: AccessPolicyType.Block,
|
|
128
|
+
clientId: "clientId",
|
|
129
|
+
};
|
|
130
|
+
const accessRuleKey = getAccessRuleRedisKey(accessRule);
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
// when
|
|
133
|
+
await accessRulesWriter.insertRules([
|
|
134
|
+
{
|
|
135
|
+
rule: accessRule,
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
140
138
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
// then
|
|
140
|
+
const insertedAccessRule = await redisClient.hGetAll(accessRuleKey);
|
|
141
|
+
const indexRecordsCount = await getIndexRecordsCount(testIndexName);
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
143
|
+
expect(insertedAccessRule).toEqual(accessRule);
|
|
144
|
+
expect(indexRecordsCount).toEqual(1);
|
|
145
|
+
});
|
|
148
146
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
147
|
+
test("inserts time limited rule", async () => {
|
|
148
|
+
// given
|
|
149
|
+
const accessRule: AccessRule = {
|
|
150
|
+
type: AccessPolicyType.Block,
|
|
151
|
+
clientId: "clientId",
|
|
152
|
+
};
|
|
153
|
+
const accessRuleKey = getAccessRuleRedisKey(accessRule);
|
|
154
|
+
// 1 hour from now.
|
|
155
|
+
const expireIn = 60 * 60; // seconds
|
|
156
|
+
const expirationTimestamp = new Date(
|
|
157
|
+
Date.now() + expireIn * 1000,
|
|
158
|
+
).getTime();
|
|
159
|
+
const expirationTimestampInSeconds = Math.floor(
|
|
160
|
+
expirationTimestamp / 1000,
|
|
161
|
+
);
|
|
164
162
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
163
|
+
// when
|
|
164
|
+
await accessRulesWriter.insertRules([
|
|
165
|
+
{
|
|
166
|
+
rule: accessRule,
|
|
167
|
+
expiresUnixTimestamp: expirationTimestampInSeconds,
|
|
168
|
+
},
|
|
169
|
+
]);
|
|
170
|
+
const ruleKey = getAccessRuleRedisKey(accessRule);
|
|
171
|
+
// then
|
|
172
|
+
const insertedAccessRule = await redisClient.hGetAll(accessRuleKey);
|
|
173
|
+
const insertedExpirationResult = await redisClient.expireAt(
|
|
174
|
+
ruleKey,
|
|
175
|
+
expirationTimestampInSeconds,
|
|
176
|
+
);
|
|
177
|
+
const indexRecordsCount = await getIndexRecordsCount(indexName);
|
|
180
178
|
|
|
181
|
-
|
|
179
|
+
const recordExpirySeconds = await redisClient.ttl(ruleKey);
|
|
182
180
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
181
|
+
expect(insertedAccessRule).toEqual(accessRule);
|
|
182
|
+
expect(insertedExpirationResult).toBe(1);
|
|
183
|
+
expect(recordExpirySeconds).toBeLessThanOrEqual(
|
|
184
|
+
expirationTimestampInSeconds,
|
|
185
|
+
);
|
|
188
186
|
|
|
189
|
-
|
|
190
|
-
|
|
187
|
+
expect(indexRecordsCount).toBe(1);
|
|
188
|
+
});
|
|
191
189
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
await accessRulesWriter.insertRules([
|
|
214
|
-
{
|
|
215
|
-
rule: accessRule,
|
|
216
|
-
expiresUnixTimestamp: expirationTimestampInSeconds,
|
|
217
|
-
},
|
|
218
|
-
]);
|
|
219
|
-
|
|
220
|
-
// Sanity: the rule exists immediately after write.
|
|
221
|
-
const initialRecord = await redisClient.hGetAll(ruleKey);
|
|
222
|
-
expect(initialRecord).toEqual(accessRule);
|
|
223
|
-
|
|
224
|
-
// Wait past the TTL. Add ~1s slack for Redis's per-key
|
|
225
|
-
// expiry sweep — the docs guarantee expired keys stop
|
|
226
|
-
// answering reads but the actual key deletion is lazy.
|
|
227
|
-
await new Promise((resolve) => setTimeout(resolve, 3500));
|
|
228
|
-
|
|
229
|
-
// After expiry: hGetAll returns {} (Redis treats an
|
|
230
|
-
// expired key as non-existent for reads).
|
|
231
|
-
const expiredRecord = await redisClient.hGetAll(ruleKey);
|
|
232
|
-
expect(expiredRecord).toEqual({});
|
|
233
|
-
|
|
234
|
-
// And the RediSearch index no longer counts it.
|
|
235
|
-
const indexRecordsCount = await getIndexRecordsCount(indexName);
|
|
236
|
-
expect(indexRecordsCount).toBe(0);
|
|
237
|
-
}, 10_000);
|
|
238
|
-
|
|
239
|
-
test("deletes rules", async () => {
|
|
240
|
-
// given
|
|
241
|
-
const johnAccessRule: AccessRule = {
|
|
242
|
-
type: AccessPolicyType.Block,
|
|
243
|
-
clientId: getUniqueString(),
|
|
244
|
-
};
|
|
245
|
-
const johnAccessRuleKey = getAccessRuleRedisKey(johnAccessRule);
|
|
190
|
+
// The existing "inserts time limited rule" test above only
|
|
191
|
+
// checks that the TTL is *set* on the Redis key. Neither
|
|
192
|
+
// side actually verifies the rule stops matching once its
|
|
193
|
+
// TTL fires — which is the property operators care about
|
|
194
|
+
// (temporary bans should self-clear). Guard against a
|
|
195
|
+
// regression that persists rules past their expiry (e.g. a
|
|
196
|
+
// future writer that swaps `expireAt` for `persist` by
|
|
197
|
+
// accident, or an index rebuild that resurrects expired
|
|
198
|
+
// records).
|
|
199
|
+
test("expired rule is no longer returned by the reader after its TTL fires", async () => {
|
|
200
|
+
const accessRule: AccessRule = {
|
|
201
|
+
type: AccessPolicyType.Block,
|
|
202
|
+
clientId: `expiring-${getUniqueString()}`,
|
|
203
|
+
};
|
|
204
|
+
const ruleKey = getAccessRuleRedisKey(accessRule);
|
|
205
|
+
// 2 s expiry — long enough that the "before expiry" check
|
|
206
|
+
// races the write cleanly, short enough that the test
|
|
207
|
+
// completes in a reasonable timeout. Redis TTL granularity
|
|
208
|
+
// is 1 s so anything shorter is under-resolution.
|
|
209
|
+
const expirationTimestampInSeconds = Math.floor(Date.now() / 1000) + 2;
|
|
246
210
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
211
|
+
await accessRulesWriter.insertRules([
|
|
212
|
+
{
|
|
213
|
+
rule: accessRule,
|
|
214
|
+
expiresUnixTimestamp: expirationTimestampInSeconds,
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
252
217
|
|
|
253
|
-
|
|
218
|
+
// Sanity: the rule exists immediately after write.
|
|
219
|
+
const initialRecord = await redisClient.hGetAll(ruleKey);
|
|
220
|
+
expect(initialRecord).toEqual(accessRule);
|
|
254
221
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
222
|
+
// Wait past the TTL. Add ~1s slack for Redis's per-key
|
|
223
|
+
// expiry sweep — the docs guarantee expired keys stop
|
|
224
|
+
// answering reads but the actual key deletion is lazy.
|
|
225
|
+
await new Promise((resolve) => setTimeout(resolve, 3500));
|
|
259
226
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
227
|
+
// After expiry: hGetAll returns {} (Redis treats an
|
|
228
|
+
// expired key as non-existent for reads).
|
|
229
|
+
const expiredRecord = await redisClient.hGetAll(ruleKey);
|
|
230
|
+
expect(expiredRecord).toEqual({});
|
|
263
231
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
232
|
+
// And the RediSearch index no longer counts it.
|
|
233
|
+
const indexRecordsCount = await getIndexRecordsCount(indexName);
|
|
234
|
+
expect(indexRecordsCount).toBe(0);
|
|
235
|
+
}, 10_000);
|
|
267
236
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
type: AccessPolicyType.Block,
|
|
276
|
-
clientId: getUniqueString(),
|
|
277
|
-
};
|
|
237
|
+
test("deletes rules", async () => {
|
|
238
|
+
// given
|
|
239
|
+
const johnAccessRule: AccessRule = {
|
|
240
|
+
type: AccessPolicyType.Block,
|
|
241
|
+
clientId: getUniqueString(),
|
|
242
|
+
};
|
|
243
|
+
const johnAccessRuleKey = getAccessRuleRedisKey(johnAccessRule);
|
|
278
244
|
|
|
279
|
-
|
|
245
|
+
const doeAccessRule: AccessRule = {
|
|
246
|
+
type: AccessPolicyType.Block,
|
|
247
|
+
clientId: getUniqueString(),
|
|
248
|
+
};
|
|
249
|
+
const doeAccessRuleKey = getAccessRuleRedisKey(doeAccessRule);
|
|
250
|
+
|
|
251
|
+
await insertRules([johnAccessRule, doeAccessRule]);
|
|
280
252
|
|
|
281
|
-
|
|
282
|
-
|
|
253
|
+
// when
|
|
254
|
+
await accessRulesWriter.deleteRules([
|
|
255
|
+
johnAccessRuleKey.slice(ACCESS_RULE_REDIS_KEY_PREFIX.length),
|
|
256
|
+
]);
|
|
283
257
|
|
|
284
|
-
|
|
285
|
-
|
|
258
|
+
// then
|
|
259
|
+
const presentAccessRule = await redisClient.hGetAll(doeAccessRuleKey);
|
|
260
|
+
const indexRecordsCount = await getIndexRecordsCount(indexName);
|
|
286
261
|
|
|
287
|
-
|
|
288
|
-
|
|
262
|
+
expect(presentAccessRule).toEqual(doeAccessRule);
|
|
263
|
+
expect(indexRecordsCount).toBe(1);
|
|
264
|
+
});
|
|
289
265
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
266
|
+
test("deletes all rules", async () => {
|
|
267
|
+
// given
|
|
268
|
+
const johnAccessRule: AccessRule = {
|
|
269
|
+
type: AccessPolicyType.Block,
|
|
270
|
+
clientId: getUniqueString(),
|
|
271
|
+
};
|
|
272
|
+
const doeAccessRule: AccessRule = {
|
|
273
|
+
type: AccessPolicyType.Block,
|
|
274
|
+
clientId: getUniqueString(),
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
await insertRules([johnAccessRule, doeAccessRule]);
|
|
278
|
+
|
|
279
|
+
// when
|
|
280
|
+
await accessRulesWriter.deleteAllRules();
|
|
281
|
+
|
|
282
|
+
// then
|
|
283
|
+
const indexRecordsCount = await getIndexRecordsCount(indexName);
|
|
284
|
+
|
|
285
|
+
expect(indexRecordsCount).toBe(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("deletes all rules when there are 1 million rules", async () => {
|
|
289
|
+
// given
|
|
290
|
+
const rulesCount = 1_000_000;
|
|
291
|
+
const batchSize = 10_000;
|
|
292
|
+
const numBatches = Math.ceil(rulesCount / batchSize);
|
|
293
|
+
|
|
294
|
+
// Insert rules in batches to avoid memory exhaustion
|
|
295
|
+
// Don't create 1M objects in memory at once!
|
|
296
|
+
for (let i = 0; i < numBatches; i++) {
|
|
297
|
+
const currentBatchSize = Math.min(
|
|
298
|
+
batchSize,
|
|
299
|
+
rulesCount - i * batchSize,
|
|
300
|
+
);
|
|
301
|
+
const batchRules: AccessRule[] = Array.from(
|
|
302
|
+
{ length: currentBatchSize },
|
|
303
|
+
() => ({
|
|
304
|
+
type: AccessPolicyType.Block,
|
|
305
|
+
clientId: getUniqueString(),
|
|
306
|
+
}),
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
await insertRules(batchRules);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// verify that there are 1 million rules in the database
|
|
313
|
+
const beforeDeleteIndexRecordsCount =
|
|
314
|
+
await getIndexRecordsCount(indexName);
|
|
315
|
+
expect(beforeDeleteIndexRecordsCount).toBe(rulesCount);
|
|
316
|
+
|
|
317
|
+
// when
|
|
318
|
+
await accessRulesWriter.deleteAllRules();
|
|
319
|
+
|
|
320
|
+
// then
|
|
321
|
+
const afterDeleteIndexRecordsCount =
|
|
322
|
+
await getIndexRecordsCount(indexName);
|
|
323
|
+
|
|
324
|
+
expect(afterDeleteIndexRecordsCount).toBe(0);
|
|
325
|
+
});
|
|
326
|
+
// The trailing 240_000 is the suite timeout. vitest 4 dropped the
|
|
327
|
+
// options-object overload for describe, so it has to be a plain number.
|
|
328
|
+
}, 240_000);
|
|
333
329
|
|
|
334
330
|
describe("reader", () => {
|
|
335
331
|
let accessRulesReader: AccessRulesReader;
|