@prosopo/user-access-policy 3.5.19 → 3.5.28

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 (93) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/.export.js +21 -0
  3. package/dist/api/.export.js +11 -0
  4. package/dist/api/delete/.export.js +1 -0
  5. package/dist/api/{deleteAllRulesEndpoint.js → delete/deleteAllRules.js} +10 -9
  6. package/dist/api/delete/deleteRuleGroups.js +52 -0
  7. package/dist/api/delete/deleteRules.js +43 -0
  8. package/dist/api/read/.export.js +1 -0
  9. package/dist/api/read/fetchRules.js +43 -0
  10. package/dist/api/read/findRuleIds.js +50 -0
  11. package/dist/api/read/getMissingIds.js +41 -0
  12. package/dist/api/ruleApiRoutes.js +131 -0
  13. package/dist/api/rulesApiClient.js +93 -0
  14. package/dist/api/write/.export.js +1 -0
  15. package/dist/api/write/insertRules.js +102 -0
  16. package/dist/api/write/rehashRules.js +57 -0
  17. package/dist/cjs/.export.cjs +21 -0
  18. package/dist/cjs/api/.export.cjs +11 -0
  19. package/dist/cjs/api/delete/.export.cjs +1 -0
  20. package/dist/cjs/api/{deleteAllRulesEndpoint.cjs → delete/deleteAllRules.cjs} +9 -8
  21. package/dist/cjs/api/delete/deleteRuleGroups.cjs +52 -0
  22. package/dist/cjs/api/delete/deleteRules.cjs +43 -0
  23. package/dist/cjs/api/read/.export.cjs +1 -0
  24. package/dist/cjs/api/read/fetchRules.cjs +43 -0
  25. package/dist/cjs/api/read/findRuleIds.cjs +50 -0
  26. package/dist/cjs/api/read/getMissingIds.cjs +41 -0
  27. package/dist/cjs/api/ruleApiRoutes.cjs +131 -0
  28. package/dist/cjs/api/rulesApiClient.cjs +93 -0
  29. package/dist/cjs/api/write/.export.cjs +1 -0
  30. package/dist/cjs/api/write/insertRules.cjs +102 -0
  31. package/dist/cjs/api/write/rehashRules.cjs +57 -0
  32. package/dist/cjs/mongoose/.export.cjs +4 -0
  33. package/dist/cjs/mongoose/mongooseRuleSchema.cjs +36 -0
  34. package/dist/cjs/redis/.export.cjs +6 -0
  35. package/dist/cjs/redis/reader/redisAggregate.cjs +60 -0
  36. package/dist/cjs/redis/reader/redisRulesQuery.cjs +99 -0
  37. package/dist/cjs/redis/reader/redisRulesReader.cjs +230 -0
  38. package/dist/cjs/redis/redisClient.cjs +67 -0
  39. package/dist/cjs/redis/redisRuleIndex.cjs +50 -0
  40. package/dist/cjs/redis/redisRulesStorage.cjs +22 -9
  41. package/dist/cjs/redis/redisRulesWriter.cjs +91 -64
  42. package/dist/cjs/rule.cjs +8 -0
  43. package/dist/cjs/ruleInput/.export.cjs +9 -0
  44. package/dist/cjs/ruleInput/policyInput.cjs +25 -0
  45. package/dist/cjs/ruleInput/ruleInput.cjs +50 -0
  46. package/dist/cjs/ruleInput/userScopeInput.cjs +55 -0
  47. package/dist/cjs/ruleRecord.cjs +23 -0
  48. package/dist/cjs/rulesStorage.cjs +8 -0
  49. package/dist/cjs/transformRule.cjs +77 -0
  50. package/dist/mongoose/.export.js +4 -0
  51. package/dist/mongoose/mongooseRuleSchema.js +36 -0
  52. package/dist/redis/.export.js +6 -0
  53. package/dist/redis/reader/redisAggregate.js +60 -0
  54. package/dist/redis/reader/redisRulesQuery.js +99 -0
  55. package/dist/redis/reader/redisRulesReader.js +213 -0
  56. package/dist/redis/redisClient.js +67 -0
  57. package/dist/redis/redisRuleIndex.js +50 -0
  58. package/dist/redis/redisRulesStorage.js +23 -10
  59. package/dist/redis/redisRulesWriter.js +91 -64
  60. package/dist/rule.js +8 -0
  61. package/dist/ruleInput/.export.js +9 -0
  62. package/dist/ruleInput/policyInput.js +25 -0
  63. package/dist/ruleInput/ruleInput.js +50 -0
  64. package/dist/ruleInput/userScopeInput.js +55 -0
  65. package/dist/ruleRecord.js +23 -0
  66. package/dist/rulesStorage.js +8 -0
  67. package/dist/transformRule.js +77 -0
  68. package/entries.ts +20 -0
  69. package/package.json +34 -18
  70. package/vite.cjs.config.ts +4 -1
  71. package/vite.esm.config.ts +6 -1
  72. package/dist/accessPolicy.js +0 -80
  73. package/dist/accessPolicyResolver.js +0 -31
  74. package/dist/accessRules.js +0 -11
  75. package/dist/api/accessRuleApiRoutes.js +0 -79
  76. package/dist/api/accessRulesApiClient.js +0 -38
  77. package/dist/api/deleteRulesEndpoint.js +0 -34
  78. package/dist/api/insertRulesEndpoint.js +0 -62
  79. package/dist/cjs/accessPolicy.cjs +0 -80
  80. package/dist/cjs/accessPolicyResolver.cjs +0 -31
  81. package/dist/cjs/accessRules.cjs +0 -11
  82. package/dist/cjs/api/accessRuleApiRoutes.cjs +0 -79
  83. package/dist/cjs/api/accessRulesApiClient.cjs +0 -38
  84. package/dist/cjs/api/deleteRulesEndpoint.cjs +0 -34
  85. package/dist/cjs/api/insertRulesEndpoint.cjs +0 -62
  86. package/dist/cjs/index.cjs +0 -31
  87. package/dist/cjs/redis/redisRulesIndex.cjs +0 -138
  88. package/dist/cjs/redis/redisRulesReader.cjs +0 -142
  89. package/dist/cjs/util.cjs +0 -5
  90. package/dist/index.js +0 -32
  91. package/dist/redis/redisRulesIndex.js +0 -138
  92. package/dist/redis/redisRulesReader.js +0 -125
  93. package/dist/util.js +0 -5
