@prosopo/user-access-policy 3.3.0 → 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 (74) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/accessPolicy.js +71 -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/index.js +27 -14
  10. package/dist/redis/redisAccessRules.js +128 -112
  11. package/dist/redis/redisAccessRulesIndex.js +94 -70
  12. package/dist/redis/redisIndex.js +15 -16
  13. package/dist/util.js +4 -2
  14. package/package.json +15 -10
  15. package/vite.cjs.config.ts +7 -6
  16. package/vite.esm.config.ts +20 -0
  17. package/vite.test.config.ts +16 -21
  18. package/dist/accessPolicy.d.ts +0 -169
  19. package/dist/accessPolicy.d.ts.map +0 -1
  20. package/dist/accessPolicy.js.map +0 -1
  21. package/dist/accessPolicyResolver.d.ts +0 -115
  22. package/dist/accessPolicyResolver.d.ts.map +0 -1
  23. package/dist/accessPolicyResolver.js.map +0 -1
  24. package/dist/accessRules.d.ts +0 -16
  25. package/dist/accessRules.d.ts.map +0 -1
  26. package/dist/accessRules.js.map +0 -1
  27. package/dist/api/accessRuleApiRoutes.d.ts +0 -27
  28. package/dist/api/accessRuleApiRoutes.d.ts.map +0 -1
  29. package/dist/api/accessRuleApiRoutes.js.map +0 -1
  30. package/dist/api/deleteAllRulesEndpoint.d.ts +0 -12
  31. package/dist/api/deleteAllRulesEndpoint.d.ts.map +0 -1
  32. package/dist/api/deleteAllRulesEndpoint.js.map +0 -1
  33. package/dist/api/deleteRulesEndpoint.d.ts +0 -116
  34. package/dist/api/deleteRulesEndpoint.d.ts.map +0 -1
  35. package/dist/api/deleteRulesEndpoint.js.map +0 -1
  36. package/dist/api/insertRulesEndpoint.d.ts +0 -22
  37. package/dist/api/insertRulesEndpoint.d.ts.map +0 -1
  38. package/dist/api/insertRulesEndpoint.js.map +0 -1
  39. package/dist/index.d.ts +0 -15
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/index.js.map +0 -1
  42. package/dist/redis/redisAccessRules.d.ts +0 -7
  43. package/dist/redis/redisAccessRules.d.ts.map +0 -1
  44. package/dist/redis/redisAccessRules.js.map +0 -1
  45. package/dist/redis/redisAccessRulesIndex.d.ts +0 -13
  46. package/dist/redis/redisAccessRulesIndex.d.ts.map +0 -1
  47. package/dist/redis/redisAccessRulesIndex.js.map +0 -1
  48. package/dist/redis/redisIndex.d.ts +0 -9
  49. package/dist/redis/redisIndex.d.ts.map +0 -1
  50. package/dist/redis/redisIndex.js.map +0 -1
  51. package/dist/tests/accessPolicy.test.d.ts +0 -2
  52. package/dist/tests/accessPolicy.test.d.ts.map +0 -1
  53. package/dist/tests/accessPolicy.test.js +0 -27
  54. package/dist/tests/accessPolicy.test.js.map +0 -1
  55. package/dist/tests/redis/redisAccessRules.test.d.ts +0 -2
  56. package/dist/tests/redis/redisAccessRules.test.d.ts.map +0 -1
  57. package/dist/tests/redis/redisAccessRules.test.js +0 -413
  58. package/dist/tests/redis/redisAccessRules.test.js.map +0 -1
  59. package/dist/tests/redis/redisIndex.test.d.ts +0 -2
  60. package/dist/tests/redis/redisIndex.test.d.ts.map +0 -1
  61. package/dist/tests/redis/redisIndex.test.js +0 -84
  62. package/dist/tests/redis/redisIndex.test.js.map +0 -1
  63. package/dist/tests/redis/testRedisClient.d.ts +0 -3
  64. package/dist/tests/redis/testRedisClient.d.ts.map +0 -1
  65. package/dist/tests/redis/testRedisClient.js +0 -8
  66. package/dist/tests/redis/testRedisClient.js.map +0 -1
  67. package/dist/tests/testLogger.d.ts +0 -4
  68. package/dist/tests/testLogger.d.ts.map +0 -1
  69. package/dist/tests/testLogger.js +0 -22
  70. package/dist/tests/testLogger.js.map +0 -1
  71. package/dist/util.d.ts +0 -2
  72. package/dist/util.d.ts.map +0 -1
  73. package/dist/util.js.map +0 -1
  74. package/vite.config.ts +0 -39
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,119 +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, skipEmptyUserScopes = true) => {
7
- const query = getRedisAccessRulesQuery(filter);
8
- if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
9
- return [];
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
+ )
10
30
  }
