@prosopo/user-access-policy 3.3.2 → 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.
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const crypto = require("node:crypto");
4
- const search = require("@redis/search");
5
- const accessPolicyResolver = require("../accessPolicyResolver.cjs");
6
- const redisIndex = require("./redisIndex.cjs");
7
- const accessRulesRedisIndexName = "index:user-access-rules";
8
- const accessRuleRedisKeyPrefix = "uar:";
9
- const accessRuleContentHashAlgorithm = "md5";
10
- const DEFAULT_SEARCH_LIMIT = 1e3;
11
- const accessRulesIndex = {
12
- name: accessRulesRedisIndexName,
13
- /**
14
- * Note on the field type decision
15
- *
16
- * TAG is designed for the exact value matching
17
- * TEXT is designed for the word-based and pattern matching
18
- *
19
- * For our goal TAG fits perfectly and, more performant
20
- */
21
- schema: {
22
- clientId: {
23
- type: search.SCHEMA_FIELD_TYPE.TAG,
24
- // necessary to make possible use of the ismissing() function on this field in the search
25
- INDEXMISSING: true
26
- },
27
- numericIpMaskMin: search.SCHEMA_FIELD_TYPE.NUMERIC,
28
- numericIpMaskMax: search.SCHEMA_FIELD_TYPE.NUMERIC,
29
- userId: search.SCHEMA_FIELD_TYPE.TAG,
30
- numericIp: { type: search.SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
31
- ja4Hash: search.SCHEMA_FIELD_TYPE.TAG,
32
- headersHash: search.SCHEMA_FIELD_TYPE.TAG,
33
- userAgentHash: search.SCHEMA_FIELD_TYPE.TAG
34
- },
35
- // the satisfy statement is to guarantee that the keys are right
36
- options: {
37
- ON: "HASH",
38
- PREFIX: [accessRuleRedisKeyPrefix]
39
- }
40
- };
41
- const createRedisAccessRulesIndex = async (client) => redisIndex.createRedisIndex(client, accessRulesIndex);
42
- const numericIndexFields = [
43
- "numericIp",
44
- "numericIpMaskMin",
45
- "numericIpMaskMax"
46
- ];
47
- const greedyFieldComparisons = {
48
- numericIp: (value) => `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`
49
- };
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 = {
55
- // #2 is a required option when the 'ismissing()' function is in the query body
56
- DIALECT: 2,
57
- LIMIT: {
58
- from: 0,
59
- size: DEFAULT_SEARCH_LIMIT
60
- }
61
- };
62
- const getRedisAccessRulesQuery = (filter) => {
63
- const { policyScope, userScope } = filter;
64
- const policyScopeFilter = getPolicyScopeQuery(
65
- policyScope,
66
- filter.policyScopeMatch
67
- );
68
- if (userScope && Object.keys(userScope).length > 0) {
69
- const userScopeFilter = getUserScopeQuery(userScope, filter.userScopeMatch);
70
- return `${policyScopeFilter} ( ${userScopeFilter} )`;
71
- }
72
- return policyScopeFilter ? policyScopeFilter : "*";
73
- };
74
- const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
75
- const clientId = policyScope?.clientId;
76
- if ("string" === typeof clientId) {
77
- return accessPolicyResolver.ScopeMatch.Exact === scopeMatchType ? `@clientId:{${clientId}}` : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
78
- }
79
- return accessPolicyResolver.ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
80
- };
81
- const getUserScopeQuery = (userScope, scopeMatchType) => {
82
- const scopeEntries = Object.entries(userScope).filter(([_, value]) => value !== void 0);
83
- const scopeJoinType = accessPolicyResolver.ScopeMatch.Exact === scopeMatchType ? " " : " | ";
84
- return scopeEntries.map(
85
- ([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(scopeFieldName, scopeFieldValue, scopeMatchType)
86
- ).join(scopeJoinType);
87
- };
88
- const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
89
- if (accessPolicyResolver.ScopeMatch.Greedy === matchType && "function" === typeof greedyFieldComparisons[fieldName]) {
90
- return greedyFieldComparisons[fieldName](fieldValue);
91
- }
92
- return numericIndexFields.includes(fieldName) ? `@${fieldName}:[${fieldValue}]` : `@${fieldName}:{${fieldValue}}`;
93
- };
94
- const getRedisAccessRuleKey = (rule) => accessRuleRedisKeyPrefix + crypto.createHash(accessRuleContentHashAlgorithm).update(
95
- JSON.stringify(
96
- rule,
97
- (key, value) => (
98
- // JSON.stringify can't handle BigInt itself: throws "Do not know how to serialize a BigInt"
99
- "bigint" === typeof value ? value.toString() : value
100
- )
101
- )
102
- ).digest("hex");
103
- const getRedisAccessRuleValue = (rule) => Object.fromEntries(
104
- Object.entries(rule).map(([key, value]) => [key, String(value)])
105
- );
106
- exports.accessRuleRedisKeyPrefix = accessRuleRedisKeyPrefix;
107
- exports.accessRulesRedisDeleteOptions = accessRulesRedisDeleteOptions;
108
- exports.accessRulesRedisIndexName = accessRulesRedisIndexName;
109
- exports.accessRulesRedisSearchOptions = accessRulesRedisSearchOptions;
110
- exports.createRedisAccessRulesIndex = createRedisAccessRulesIndex;
111
- exports.getRedisAccessRuleKey = getRedisAccessRuleKey;
112
- exports.getRedisAccessRuleValue = getRedisAccessRuleValue;
113
- exports.getRedisAccessRulesQuery = getRedisAccessRulesQuery;
@@ -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;
@@ -1,113 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { SCHEMA_FIELD_TYPE } from "@redis/search";
3
- import { ScopeMatch } from "../accessPolicyResolver.js";
4
- import { createRedisIndex } from "./redisIndex.js";
5
- const accessRulesRedisIndexName = "index:user-access-rules";
6
- const accessRuleRedisKeyPrefix = "uar:";
7
- const accessRuleContentHashAlgorithm = "md5";
8
- const DEFAULT_SEARCH_LIMIT = 1e3;
9
- const accessRulesIndex = {
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
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
- }
38
- };
39
- const createRedisAccessRulesIndex = async (client) => createRedisIndex(client, accessRulesIndex);
40
- const numericIndexFields = [
41
- "numericIp",
42
- "numericIpMaskMin",
43
- "numericIpMaskMax"
44
- ];
45
- const greedyFieldComparisons = {
46
- numericIp: (value) => `( @numericIp:[${value}] | ( @numericIpMaskMin:[-inf ${value}] @numericIpMaskMax:[${value} +inf] ) )`
47
- };
48
- const accessRulesRedisSearchOptions = {
49
- // #2 is a required option when the 'ismissing()' function is in the query body
50
- DIALECT: 2
51
- };
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 : "*";
71
- };
72
- const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
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)" : "";
78
- };
79
- const getUserScopeQuery = (userScope, scopeMatchType) => {
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);
85
- };
86
- const getUserScopeFieldQuery = (fieldName, fieldValue, matchType) => {
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
113
- };
@@ -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
- };