@prosopo/user-access-policy 3.2.1 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/accessPolicy.js +72 -57
  3. package/dist/accessPolicyResolver.js +62 -36
  4. package/dist/accessRules.js +9 -6
  5. package/dist/api/accessRuleApiRoutes.js +73 -50
  6. package/dist/api/deleteAllRulesEndpoint.js +22 -19
  7. package/dist/api/deleteRulesEndpoint.js +30 -27
  8. package/dist/api/insertRulesEndpoint.js +57 -57
  9. package/dist/cjs/accessPolicy.cjs +4 -3
  10. package/dist/cjs/redis/redisAccessRules.cjs +21 -15
  11. package/dist/cjs/redis/redisAccessRulesIndex.cjs +7 -2
  12. package/dist/index.js +27 -14
  13. package/dist/redis/redisAccessRules.js +128 -105
  14. package/dist/redis/redisAccessRulesIndex.js +95 -68
  15. package/dist/redis/redisIndex.js +15 -16
  16. package/dist/util.js +4 -2
  17. package/package.json +15 -10
  18. package/vite.cjs.config.ts +7 -6
  19. package/vite.esm.config.ts +20 -0
  20. package/vite.test.config.ts +16 -21
  21. package/dist/accessPolicy.d.ts +0 -169
  22. package/dist/accessPolicy.d.ts.map +0 -1
  23. package/dist/accessPolicy.js.map +0 -1
  24. package/dist/accessPolicyResolver.d.ts +0 -115
  25. package/dist/accessPolicyResolver.d.ts.map +0 -1
  26. package/dist/accessPolicyResolver.js.map +0 -1
  27. package/dist/accessRules.d.ts +0 -16
  28. package/dist/accessRules.d.ts.map +0 -1
  29. package/dist/accessRules.js.map +0 -1
  30. package/dist/api/accessRuleApiRoutes.d.ts +0 -27
  31. package/dist/api/accessRuleApiRoutes.d.ts.map +0 -1
  32. package/dist/api/accessRuleApiRoutes.js.map +0 -1
  33. package/dist/api/deleteAllRulesEndpoint.d.ts +0 -12
  34. package/dist/api/deleteAllRulesEndpoint.d.ts.map +0 -1
  35. package/dist/api/deleteAllRulesEndpoint.js.map +0 -1
  36. package/dist/api/deleteRulesEndpoint.d.ts +0 -116
  37. package/dist/api/deleteRulesEndpoint.d.ts.map +0 -1
  38. package/dist/api/deleteRulesEndpoint.js.map +0 -1
  39. package/dist/api/insertRulesEndpoint.d.ts +0 -22
  40. package/dist/api/insertRulesEndpoint.d.ts.map +0 -1
  41. package/dist/api/insertRulesEndpoint.js.map +0 -1
  42. package/dist/index.d.ts +0 -15
  43. package/dist/index.d.ts.map +0 -1
  44. package/dist/index.js.map +0 -1
  45. package/dist/redis/redisAccessRules.d.ts +0 -7
  46. package/dist/redis/redisAccessRules.d.ts.map +0 -1
  47. package/dist/redis/redisAccessRules.js.map +0 -1
  48. package/dist/redis/redisAccessRulesIndex.d.ts +0 -12
  49. package/dist/redis/redisAccessRulesIndex.d.ts.map +0 -1
  50. package/dist/redis/redisAccessRulesIndex.js.map +0 -1
  51. package/dist/redis/redisIndex.d.ts +0 -9
  52. package/dist/redis/redisIndex.d.ts.map +0 -1
  53. package/dist/redis/redisIndex.js.map +0 -1
  54. package/dist/tests/accessPolicy.test.d.ts +0 -2
  55. package/dist/tests/accessPolicy.test.d.ts.map +0 -1
  56. package/dist/tests/accessPolicy.test.js +0 -27
  57. package/dist/tests/accessPolicy.test.js.map +0 -1
  58. package/dist/tests/redis/redisAccessRules.test.d.ts +0 -2
  59. package/dist/tests/redis/redisAccessRules.test.d.ts.map +0 -1
  60. package/dist/tests/redis/redisAccessRules.test.js +0 -400
  61. package/dist/tests/redis/redisAccessRules.test.js.map +0 -1
  62. package/dist/tests/redis/redisIndex.test.d.ts +0 -2
  63. package/dist/tests/redis/redisIndex.test.d.ts.map +0 -1
  64. package/dist/tests/redis/redisIndex.test.js +0 -84
  65. package/dist/tests/redis/redisIndex.test.js.map +0 -1
  66. package/dist/tests/redis/testRedisClient.d.ts +0 -3
  67. package/dist/tests/redis/testRedisClient.d.ts.map +0 -1
  68. package/dist/tests/redis/testRedisClient.js +0 -8
  69. package/dist/tests/redis/testRedisClient.js.map +0 -1
  70. package/dist/tests/testLogger.d.ts +0 -4
  71. package/dist/tests/testLogger.d.ts.map +0 -1
  72. package/dist/tests/testLogger.js +0 -22
  73. package/dist/tests/testLogger.js.map +0 -1
  74. package/dist/util.d.ts +0 -2
  75. package/dist/util.d.ts.map +0 -1
  76. package/dist/util.js.map +0 -1
  77. package/vite.config.ts +0 -39
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const types = require("@prosopo/types");
4
+ const util = require("@prosopo/util");
4
5
  const ipAddress = require("ip-address");