11
- let searchReply;
12
- try {
13
- searchReply = await client.ft.search(accessRulesRedisIndexName, query, accessRulesRedisSearchOptions);
14
- if (searchReply.total > 0) {
15
- logger.debug(() => ({
16
- msg: "Executed search query",
17
- data: {
18
- inspect: util.inspect({
19
- filter: filter,
20
- searchReply: searchReply,
21
- query: query,
22
- }, { depth: null }),
23
- },
24
- }));
25
- }
26
- }
27
- catch (e) {
28
- logger.error(() => ({
29
- err: e,
30
- data: {
31
- inspect: util.inspect({
32
- query: query,
33
- filter: filter,
34
- }, {
35
- depth: null,
36
- }),
37
- },
38
- msg: "failed to execute search query",
39
- }));
40
- return [];
41
- }
42
- return extractAccessRulesFromSearchReply(searchReply, logger);
43
- },
44
- findRuleIds: async (filter) => {
45
- const query = getRedisAccessRulesQuery(filter);
46
- let searchReply;
47
- try {
48
- searchReply = await client.ft.searchNoContent(accessRulesRedisIndexName, query, accessRulesRedisSearchOptions);
49
- }
50
- catch (e) {
51
- logger.error(() => ({
52
- err: e,
53
- data: {
54
- inspect: util.inspect({
55
- query: query,
56
- filter: filter,
57
- }, {
58
- depth: null,
59
- }),
60
- },
61
- msg: "Failed to execute search query for rule IDs",
62
- }));
63
- return [];
64
- }
65
- return searchReply.documents;
66
- },
67
- };
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
+ };
68
83
  };
69
- export const createRedisAccessRulesWriter = (client) => {
70
- return {
71
- insertRule: async (rule, expirationTimestamp) => {
72
- const ruleKey = getRedisAccessRuleKey(rule);
73
- const ruleValue = getRedisAccessRuleValue(rule);
74
- await client.hSet(ruleKey, ruleValue);
75
- if (expirationTimestamp) {
76
- const expiryDate = new Date(expirationTimestamp);
77
- if (expiryDate.getUTCFullYear() === 1970) {
78
- await client.expireAt(ruleKey, expirationTimestamp);
79
- }
80
- else {
81
- const timestampInSeconds = Math.floor(expirationTimestamp / 1000);
82
- await client.expireAt(ruleKey, timestampInSeconds);
83
- }
84
- }
85
- return ruleKey;
86
- },
87
- deleteRules: async (ruleIds) => void (await client.del(ruleIds)),
88
- deleteAllRules: async () => {
89
- const keys = await client.keys(`${accessRuleRedisKeyPrefix}*`);
90
- if (keys.length === 0)
91
- return 0;
92
- return await client.del(keys);
93
- },
94
- };
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
+ };
95
108
  };
96
- export const createRedisAccessRulesStorage = (client, logger) => {
97
- return {
98
- ...createRedisAccessRulesReader(client, logger),
99
- ...createRedisAccessRulesWriter(client),
100
- };
109
+ const createRedisAccessRulesStorage = (client, logger) => {
110
+ return {
111
+ ...createRedisAccessRulesReader(client, logger),
112
+ ...createRedisAccessRulesWriter(client)
113
+ };
101
114
  };
102
115
  const extractAccessRulesFromSearchReply = (searchReply, logger) => {
103
- const accessRules = [];
104
- searchReply.documents.map(({ id, value: document }) => {
105
- const parsedDocument = accessRuleSchema.safeParse(document);
106
- if (parsedDocument.success) {
107
- accessRules.push(parsedDocument.data);
108
- }
109
- else {
110
- logger.debug(() => ({
111
- msg: "Failed to parse access rule from search reply",
112
- id: id,
113
- error: parsedDocument.error,
114
- }));
115
- }
116
- });
117
- 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
118
135
  };
119
- //# sourceMappingURL=redisAccessRules.js.map
@@ -1,89 +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 = 1000;
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,
48
+ const accessRulesRedisSearchOptions = {
49
+ // #2 is a required option when the 'ismissing()' function is in the query body
50
+ DIALECT: 2
40
51
  };
41
- export const accessRulesRedisDeleteOptions = {
42
- DIALECT: 2,
43
- LIMIT: {
44
- from: 0,
45
- size: DEFAULT_SEARCH_LIMIT,
46
- },
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
+ }
47
59
  };
48
- export const getRedisAccessRulesQuery = (filter) => {
49
- const { policyScope, userScope } = filter;
50
- const policyScopeFilter = getPolicyScopeQuery(policyScope, filter.policyScopeMatch);
51
- if (userScope && Object.keys(userScope).length > 0) {
52
- const userScopeFilter = getUserScopeQuery(userScope, filter.userScopeMatch);
53
- return `${policyScopeFilter} ( ${userScopeFilter} )`;
54
- }
55
- return policyScopeFilter ? policyScopeFilter : "*";
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 : "*";
56
71
  };
57
72
  const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
58
- const clientId = policyScope?.clientId;
59
- if ("string" === typeof clientId) {
60
- return ScopeMatch.Exact === scopeMatchType
61
- ? `@clientId:{${clientId}}`
62
- : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
63
- }
64
- 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)" : "";
65
78
  };