@@ -1,142 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const util = require("node:util");
4
- const accessRules = require("../accessRules.cjs");
5
- const redisRulesIndex = require("./redisRulesIndex.cjs");
6
- function _interopNamespaceDefault(e) {
7
- const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
8
- if (e) {
9
- for (const k in e) {
10
- if (k !== "default") {
11
- const d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: () => e[k]
15
- });
16
- }
17
- }
18
- }
19
- n.default = e;
20
- return Object.freeze(n);
21
- }
22
- const util__namespace = /* @__PURE__ */ _interopNamespaceDefault(util);
23
- const createRedisRulesReader = (client, logger) => {
24
- return {
25
- findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
26
- const query = redisRulesIndex.getRedisRulesQuery(filter, matchingFieldsOnly);
27
- if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
28
- return [];
29
- }
30
- let searchReply;
31
- try {
32
- searchReply = await client.ft.search(
33
- redisRulesIndex.redisRulesIndexName,
34
- query,
35
- redisRulesIndex.redisRulesSearchOptions
36
- );
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
- }
52
- } catch (e) {
53
- logger.error(() => ({
54
- err: e,
55
- data: {
56
- inspect: util__namespace.inspect(
57
- {
58
- query,
59
- filter
60
- },
61
- {
62
- depth: null
63
- }
64
- )
65
- },
66
- msg: "failed to execute search query"
67
- }));
68
- return [];
69
- }
70
- return extractRulesFromSearchReply(searchReply, logger);
71
- },
72
- findRuleIds: async (filter, matchingFieldsOnly = false) => {
73
- const query = redisRulesIndex.getRedisRulesQuery(filter, matchingFieldsOnly);
74
- let searchReply;
75
- try {
76
- searchReply = await client.ft.searchNoContent(
77
- redisRulesIndex.redisRulesIndexName,
78
- query,
79
- redisRulesIndex.redisRulesSearchOptions
80
- );
81
- } catch (e) {
82
- logger.error(() => ({
83
- err: e,
84
- data: {
85
- inspect: util__namespace.inspect(
86
- {
87
- query,
88
- filter
89
- },
90
- {
91
- depth: null
92
- }
93
- )
94
- },
95
- msg: "Failed to execute search query for rule IDs"
96
- }));
97
- return [];
98
- }
99
- return searchReply.documents;
100
- }
101
- };
102
- };
103
- const getDummyRedisRulesReader = (logger) => {
104
- return {
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
110
- }
111
- }));
112
- return [];
113
- },
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 [];
122
- }
123
- };
124
- };
125
- const extractRulesFromSearchReply = (searchReply, logger) => {
126
- const accessRules$1 = [];
127
- searchReply.documents.map(({ id, value: document }) => {
128
- const parsedDocument = accessRules.accessRuleSchema.safeParse(document);
129
- if (parsedDocument.success) {
130
- accessRules$1.push(parsedDocument.data);
131
- } else {
132
- logger.debug(() => ({
133
- msg: "Failed to parse access rule from search reply",
134
- id,
135
- error: parsedDocument.error
136
- }));
137
- }
138
- });
139
- return accessRules$1;
140
- };
141
- exports.createRedisRulesReader = createRedisRulesReader;
142
- exports.getDummyRedisRulesReader = getDummyRedisRulesReader;
package/dist/cjs/util.cjs DELETED
@@ -1,5 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const crypto = require("node:crypto");
4
- const hashUserAgent = (userAgent) => crypto.createHash("sha256").update(userAgent).digest("hex");
5
- exports.hashUserAgent = hashUserAgent;
package/dist/index.js DELETED
@@ -1,32 +0,0 @@
1
- import { AccessPolicyType, accessPolicySchema, accessRuleSchemaExtended, policyScopeSchema, userScopeInputSchema } from "./accessPolicy.js";
2
- import { ScopeMatch } from "./accessPolicyResolver.js";
3
- import { accessRuleSchema } from "./accessRules.js";
4
- import { AccessRuleApiRoutes } from "./api/accessRuleApiRoutes.js";
5
- import { accessRuleApiPaths, getExpressApiRuleRateLimits } from "./api/accessRuleApiRoutes.js";
6
- import { deleteAllRulesEndpointSchema } from "./api/deleteAllRulesEndpoint.js";
7
- import { deleteRulesEndpointSchema } from "./api/deleteRulesEndpoint.js";
8
- import { insertRulesEndpointSchema } from "./api/insertRulesEndpoint.js";
9
- import { createRedisAccessRulesStorage } from "./redis/redisRulesStorage.js";
10
- import { redisAccessRulesIndex } from "./redis/redisRulesIndex.js";
11
- import { AccessRulesApiClient } from "./api/accessRulesApiClient.js";
12
- const createApiRuleRoutesProvider = (rulesStorage) => {
13
- return new AccessRuleApiRoutes(rulesStorage);
14
- };
15
- export {
16
- AccessPolicyType,
17
- AccessRulesApiClient,
18
- ScopeMatch,
19
- accessPolicySchema,
20
- accessRuleApiPaths,
21
- accessRuleSchema,
22
- accessRuleSchemaExtended,
23
- createApiRuleRoutesProvider,
24
- createRedisAccessRulesStorage,
25
- deleteAllRulesEndpointSchema,
26
- deleteRulesEndpointSchema,
27
- getExpressApiRuleRateLimits,
28
- insertRulesEndpointSchema,
29
- policyScopeSchema,
30
- redisAccessRulesIndex,
31
- userScopeInputSchema
32
- };
@@ -1,138 +0,0 @@
1
- import { SCHEMA_FIELD_TYPE } from "@redis/search";
2
- import { userScopeSchema } from "../accessPolicy.js";
3
- import { ScopeMatch } from "../accessPolicyResolver.js";
4
- const redisRulesIndexName = "index:user-access-rules";
5
- const redisRuleKeyPrefix = "uar:";
6
- const redisAccessRulesIndex = {
7
- name: redisRulesIndexName,
8
- /**
9
- * Note on the field type decision
10
- *
11
- * TAG is designed for the exact value matching
12
- * TEXT is designed for the word-based and pattern matching
13
- *
14
- * For our goal TAG fits perfectly and, more performant
15
- */
16
- schema: {
17
- clientId: {
18
- type: SCHEMA_FIELD_TYPE.TAG,
19
- // necessary to make possible use of the ismissing() function on this field in the search
20
- INDEXMISSING: true
21
- },
22
- numericIpMaskMin: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
23
- numericIpMaskMax: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
24
- userId: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
25
- numericIp: { type: SCHEMA_FIELD_TYPE.NUMERIC, INDEXMISSING: true },
26
- ja4Hash: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
27
- headersHash: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true },
28
- userAgentHash: { type: SCHEMA_FIELD_TYPE.TAG, INDEXMISSING: true }
29
- },
30
- // the satisfy statement is to guarantee that the keys are right
31
- options: {
32
- ON: "HASH",
33
- PREFIX: [redisRuleKeyPrefix]
34
- }
35
- };
36
- const numericIndexFields = [
37
- "numericIp",
38
- "numericIpMaskMin",
39
- "numericIpMaskMax"
40
- ];
41
- const greedyFieldComparisons = {
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
- }
63
- };
64
- const redisRulesSearchOptions = {
65
- // #2 is a required option when the 'ismissing()' function is in the query body
66
- DIALECT: 2
67
- };
68
- const getRedisRulesQuery = (filter, matchingFieldsOnly) => {
69
- const { policyScope, userScope } = filter;
70
- const policyScopeFilter = getPolicyScopeQuery(
71
- policyScope,
72
- filter.policyScopeMatch
73
- );
74
- if (userScope && Object.keys(userScope).length > 0) {
75
- const userScopeFilter = getUserScopeQuery(
76
- userScope,
77
- filter.userScopeMatch,
78
- matchingFieldsOnly
79
- );
80
- return `${policyScopeFilter} ( ${userScopeFilter} )`;
81
- }
82
- return policyScopeFilter ? policyScopeFilter : "*";
83
- };
84
- const getPolicyScopeQuery = (policyScope, scopeMatchType) => {
85
- const clientId = policyScope?.clientId;
86
- if ("string" === typeof clientId) {
87
- return ScopeMatch.Exact === scopeMatchType ? `@clientId:{${clientId}}` : `( @clientId:{${clientId}} | ismissing(@clientId) )`;
88
- }
89
- return ScopeMatch.Exact === scopeMatchType ? "ismissing(@clientId)" : "";
90
- };
91
- const getUserScopeQuery = (userScope, scopeMatchType, matchingFieldsOnly) => {
92
- let scopeEntries = Object.entries(userScope);
93
- let scopeJoinType = " ";
94
- if (scopeMatchType === ScopeMatch.Greedy) {
95
- scopeEntries = scopeEntries.filter(
96
- ([_, value]) => value !== void 0
97
- );
98
- scopeJoinType = " | ";
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);
114
- return scopeEntries.map(
115
- ([scopeFieldName, scopeFieldValue]) => getUserScopeFieldQuery(
116
- scopeFieldName,
117
- scopeFieldValue,
118
- scopeMatchType,
119
- scopeObj
120
- )
121
- ).filter(Boolean).join(scopeJoinType);
122
- };
123
- const getUserScopeFieldQuery = (fieldName, fieldValue, matchType, fullScope) => {
124
- if ("function" === typeof greedyFieldComparisons[fieldName]) {
125
- return greedyFieldComparisons[fieldName](fieldValue, fullScope);
126
- }
127
- if (fieldValue === void 0) {
128
- return `ismissing(@${fieldName})`;
129
- }
130
- return numericIndexFields.includes(fieldName) ? `@${fieldName}:[${fieldValue}]` : `@${fieldName}:{${fieldValue}}`;
131
- };
132
- export {
133
- getRedisRulesQuery,
134
- redisAccessRulesIndex,
135
- redisRuleKeyPrefix,
136
- redisRulesIndexName,
137
- redisRulesSearchOptions
138
- };
@@ -1,125 +0,0 @@
1
- import * as util from "node:util";
2
- import { accessRuleSchema } from "../accessRules.js";
3
- import { getRedisRulesQuery, redisRulesIndexName, redisRulesSearchOptions } from "./redisRulesIndex.js";
4
- const createRedisRulesReader = (client, logger) => {
5
- return {
6
- findRules: async (filter, matchingFieldsOnly = false, skipEmptyUserScopes = true) => {
7
- const query = getRedisRulesQuery(filter, matchingFieldsOnly);
8
- if (skipEmptyUserScopes && query === "ismissing(@clientId)") {
9
- return [];
10
- }
11
- let searchReply;
12
- try {
13
- searchReply = await client.ft.search(
14
- redisRulesIndexName,
15
- query,
16
- redisRulesSearchOptions
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
- )
30
- }
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 extractRulesFromSearchReply(searchReply, logger);
52
- },
53
- findRuleIds: async (filter, matchingFieldsOnly = false) => {
54
- const query = getRedisRulesQuery(filter, matchingFieldsOnly);
55
- let searchReply;
56
- try {
57
- searchReply = await client.ft.searchNoContent(
58
- redisRulesIndexName,
59
- query,
60
- redisRulesSearchOptions
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
- };
83
- };
84
- const getDummyRedisRulesReader = (logger) => {
85
- return {
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
91
- }
92
- }));
93
- return [];
94
- },
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 [];
103
- }
104
- };
105
- };
106
- const extractRulesFromSearchReply = (searchReply, logger) => {
107
- const accessRules = [];
108
- searchReply.documents.map(({ id, value: document }) => {
109
- const parsedDocument = accessRuleSchema.safeParse(document);
110
- if (parsedDocument.success) {
111
- accessRules.push(parsedDocument.data);
112
- } else {
113
- logger.debug(() => ({
114
- msg: "Failed to parse access rule from search reply",
115
- id,
116
- error: parsedDocument.error
117
- }));
118
- }
119
- });
120
- return accessRules;
121
- };
122
- export {
123
- createRedisRulesReader,
124
- getDummyRedisRulesReader
125
- };
package/dist/util.js DELETED
@@ -1,5 +0,0 @@
1
- import crypto from "node:crypto";
2
- const hashUserAgent = (userAgent) => crypto.createHash("sha256").update(userAgent).digest("hex");
3
- export {
4
- hashUserAgent
5
- };