5
6
  const zod = require("zod");
6
- const util = require("./util.cjs");
7
+ const util$1 = require("./util.cjs");
7
8
  var AccessPolicyType = /* @__PURE__ */ ((AccessPolicyType2) => {
8
9
  AccessPolicyType2["Block"] = "block";
9
10
  AccessPolicyType2["Restrict"] = "restrict";
@@ -49,7 +50,7 @@ const userScopeInputSchema = userScopeSchema.extend({
49
50
  }).transform((inputUserScope) => {
50
51
  const { ip, ipMask, userAgent, ...userScope } = inputUserScope;
51
52
  if ("string" === typeof ip) {
52
- userScope.numericIp = new ipAddress.Address4(ip).bigInt();
53
+ userScope.numericIp = util.getIPAddress(ip).bigInt();
53
54
  }
54
55
  if ("string" === typeof ipMask) {
55
56
  const ipObject = new ipAddress.Address4(ipMask);
@@ -57,7 +58,7 @@ const userScopeInputSchema = userScopeSchema.extend({
57
58
  userScope.numericIpMaskMax = ipObject.endAddress().bigInt();
58
59
  }
59
60
  if ("string" === typeof userAgent) {
60
- userScope.userAgentHash = util.hashUserAgent(userAgent);
61
+ userScope.userAgentHash = util$1.hashUserAgent(userAgent);
61
62
  }
62
63
  return userScope;
63
64
  });
@@ -22,8 +22,11 @@ function _interopNamespaceDefault(e) {
22
22
  const util__namespace = /* @__PURE__ */ _interopNamespaceDefault(util);
23
23
  const createRedisAccessRulesReader = (client, logger) => {
24
24
  return {
25
- findRules: async (filter) => {
25
+ findRules: async (filter, skipEmptyUserScopes = true) => {
26
26
  const query = redisAccessRulesIndex.getRedisAccessRulesQuery(filter);
27
+ if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
28
+ return [];
29
+ }
27
30
  let searchReply;
28
31
  try {
29
32
  searchReply = await client.ft.search(
@@ -31,19 +34,21 @@ const createRedisAccessRulesReader = (client, logger) => {
31
34
  query,
32
35
  redisAccessRulesIndex.accessRulesRedisSearchOptions
33
36
  );
34
- logger.debug(() => ({
35
- msg: "Executed search query",
36
- data: {
37
- inspect: util__namespace.inspect(
38
- {
39
- filter,
40
- searchReply,
41
- query
42
- },
43
- { depth: null }
44
- )
45
- }
46
- }));
37
+ if (searchReply.total > 0) {
38
+ logger.debug(() => ({
39
+ msg: "Executed search query",
40
+ data: {
41
+ inspect: util__namespace.inspect(
42
+ {
43
+ filter,
44
+ searchReply,
45
+ query
46
+ },
47
+ { depth: null }
48
+ )
49
+ }
50
+ }));
51
+ }
47
52
  } catch (e) {
48
53
  logger.error(() => ({
49
54
  err: e,
@@ -115,7 +120,8 @@ const createRedisAccessRulesWriter = (client) => {
115
120
  deleteRules: async (ruleIds) => void await client.del(ruleIds),
116
121
  deleteAllRules: async () => {
117
122
  const keys = await client.keys(`${redisAccessRulesIndex.accessRuleRedisKeyPrefix}*`);
118
- return keys.length > 0 ? await client.del(keys) : 0;
123
+ if (keys.length === 0) return 0;
124
+ return await client.del(keys);
119
125
  }
120
126
  };
121
127
  };
@@ -7,7 +7,7 @@ const redisIndex = require("./redisIndex.cjs");
7
7
  const accessRulesRedisIndexName = "index:user-access-rules";
8
8
  const accessRuleRedisKeyPrefix = "uar:";
9
9
  const accessRuleContentHashAlgorithm = "md5";
10
- const DEFAULT_SEARCH_LIMIT = 1e4;
10
+ const DEFAULT_SEARCH_LIMIT = 1e3;
11
11
  const accessRulesIndex = {
12
12
  name: accessRulesRedisIndexName,
13
13
  /**
@@ -35,7 +35,7 @@ const accessRulesIndex = {
35
35
  // the satisfy statement is to guarantee that the keys are right
36
36
  options: {
37
37
  ON: "HASH",
38
- PREFIX: accessRuleRedisKeyPrefix
38
+ PREFIX: [accessRuleRedisKeyPrefix]
39
39
  }
40
40
  };
41
41
  const createRedisAccessRulesIndex = async (client) => redisIndex.createRedisIndex(client, accessRulesIndex);
@@ -48,6 +48,10 @@ const greedyFieldComparisons = {
48
48
  numericIp: (value) => `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`
49
49
  };
50
50
  const accessRulesRedisSearchOptions = {
51
+ // #2 is a required option when the 'ismissing()' function is in the query body
52
+ DIALECT: 2
53
+ };
54
+ const accessRulesRedisDeleteOptions = {
51
55
  // #2 is a required option when the 'ismissing()' function is in the query body
52
56
  DIALECT: 2,
53
57
  LIMIT: {
@@ -100,6 +104,7 @@ const getRedisAccessRuleValue = (rule) => Object.fromEntries(
100
104
  Object.entries(rule).map(([key, value]) => [key, String(value)])
101
105
  );
102
106
  exports.accessRuleRedisKeyPrefix = accessRuleRedisKeyPrefix;
107
+ exports.accessRulesRedisDeleteOptions = accessRulesRedisDeleteOptions;
103
108
  exports.accessRulesRedisIndexName = accessRulesRedisIndexName;
104
109
  exports.accessRulesRedisSearchOptions = accessRulesRedisSearchOptions;
105
110
  exports.createRedisAccessRulesIndex = createRedisAccessRulesIndex;
package/dist/index.js CHANGED
@@ -1,15 +1,28 @@
1
- import { AccessPolicyType, accessPolicySchema, policyScopeSchema, } from "#policy/accessPolicy.js";
2
- import { createAccessPolicyResolver, } from "#policy/accessPolicyResolver.js";
3
- import { ScopeMatch } from "#policy/accessPolicyResolver.js";
4
- import { AccessRuleApiRoutes, accessRuleApiPaths, getExpressApiRuleRateLimits, } from "#policy/api/accessRuleApiRoutes.js";
5
- import { deleteAllRulesEndpointSchema } from "#policy/api/deleteAllRulesEndpoint.js";
6
- import { deleteRulesEndpointSchema, } from "#policy/api/deleteRulesEndpoint.js";
7
- import { insertRulesEndpointSchema, } from "#policy/api/insertRulesEndpoint.js";
8
- import { createRedisAccessRulesStorage } from "#policy/redis/redisAccessRules.js";
9
- import { createRedisAccessRulesIndex } from "#policy/redis/redisAccessRulesIndex.js";
10
- import { userScopeInputSchema } from "./accessPolicy.js";
11
- export const createApiRuleRoutesProvider = (rulesStorage) => {
12
- return new AccessRuleApiRoutes(rulesStorage);
1
+ import { AccessPolicyType, accessPolicySchema, policyScopeSchema, userScopeInputSchema } from "./accessPolicy.js";
2
+ import { ScopeMatch, createAccessPolicyResolver } from "./accessPolicyResolver.js";
3
+ import { AccessRuleApiRoutes } from "./api/accessRuleApiRoutes.js";
4
+ import { accessRuleApiPaths, getExpressApiRuleRateLimits } from "./api/accessRuleApiRoutes.js";
5
+ import { deleteAllRulesEndpointSchema } from "./api/deleteAllRulesEndpoint.js";
6
+ import { deleteRulesEndpointSchema } from "./api/deleteRulesEndpoint.js";
7
+ import { insertRulesEndpointSchema } from "./api/insertRulesEndpoint.js";
8
+ import { createRedisAccessRulesStorage } from "./redis/redisAccessRules.js";
9
+ import { createRedisAccessRulesIndex } from "./redis/redisAccessRulesIndex.js";
10
+ const createApiRuleRoutesProvider = (rulesStorage) => {
11
+ return new AccessRuleApiRoutes(rulesStorage);
12
+ };
13
+ export {
14
+ AccessPolicyType,
15
+ ScopeMatch,
16
+ accessPolicySchema,
17
+ accessRuleApiPaths,
18
+ createAccessPolicyResolver,
19
+ createApiRuleRoutesProvider,
20
+ createRedisAccessRulesIndex,
21
+ createRedisAccessRulesStorage,
22
+ deleteAllRulesEndpointSchema,
23
+ deleteRulesEndpointSchema,
24
+ getExpressApiRuleRateLimits,
25
+ insertRulesEndpointSchema,
26
+ policyScopeSchema,
27
+ userScopeInputSchema
13
28
  };
14
- export { createAccessPolicyResolver, AccessPolicyType, ScopeMatch, createRedisAccessRulesIndex, createRedisAccessRulesStorage, accessRuleApiPaths, accessPolicySchema, policyScopeSchema, insertRulesEndpointSchema, deleteAllRulesEndpointSchema, deleteRulesEndpointSchema, getExpressApiRuleRateLimits, userScopeInputSchema, };
15
- //# sourceMappingURL=index.js.map
@@ -1,112 +1,135 @@
1
1
  import * as util from "node:util";
2
- import { accessRuleSchema, } from "#policy/accessRules.js";
3
- import { accessRuleRedisKeyPrefix, accessRulesRedisIndexName, accessRulesRedisSearchOptions, getRedisAccessRuleKey, getRedisAccessRuleValue, getRedisAccessRulesQuery, } from "#policy/redis/redisAccessRulesIndex.js";
4
- export const createRedisAccessRulesReader = (client, logger) => {
5
- return {
6
- findRules: async (filter) => {
7
- const query = getRedisAccessRulesQuery(filter);
8
- let searchReply;
9
- try {
10
- searchReply = await client.ft.search(accessRulesRedisIndexName, query, accessRulesRedisSearchOptions);
11
- logger.debug(() => ({
12
- msg: "Executed search query",
13
- data: {
14
- inspect: util.inspect({
15
- filter: filter,
16
- searchReply: searchReply,
17
- query: query,
18
- }, { depth: null }),
19
- },
20
- }));
2
+ import { accessRuleSchema } from "../accessRules.js";
3
+ import { getRedisAccessRulesQuery, accessRulesRedisIndexName, accessRulesRedisSearchOptions, accessRuleRedisKeyPrefix, getRedisAccessRuleKey, getRedisAccessRuleValue } from "./redisAccessRulesIndex.js";
4
+ const createRedisAccessRulesReader = (client, logger) => {
5
+ return {
6
+ findRules: async (filter, skipEmptyUserScopes = true) => {
7
+ const query = getRedisAccessRulesQuery(filter);
8
+ if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
9
+ return [];
10
+ }
11
+ let searchReply;
12
+ try {
13
+ searchReply = await client.ft.search(
14
+ accessRulesRedisIndexName,
15
+ query,
16
+ accessRulesRedisSearchOptions
17
+ );
18
+ if (searchReply.total > 0) {
19
+ logger.debug(() => ({
20
+ msg: "Executed search query",
21
+ data: {
22
+ inspect: util.inspect(
23
+ {
24
+ filter,
25
+ searchReply,
26
+ query
27
+ },
28
+ { depth: null }
29
+ )
21
30
  }
22
- catch (e) {
23
- logger.error(() => ({
24
- err: e,
25
- data: {
26
- inspect: util.inspect({
27
- query: query,
28
- filter: filter,
29
- }, {
30
- depth: null,
31
- }),
32
- },
33
- msg: "failed to execute search query",
34
- }));
35
- return [];
36
- }
37
- return extractAccessRulesFromSearchReply(searchReply, logger);
38
- },
39
- findRuleIds: async (filter) => {
40
- const query = getRedisAccessRulesQuery(filter);
41
- let searchReply;
42
- try {
43
- searchReply = await client.ft.searchNoContent(accessRulesRedisIndexName, query, accessRulesRedisSearchOptions);
44
- }
45
- catch (e) {
46
- logger.error(() => ({
47
- err: e,
48
- data: {
49
- inspect: util.inspect({
50
- query: query,
51
- filter: filter,
52
- }, {
53
- depth: null,
54
- }),
55
- },
56
- msg: "Failed to execute search query for rule IDs",
57
- }));
58
- return [];
59
- }
60
- return searchReply.documents;
61
- },
62
- };
31
+ }));
32
+ }
33
+ } catch (e) {
34
+ logger.error(() => ({
35
+ err: e,
36
+ data: {
37
+ inspect: util.inspect(
38
+ {
39
+ query,
40
+ filter
41
+ },
42
+ {
43
+ depth: null
44
+ }
45
+ )
46
+ },
47
+ msg: "failed to execute search query"
48
+ }));
49
+ return [];
50
+ }
51
+ return extractAccessRulesFromSearchReply(searchReply, logger);
52
+ },
53
+ findRuleIds: async (filter) => {
54
+ const query = getRedisAccessRulesQuery(filter);
55
+ let searchReply;
56
+ try {
57
+ searchReply = await client.ft.searchNoContent(
58
+ accessRulesRedisIndexName,
59
+ query,
60
+ accessRulesRedisSearchOptions
61
+ );
62
+ } catch (e) {
63
+ logger.error(() => ({
64
+ err: e,
65
+ data: {
66
+ inspect: util.inspect(
67
+ {
68
+ query,
69
+ filter
70
+ },
71
+ {
72
+ depth: null
73
+ }
74
+ )
75
+ },
76
+ msg: "Failed to execute search query for rule IDs"
77
+ }));
78
+ return [];
79
+ }
80
+ return searchReply.documents;
81
+ }
82
+ };
63
83
  };
64
- export const createRedisAccessRulesWriter = (client) => {
65
- return {
66
- insertRule: async (rule, expirationTimestamp) => {
67
- const ruleKey = getRedisAccessRuleKey(rule);
68
- const ruleValue = getRedisAccessRuleValue(rule);
69
- await client.hSet(ruleKey, ruleValue);
70
- if (expirationTimestamp) {
71
- const expiryDate = new Date(expirationTimestamp);
72
- if (expiryDate.getUTCFullYear() === 1970) {
73
- await client.expireAt(ruleKey, expirationTimestamp);
74
- }
75
- else {
76
- const timestampInSeconds = Math.floor(expirationTimestamp / 1000);
77
- await client.expireAt(ruleKey, timestampInSeconds);
78
- }
79
- }
80
- return ruleKey;
81
- },
82
- deleteRules: async (ruleIds) => void (await client.del(ruleIds)),
83
- deleteAllRules: async () => {
84
- const keys = await client.keys(`${accessRuleRedisKeyPrefix}*`);
85
- return keys.length > 0 ? await client.del(keys) : 0;
86
- },
87
- };
84
+ const createRedisAccessRulesWriter = (client) => {
85
+ return {
86
+ insertRule: async (rule, expirationTimestamp) => {
87
+ const ruleKey = getRedisAccessRuleKey(rule);
88
+ const ruleValue = getRedisAccessRuleValue(rule);
89
+ await client.hSet(ruleKey, ruleValue);
90
+ if (expirationTimestamp) {
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);
97
+ }
98
+ }
99
+ return ruleKey;
100
+ },
101
+ deleteRules: async (ruleIds) => void await client.del(ruleIds),
102
+ deleteAllRules: async () => {
103
+ const keys = await client.keys(`${accessRuleRedisKeyPrefix}*`);
104
+ if (keys.length === 0) return 0;
105
+ return await client.del(keys);
106
+ }
107
+ };
88
108
  };
89
- export const createRedisAccessRulesStorage = (client, logger) => {
90
- return {
91
- ...createRedisAccessRulesReader(client, logger),
92
- ...createRedisAccessRulesWriter(client),
93
- };
109
+ const createRedisAccessRulesStorage = (client, logger) => {
110
+ return {
111
+ ...createRedisAccessRulesReader(client, logger),
112
+ ...createRedisAccessRulesWriter(client)
113
+ };
94
114
  };
95
115
  const extractAccessRulesFromSearchReply = (searchReply, logger) => {
96
- const accessRules = [];
97
- searchReply.documents.map(({ id, value: document }) => {
98
- const parsedDocument = accessRuleSchema.safeParse(document);
99
- if (parsedDocument.success) {
100
- accessRules.push(parsedDocument.data);
101
- }
102
- else {
103
- logger.debug(() => ({
104
- msg: "Failed to parse access rule from search reply",
105
- id: id,
106
- error: parsedDocument.error,
107
- }));
108
- }
109
- });
110
- return accessRules;
116
+ const accessRules = [];
117
+ searchReply.documents.map(({ id, value: document }) => {
118
+ const parsedDocument = accessRuleSchema.safeParse(document);
119
+ if (parsedDocument.success) {
120
+ accessRules.push(parsedDocument.data);
121
+ } else {
122
+ logger.debug(() => ({
123
+ msg: "Failed to parse access rule from search reply",
124
+ id,
125
+ error: parsedDocument.error
126
+ }));
127
+ }
128
+ });
129
+ return accessRules;
130
+ };
131
+ export {
132
+ createRedisAccessRulesReader,
133
+ createRedisAccessRulesStorage,
134
+ createRedisAccessRulesWriter
111
135
  };
112
- //# sourceMappingURL=redisAccessRules.js.map
@@ -1,86 +1,113 @@
1
1
  import crypto from "node:crypto";
2
2
  import { SCHEMA_FIELD_TYPE } from "@redis/search";
3
- import { ScopeMatch } from "#policy/accessPolicyResolver.js";
4
- import { createRedisIndex } from "#policy/redis/redisIndex.js";
5
- export const accessRulesRedisIndexName = "index:user-access-rules";
6
- export const accessRuleRedisKeyPrefix = "uar:";
3
+ import { ScopeMatch } from "../accessPolicyResolver.js";
4
+ import { createRedisIndex } from "./redisIndex.js";
5
+ const accessRulesRedisIndexName = "index:user-access-rules";
6
+ const accessRuleRedisKeyPrefix = "uar:";
7
7
  const accessRuleContentHashAlgorithm = "md5";
8
- const DEFAULT_SEARCH_LIMIT = 10000;
8
+ const DEFAULT_SEARCH_LIMIT = 1e3;
9
9
  const accessRulesIndex = {
10
- name: accessRulesRedisIndexName,
11
- schema: {
12
- clientId: {
13
- type: SCHEMA_FIELD_TYPE.TAG,
14
- INDEXMISSING: true,
15
- },
16
- numericIpMaskMin: SCHEMA_FIELD_TYPE.NUMERIC,
17
- numericIpMaskMax: SCHEMA_FIELD_TYPE.NUMERIC,
18
- userId: SCHEMA_FIELD_TYPE.TAG,
19
- numericIp: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
20
- ja4Hash: SCHEMA_FIELD_TYPE.TAG,
21
- headersHash: SCHEMA_FIELD_TYPE.TAG,
22
- userAgentHash: SCHEMA_FIELD_TYPE.TAG,
23
- },
24
- options: {
25
- ON: "HASH",
26
- PREFIX: accessRuleRedisKeyPrefix,
10
+ name: accessRulesRedisIndexName,
11
+ /**
12
+ * Note on the field type decision
13
+ *
14
+ * TAG is designed for the exact value matching
15
+ * TEXT is designed for the word-based and pattern matching
16
+ *
17
+ * For our goal TAG fits perfectly and, more performant
18
+ */
19
+ schema: {
20
+ clientId: {
21
+ type: SCHEMA_FIELD_TYPE.TAG,
22
+ // necessary to make possible use of the ismissing() function on this field in the search
23
+ INDEXMISSING: true
27
24
  },
25
+ numericIpMaskMin: SCHEMA_FIELD_TYPE.NUMERIC,
26
+ numericIpMaskMax: SCHEMA_FIELD_TYPE.NUMERIC,
27
+ userId: SCHEMA_FIELD_TYPE.TAG,
28
+ numericIp: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
29
+ ja4Hash: SCHEMA_FIELD_TYPE.TAG,
30
+ headersHash: SCHEMA_FIELD_TYPE.TAG,
31
+ userAgentHash: SCHEMA_FIELD_TYPE.TAG
32
+ },
33
+ // the satisfy statement is to guarantee that the keys are right
34
+ options: {
35
+ ON: "HASH",
36
+ PREFIX: [accessRuleRedisKeyPrefix]
37
+ }
28
38
  };
29
- export const createRedisAccessRulesIndex = async (client) => createRedisIndex(client, accessRulesIndex);
39
+ const createRedisAccessRulesIndex = async (client) => createRedisIndex(client, accessRulesIndex);
30
40
  const numericIndexFields = [
31
- "numericIp",
32
- "numericIpMaskMin",
33
- "numericIpMaskMax",
41
+ "numericIp",
42
+ "numericIpMaskMin",
43
+ "numericIpMaskMax"
34
44
  ];
35
45
  const greedyFieldComparisons = {
36
- numericIp: (value) => `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`,
46
+ numericIp: (value) => `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`
37
47
  };
38
- export const accessRulesRedisSearchOptions = {
39
- DIALECT: 2,
40
- LIMIT: {
41
- from: 0,
42
- size: DEFAULT_SEARCH_LIMIT,
43
- },
48
+ const accessRulesRedisSearchOptions = {
49
+ // #2 is a required option when the 'ismissing()' function is in the query body
50
+ DIALECT: 2
44
51
  };
45
- export const getRedisAccessRulesQuery = (filter) => {
46
- const { policyScope, userScope } = filter;
47
- const policyScopeFilter = getPolicyScopeQuery(policyScope, filter.policyScopeMatch);
48
- if (userScope && Object.keys(userScope).length > 0) {
49
- const userScopeFilter = getUserScopeQuery(userScope, filter.userScopeMatch);
50
- return `${policyScopeFilter} ( ${userScopeFilter} )`;
51
- }
52
- return policyScopeFilter ? policyScopeFilter : "*";
52
+ const accessRulesRedisDeleteOptions = {
53
+ // #2 is a required option when the 'ismissing()' function is in the query body
54
+ DIALECT: 2,
55
+ LIMIT: {
56
+ from: 0,
57
+ size: DEFAULT_SEARCH_LIMIT
58
+ }
59
+ };
60
+ const getRedisAccessRulesQuery = (filter) => {
61
+ const { policyScope, userScope } = filter;
62
+ const policyScopeFilter = getPolicyScopeQuery(
63
+ policyScope,
64
+ filter.policyScopeMatch
65
+ );
66
+ if (userScope && Object.keys(userScope).length > 0) {
67
+ const userScopeFilter = getUserScopeQuery(userScope, filter.userScopeMatch);
68
+ return `${policyScopeFilter} ( ${userScopeFilter} )`;
69
+ }
70
+ return policyScopeFilter ? policyScopeFilter : "*";
53
71
  };
54
72
  const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
55
- const clientId = policyScope?.clientId;
56
- if ("string" === typeof clientId) {
57
- return ScopeMatch.Exact === scopeMatchType
58
- ? `@clientId:{${clientId}}`
59
- : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
60
- }
61
- return ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
73
+ const clientId = policyScope?.clientId;
74
+ if ("string" === typeof clientId) {
75
+ return ScopeMatch.Exact === scopeMatchType ? `@clientId:{${clientId}}` : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
76
+ }
77
+ return ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
62
78
  };
63
79
  const getUserScopeQuery = (userScope, scopeMatchType) => {
64
- const scopeEntries = Object.entries(userScope)
65
- .filter(([_, value]) => value !== undefined);
66
- const scopeJoinType = ScopeMatch.Exact === scopeMatchType ? " " : " | ";
67
- return scopeEntries
68
- .map(([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(scopeFieldName, scopeFieldValue, scopeMatchType))
69
- .join(scopeJoinType);
80
+ const scopeEntries = Object.entries(userScope).filter(([_, value]) => value !== void 0);
81
+ const scopeJoinType = ScopeMatch.Exact === scopeMatchType ? " " : " | ";
82
+ return scopeEntries.map(
83
+ ([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(scopeFieldName, scopeFieldValue, scopeMatchType)
84
+ ).join(scopeJoinType);
70
85
  };
71
86
  const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
72
- if (ScopeMatch.Greedy === matchType &&
73
- "function" === typeof greedyFieldComparisons[fieldName]) {
74
- return greedyFieldComparisons[fieldName](fieldValue);
75
- }
76
- return numericIndexFields.includes(fieldName)
77
- ? `@${fieldName}:[${fieldValue}]`
78
- : `@${fieldName}:{${fieldValue}}`;
87
+ if (ScopeMatch.Greedy === matchType && "function" === typeof greedyFieldComparisons[fieldName]) {
88
+ return greedyFieldComparisons[fieldName](fieldValue);
89
+ }
90
+ return numericIndexFields.includes(fieldName) ? `@${fieldName}:[${fieldValue}]` : `@${fieldName}:{${fieldValue}}`;
91
+ };
92
+ const getRedisAccessRuleKey = (rule) => accessRuleRedisKeyPrefix + crypto.createHash(accessRuleContentHashAlgorithm).update(
93
+ JSON.stringify(
94
+ rule,
95
+ (key, value) => (
96
+ // JSON.stringify can't handle BigInt itself: throws "Do not know how to serialize a BigInt"
97
+ "bigint" === typeof value ? value.toString() : value
98
+ )
99
+ )
100
+ ).digest("hex");
101
+ const getRedisAccessRuleValue = (rule) => Object.fromEntries(
102
+ Object.entries(rule).map(([key, value]) => [key, String(value)])
103
+ );
104
+ export {
105
+ accessRuleRedisKeyPrefix,
106
+ accessRulesRedisDeleteOptions,
107
+ accessRulesRedisIndexName,
108
+ accessRulesRedisSearchOptions,
109
+ createRedisAccessRulesIndex,
110
+ getRedisAccessRuleKey,
111
+ getRedisAccessRuleValue,
112
+ getRedisAccessRulesQuery
79
113
  };
80
- export const getRedisAccessRuleKey = (rule) => accessRuleRedisKeyPrefix +
81
- crypto
82
- .createHash(accessRuleContentHashAlgorithm)
83
- .update(JSON.stringify(rule, (key, value) => "bigint" === typeof value ? value.toString() : value))
84
- .digest("hex");
85
- export const getRedisAccessRuleValue = (rule) => Object.fromEntries(Object.entries(rule).map(([key, value]) => [key, String(value)]));
86
- //# sourceMappingURL=redisAccessRulesIndex.js.map
@@ -1,23 +1,22 @@
1
1
  import crypto from "node:crypto";
2
2
  const redisIndexHashesRecordKey = "_index_hashes";
3
3
  const redisIndexHashAlgorithm = "sha256";
4
- export 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);
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;
13
11
  }
14
- await client.ft.create(index.name, index.schema, index.options);
15
- await saveIndexHash(client, index.name, indexHash);
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
16
  };
17
- const createIndexHash = (index) => crypto
18
- .createHash(redisIndexHashAlgorithm)
19
- .update(JSON.stringify(index))
20
- .digest("hex");
17
+ const createIndexHash = (index) => crypto.createHash(redisIndexHashAlgorithm).update(JSON.stringify(index)).digest("hex");
21
18
  const fetchIndexHash = async (client, indexName) => client.hGet(redisIndexHashesRecordKey, indexName);
22
19
  const saveIndexHash = async (client, indexName, indexHash) => client.hSet(redisIndexHashesRecordKey, indexName, indexHash);
23
- //# sourceMappingURL=redisIndex.js.map
20
+ export {
21
+ createRedisIndex
22
+ };
package/dist/util.js CHANGED
@@ -1,3 +1,5 @@
1
1
  import crypto from "node:crypto";
2
- export const hashUserAgent = (userAgent) => crypto.createHash("sha256").update(userAgent).digest("hex");
3
- //# sourceMappingURL=util.js.map
2
+ const hashUserAgent = (userAgent) => crypto.createHash("sha256").update(userAgent).digest("hex");
3
+ export {
4
+ hashUserAgent
5
+ };