66
79
  const getUserScopeQuery = (userScope, scopeMatchType) => {
67
- const scopeEntries = Object.entries(userScope)
68
- .filter(([_, value]) => value !== undefined);
69
- const scopeJoinType = ScopeMatch.Exact === scopeMatchType ? " " : " | ";
70
- return scopeEntries
71
- .map(([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(scopeFieldName, scopeFieldValue, scopeMatchType))
72
- .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);
73
85
  };
74
86
  const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
75
- if (ScopeMatch.Greedy === matchType &&
76
- "function" === typeof greedyFieldComparisons[fieldName]) {
77
- return greedyFieldComparisons[fieldName](fieldValue);
78
- }
79
- return numericIndexFields.includes(fieldName)
80
- ? `@${fieldName}:[${fieldValue}]`
81
- : `@${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
82
113
  };
83
- export const getRedisAccessRuleKey = (rule) => accessRuleRedisKeyPrefix +
84
- crypto
85
- .createHash(accessRuleContentHashAlgorithm)
86
- .update(JSON.stringify(rule, (key, value) => "bigint" === typeof value ? value.toString() : value))
87
- .digest("hex");
88
- export const getRedisAccessRuleValue = (rule) => Object.fromEntries(Object.entries(rule).map(([key, value]) => [key, String(value)]));
89
- //# 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
+ };
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@prosopo/user-access-policy",
3
- "version": "3.3.0",
3
+ "version": "3.3.1",
4
4
  "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
5
6
  "type": "module",
6
7
  "engines": {
7
8
  "node": "20",
@@ -9,6 +10,7 @@
9
10
  },
10
11
  "exports": {
11
12
  ".": {
13
+ "types": "./dist/index.d.ts",
12
14
  "import": "./dist/index.js",
13
15
  "require": "./dist/cjs/index.cjs"
14
16
  }
@@ -17,22 +19,25 @@
17
19
  "#policy/*": "./dist/*"
18
20
  },
19
21
  "scripts": {
20
- "build": "tsc --build --verbose",
21
- "clean": "tsc --build --clean",
22
- "build:cjs": "npx vite build --config vite.cjs.config.ts",
22
+ "clean": "del-cli --verbose dist tsconfig.tsbuildinfo",
23
+ "build": "NODE_ENV=${NODE_ENV:-production}; vite build --config vite.esm.config.ts --mode $NODE_ENV",
24
+ "build:tsc": "tsc --build --verbose",
25
+ "build:cjs": "NODE_ENV=${NODE_ENV:-production}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
26
+ "typecheck": "tsc --build --declaration --emitDeclarationOnly",
23
27
  "test": "NODE_ENV=${NODE_ENV:-test}; npx vitest run --config ./vite.test.config.ts"
24
28
  },
25
29
  "dependencies": {
26
- "@prosopo/api-route": "2.6.7",
27
- "@prosopo/common": "3.0.2",
28
- "@prosopo/types": "3.0.3",
29
- "@prosopo/util": "3.0.2",
30
+ "@prosopo/api-route": "2.6.8",
31
+ "@prosopo/common": "3.1.0",
32
+ "@prosopo/types": "3.0.4",
33
+ "@prosopo/util": "3.0.3",
30
34
  "axios": "1.10.0",
31
35
  "esbuild": "0.25.6",
32
36
  "ip-address": "10.0.1",
33
37
  "redis": "5.0.0",
34
- "webpack-dev-server": "5.2.2",
35
- "zod": "3.23.8"
38
+ "zod": "3.23.8",
39
+ "@prosopo/config": "3.1.1",
40
+ "webpack-dev-server": "5.2.2"
36
41
  },
37
42
  "devDependencies": {
38
43
  "vite": "6.3.5",
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  // Copyright 2021-2025 Prosopo (UK) Ltd.
2
3
  //
3
4
  // Licensed under the Apache License, Version 2.0 (the "License");
@@ -11,11 +12,11 @@
11
12
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
13
  // See the License for the specific language governing permissions and
13
14
  // limitations under the License.
14
-
15
- import path from "node:path";
16
15
  import { ViteCommonJSConfig } from "@prosopo/config";
17
16
 
18
- export default ViteCommonJSConfig(
19
- "user-access-policy",
20
- path.resolve("./tsconfig.cjs.json"),
21
- );
17
+ export default function () {
18
+ return ViteCommonJSConfig(
19
+ path.basename("."),
20
+ path.resolve("./tsconfig.json"),
21
+ );
22
+ }
@@ -0,0 +1,20 @@
1
+ // Copyright 2021-2025 Prosopo (UK) Ltd.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ import path from "node:path";
16
+ import { ViteEsmConfig } from "@prosopo/config";
17
+
18
+ export default function () {
19
+ return ViteEsmConfig(path.basename("."), path.resolve("./tsconfig.json"));
20
+ }