@liam-public/json-logic-whitelist 0.1.0
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/README.md +3 -0
- package/dist/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ALLOWED_OPS: () => ALLOWED_OPS,
|
|
34
|
+
ALLOWED_VARS: () => ALLOWED_VARS,
|
|
35
|
+
createJsonLogicWhitelist: () => createJsonLogicWhitelist,
|
|
36
|
+
evaluateRule: () => evaluateRule,
|
|
37
|
+
stripPolicyMarkers: () => stripPolicyMarkers,
|
|
38
|
+
validateRule: () => validateRule
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(index_exports);
|
|
41
|
+
var import_json_logic_js = __toESM(require("json-logic-js"), 1);
|
|
42
|
+
var defaultOperators = ["var", "==", "!=", "<", "<=", ">", ">=", "in", "and", "or", "!"];
|
|
43
|
+
var ALLOWED_OPS = defaultOperators;
|
|
44
|
+
var ALLOWED_VARS = [
|
|
45
|
+
"user.status",
|
|
46
|
+
"user.tier_app_singles",
|
|
47
|
+
"user.tier_app_doubles",
|
|
48
|
+
"user.tier_club_singles",
|
|
49
|
+
"user.tier_club_doubles",
|
|
50
|
+
"user.club_member_status",
|
|
51
|
+
"user.rating_app_singles",
|
|
52
|
+
"user.rating_app_doubles",
|
|
53
|
+
"user.rating_club_singles",
|
|
54
|
+
"user.rating_club_doubles",
|
|
55
|
+
"user.invite_list_member",
|
|
56
|
+
"club_members_only"
|
|
57
|
+
];
|
|
58
|
+
var POLICY_MARKERS = ["club_members_only", "invite_list"];
|
|
59
|
+
function createJsonLogicWhitelist(config) {
|
|
60
|
+
const operators = new Set(config.operators ?? defaultOperators);
|
|
61
|
+
const variables = new Set(config.variables);
|
|
62
|
+
const markers = config.markers ?? {};
|
|
63
|
+
const isValidNode = (node) => {
|
|
64
|
+
if (node === null || ["boolean", "number", "string"].includes(typeof node)) return true;
|
|
65
|
+
if (Array.isArray(node)) return node.every(isValidNode);
|
|
66
|
+
if (typeof node !== "object") return false;
|
|
67
|
+
const entries = Object.entries(node);
|
|
68
|
+
if (entries.length !== 1) return false;
|
|
69
|
+
const [operator, value] = entries[0];
|
|
70
|
+
if (!operators.has(operator)) return false;
|
|
71
|
+
if (operator === "var") return typeof value === "string" && variables.has(value);
|
|
72
|
+
return isValidNode(value);
|
|
73
|
+
};
|
|
74
|
+
const isValidTopLevel = (rule) => {
|
|
75
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return false;
|
|
76
|
+
let operationCount = 0;
|
|
77
|
+
for (const [key, value] of Object.entries(rule)) {
|
|
78
|
+
const marker = markers[key];
|
|
79
|
+
if (marker) {
|
|
80
|
+
if (!marker(value)) return false;
|
|
81
|
+
} else {
|
|
82
|
+
operationCount += 1;
|
|
83
|
+
if (operationCount > 1 || !isValidNode({ [key]: value })) return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
const stripMarkers = (rule) => {
|
|
89
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return rule;
|
|
90
|
+
return Object.fromEntries(
|
|
91
|
+
Object.entries(rule).filter(([key]) => !markers[key])
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
const validate = (rule) => {
|
|
95
|
+
if (!isValidTopLevel(rule)) throw new Error("JSON Logic rule contains an operator, variable, or marker that is not allowed");
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
validate,
|
|
99
|
+
stripMarkers,
|
|
100
|
+
evaluate(rule, context) {
|
|
101
|
+
validate(rule);
|
|
102
|
+
const logic = stripMarkers(rule);
|
|
103
|
+
if (logic && typeof logic === "object" && Object.keys(logic).length === 0) return true;
|
|
104
|
+
return Boolean(import_json_logic_js.default.apply(logic, context));
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
var legacyWhitelist = createJsonLogicWhitelist({
|
|
109
|
+
variables: ALLOWED_VARS,
|
|
110
|
+
operators: ALLOWED_OPS,
|
|
111
|
+
markers: {
|
|
112
|
+
club_members_only: (value) => typeof value === "boolean",
|
|
113
|
+
invite_list: (value) => Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
function validateRule(rule) {
|
|
117
|
+
legacyWhitelist.validate(rule);
|
|
118
|
+
}
|
|
119
|
+
function stripPolicyMarkers(rule) {
|
|
120
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return rule;
|
|
121
|
+
return Object.fromEntries(
|
|
122
|
+
Object.entries(rule).filter(
|
|
123
|
+
([key]) => !POLICY_MARKERS.includes(key)
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function evaluateRule(rule, context) {
|
|
128
|
+
validateRule(rule);
|
|
129
|
+
const stripped = stripPolicyMarkers(rule);
|
|
130
|
+
if (stripped && typeof stripped === "object" && !Array.isArray(stripped) && Object.keys(stripped).length === 0) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return legacyWhitelist.evaluate(stripped, context);
|
|
134
|
+
}
|
|
135
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
136
|
+
0 && (module.exports = {
|
|
137
|
+
ALLOWED_OPS,
|
|
138
|
+
ALLOWED_VARS,
|
|
139
|
+
createJsonLogicWhitelist,
|
|
140
|
+
evaluateRule,
|
|
141
|
+
stripPolicyMarkers,
|
|
142
|
+
validateRule
|
|
143
|
+
});
|
|
144
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import jsonLogic from 'json-logic-js'\n\nconst defaultOperators = ['var', '==', '!=', '<', '<=', '>', '>=', 'in', 'and', 'or', '!']\nexport const ALLOWED_OPS = defaultOperators\nexport const ALLOWED_VARS = [\n 'user.status',\n 'user.tier_app_singles',\n 'user.tier_app_doubles',\n 'user.tier_club_singles',\n 'user.tier_club_doubles',\n 'user.club_member_status',\n 'user.rating_app_singles',\n 'user.rating_app_doubles',\n 'user.rating_club_singles',\n 'user.rating_club_doubles',\n 'user.invite_list_member',\n 'club_members_only',\n] as const\nconst POLICY_MARKERS = ['club_members_only', 'invite_list'] as const\n\nexport interface JsonLogicWhitelist {\n validate(rule: unknown): void\n evaluate(rule: unknown, context: unknown): boolean\n stripMarkers(rule: unknown): unknown\n}\n\nexport function createJsonLogicWhitelist(config: {\n readonly variables: readonly string[]\n readonly operators?: readonly string[]\n readonly markers?: Readonly<Record<string, (value: unknown) => boolean>>\n}): JsonLogicWhitelist {\n const operators = new Set(config.operators ?? defaultOperators)\n const variables = new Set(config.variables)\n const markers = config.markers ?? {}\n\n const isValidNode = (node: unknown): boolean => {\n if (node === null || ['boolean', 'number', 'string'].includes(typeof node)) return true\n if (Array.isArray(node)) return node.every(isValidNode)\n if (typeof node !== 'object') return false\n const entries = Object.entries(node as Record<string, unknown>)\n if (entries.length !== 1) return false\n const [operator, value] = entries[0]!\n if (!operators.has(operator)) return false\n if (operator === 'var') return typeof value === 'string' && variables.has(value)\n return isValidNode(value)\n }\n\n const isValidTopLevel = (rule: unknown): boolean => {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return false\n let operationCount = 0\n for (const [key, value] of Object.entries(rule as Record<string, unknown>)) {\n const marker = markers[key]\n if (marker) {\n if (!marker(value)) return false\n } else {\n operationCount += 1\n if (operationCount > 1 || !isValidNode({ [key]: value })) return false\n }\n }\n return true\n }\n\n const stripMarkers = (rule: unknown): unknown => {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return rule\n return Object.fromEntries(\n Object.entries(rule as Record<string, unknown>).filter(([key]) => !markers[key]),\n )\n }\n\n const validate = (rule: unknown): void => {\n if (!isValidTopLevel(rule)) throw new Error('JSON Logic rule contains an operator, variable, or marker that is not allowed')\n }\n\n return {\n validate,\n stripMarkers,\n evaluate(rule, context) {\n validate(rule)\n const logic = stripMarkers(rule)\n if (logic && typeof logic === 'object' && Object.keys(logic).length === 0) return true\n return Boolean(jsonLogic.apply(logic, context))\n },\n }\n}\n\nconst legacyWhitelist = createJsonLogicWhitelist({\n variables: ALLOWED_VARS,\n operators: ALLOWED_OPS,\n markers: {\n club_members_only: (value) => typeof value === 'boolean',\n invite_list: (value) => Array.isArray(value) && value.every((item) => typeof item === 'string'),\n },\n})\n\nexport function validateRule(rule: unknown): void {\n legacyWhitelist.validate(rule)\n}\n\nexport function stripPolicyMarkers(rule: unknown): unknown {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return rule\n return Object.fromEntries(\n Object.entries(rule as Record<string, unknown>).filter(\n ([key]) => !(POLICY_MARKERS as readonly string[]).includes(key),\n ),\n )\n}\n\nexport function evaluateRule(rule: unknown, context: Record<string, unknown>): boolean {\n validateRule(rule)\n const stripped = stripPolicyMarkers(rule)\n if (\n stripped &&\n typeof stripped === 'object' &&\n !Array.isArray(stripped) &&\n Object.keys(stripped as Record<string, unknown>).length === 0\n ) {\n return true\n }\n return legacyWhitelist.evaluate(stripped, context)\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAsB;AAEtB,IAAM,mBAAmB,CAAC,OAAO,MAAM,MAAM,KAAK,MAAM,KAAK,MAAM,MAAM,OAAO,MAAM,GAAG;AAClF,IAAM,cAAc;AACpB,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,IAAM,iBAAiB,CAAC,qBAAqB,aAAa;AAQnD,SAAS,yBAAyB,QAIlB;AACrB,QAAM,YAAY,IAAI,IAAI,OAAO,aAAa,gBAAgB;AAC9D,QAAM,YAAY,IAAI,IAAI,OAAO,SAAS;AAC1C,QAAM,UAAU,OAAO,WAAW,CAAC;AAEnC,QAAM,cAAc,CAAC,SAA2B;AAC9C,QAAI,SAAS,QAAQ,CAAC,WAAW,UAAU,QAAQ,EAAE,SAAS,OAAO,IAAI,EAAG,QAAO;AACnF,QAAI,MAAM,QAAQ,IAAI,EAAG,QAAO,KAAK,MAAM,WAAW;AACtD,QAAI,OAAO,SAAS,SAAU,QAAO;AACrC,UAAM,UAAU,OAAO,QAAQ,IAA+B;AAC9D,QAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,UAAM,CAAC,UAAU,KAAK,IAAI,QAAQ,CAAC;AACnC,QAAI,CAAC,UAAU,IAAI,QAAQ,EAAG,QAAO;AACrC,QAAI,aAAa,MAAO,QAAO,OAAO,UAAU,YAAY,UAAU,IAAI,KAAK;AAC/E,WAAO,YAAY,KAAK;AAAA,EAC1B;AAEA,QAAM,kBAAkB,CAAC,SAA2B;AAClD,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,QAAO;AACrE,QAAI,iBAAiB;AACrB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAA+B,GAAG;AAC1E,YAAM,SAAS,QAAQ,GAAG;AAC1B,UAAI,QAAQ;AACV,YAAI,CAAC,OAAO,KAAK,EAAG,QAAO;AAAA,MAC7B,OAAO;AACL,0BAAkB;AAClB,YAAI,iBAAiB,KAAK,CAAC,YAAY,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,EAAG,QAAO;AAAA,MACnE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,CAAC,SAA2B;AAC/C,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,QAAO;AACrE,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,IAA+B,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,GAAG,CAAC;AAAA,IACjF;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,SAAwB;AACxC,QAAI,CAAC,gBAAgB,IAAI,EAAG,OAAM,IAAI,MAAM,+EAA+E;AAAA,EAC7H;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,MAAM,SAAS;AACtB,eAAS,IAAI;AACb,YAAM,QAAQ,aAAa,IAAI;AAC/B,UAAI,SAAS,OAAO,UAAU,YAAY,OAAO,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AAClF,aAAO,QAAQ,qBAAAA,QAAU,MAAM,OAAO,OAAO,CAAC;AAAA,IAChD;AAAA,EACF;AACF;AAEA,IAAM,kBAAkB,yBAAyB;AAAA,EAC/C,WAAW;AAAA,EACX,WAAW;AAAA,EACX,SAAS;AAAA,IACP,mBAAmB,CAAC,UAAU,OAAO,UAAU;AAAA,IAC/C,aAAa,CAAC,UAAU,MAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,CAAC,SAAS,OAAO,SAAS,QAAQ;AAAA,EAChG;AACF,CAAC;AAEM,SAAS,aAAa,MAAqB;AAChD,kBAAgB,SAAS,IAAI;AAC/B;AAEO,SAAS,mBAAmB,MAAwB;AACzD,MAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,QAAO;AACrE,SAAO,OAAO;AAAA,IACZ,OAAO,QAAQ,IAA+B,EAAE;AAAA,MAC9C,CAAC,CAAC,GAAG,MAAM,CAAE,eAAqC,SAAS,GAAG;AAAA,IAChE;AAAA,EACF;AACF;AAEO,SAAS,aAAa,MAAe,SAA2C;AACrF,eAAa,IAAI;AACjB,QAAM,WAAW,mBAAmB,IAAI;AACxC,MACE,YACA,OAAO,aAAa,YACpB,CAAC,MAAM,QAAQ,QAAQ,KACvB,OAAO,KAAK,QAAmC,EAAE,WAAW,GAC5D;AACA,WAAO;AAAA,EACT;AACA,SAAO,gBAAgB,SAAS,UAAU,OAAO;AACnD;","names":["jsonLogic"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const ALLOWED_OPS: string[];
|
|
2
|
+
declare const ALLOWED_VARS: readonly ["user.status", "user.tier_app_singles", "user.tier_app_doubles", "user.tier_club_singles", "user.tier_club_doubles", "user.club_member_status", "user.rating_app_singles", "user.rating_app_doubles", "user.rating_club_singles", "user.rating_club_doubles", "user.invite_list_member", "club_members_only"];
|
|
3
|
+
interface JsonLogicWhitelist {
|
|
4
|
+
validate(rule: unknown): void;
|
|
5
|
+
evaluate(rule: unknown, context: unknown): boolean;
|
|
6
|
+
stripMarkers(rule: unknown): unknown;
|
|
7
|
+
}
|
|
8
|
+
declare function createJsonLogicWhitelist(config: {
|
|
9
|
+
readonly variables: readonly string[];
|
|
10
|
+
readonly operators?: readonly string[];
|
|
11
|
+
readonly markers?: Readonly<Record<string, (value: unknown) => boolean>>;
|
|
12
|
+
}): JsonLogicWhitelist;
|
|
13
|
+
declare function validateRule(rule: unknown): void;
|
|
14
|
+
declare function stripPolicyMarkers(rule: unknown): unknown;
|
|
15
|
+
declare function evaluateRule(rule: unknown, context: Record<string, unknown>): boolean;
|
|
16
|
+
|
|
17
|
+
export { ALLOWED_OPS, ALLOWED_VARS, type JsonLogicWhitelist, createJsonLogicWhitelist, evaluateRule, stripPolicyMarkers, validateRule };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const ALLOWED_OPS: string[];
|
|
2
|
+
declare const ALLOWED_VARS: readonly ["user.status", "user.tier_app_singles", "user.tier_app_doubles", "user.tier_club_singles", "user.tier_club_doubles", "user.club_member_status", "user.rating_app_singles", "user.rating_app_doubles", "user.rating_club_singles", "user.rating_club_doubles", "user.invite_list_member", "club_members_only"];
|
|
3
|
+
interface JsonLogicWhitelist {
|
|
4
|
+
validate(rule: unknown): void;
|
|
5
|
+
evaluate(rule: unknown, context: unknown): boolean;
|
|
6
|
+
stripMarkers(rule: unknown): unknown;
|
|
7
|
+
}
|
|
8
|
+
declare function createJsonLogicWhitelist(config: {
|
|
9
|
+
readonly variables: readonly string[];
|
|
10
|
+
readonly operators?: readonly string[];
|
|
11
|
+
readonly markers?: Readonly<Record<string, (value: unknown) => boolean>>;
|
|
12
|
+
}): JsonLogicWhitelist;
|
|
13
|
+
declare function validateRule(rule: unknown): void;
|
|
14
|
+
declare function stripPolicyMarkers(rule: unknown): unknown;
|
|
15
|
+
declare function evaluateRule(rule: unknown, context: Record<string, unknown>): boolean;
|
|
16
|
+
|
|
17
|
+
export { ALLOWED_OPS, ALLOWED_VARS, type JsonLogicWhitelist, createJsonLogicWhitelist, evaluateRule, stripPolicyMarkers, validateRule };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import jsonLogic from "json-logic-js";
|
|
3
|
+
var defaultOperators = ["var", "==", "!=", "<", "<=", ">", ">=", "in", "and", "or", "!"];
|
|
4
|
+
var ALLOWED_OPS = defaultOperators;
|
|
5
|
+
var ALLOWED_VARS = [
|
|
6
|
+
"user.status",
|
|
7
|
+
"user.tier_app_singles",
|
|
8
|
+
"user.tier_app_doubles",
|
|
9
|
+
"user.tier_club_singles",
|
|
10
|
+
"user.tier_club_doubles",
|
|
11
|
+
"user.club_member_status",
|
|
12
|
+
"user.rating_app_singles",
|
|
13
|
+
"user.rating_app_doubles",
|
|
14
|
+
"user.rating_club_singles",
|
|
15
|
+
"user.rating_club_doubles",
|
|
16
|
+
"user.invite_list_member",
|
|
17
|
+
"club_members_only"
|
|
18
|
+
];
|
|
19
|
+
var POLICY_MARKERS = ["club_members_only", "invite_list"];
|
|
20
|
+
function createJsonLogicWhitelist(config) {
|
|
21
|
+
const operators = new Set(config.operators ?? defaultOperators);
|
|
22
|
+
const variables = new Set(config.variables);
|
|
23
|
+
const markers = config.markers ?? {};
|
|
24
|
+
const isValidNode = (node) => {
|
|
25
|
+
if (node === null || ["boolean", "number", "string"].includes(typeof node)) return true;
|
|
26
|
+
if (Array.isArray(node)) return node.every(isValidNode);
|
|
27
|
+
if (typeof node !== "object") return false;
|
|
28
|
+
const entries = Object.entries(node);
|
|
29
|
+
if (entries.length !== 1) return false;
|
|
30
|
+
const [operator, value] = entries[0];
|
|
31
|
+
if (!operators.has(operator)) return false;
|
|
32
|
+
if (operator === "var") return typeof value === "string" && variables.has(value);
|
|
33
|
+
return isValidNode(value);
|
|
34
|
+
};
|
|
35
|
+
const isValidTopLevel = (rule) => {
|
|
36
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return false;
|
|
37
|
+
let operationCount = 0;
|
|
38
|
+
for (const [key, value] of Object.entries(rule)) {
|
|
39
|
+
const marker = markers[key];
|
|
40
|
+
if (marker) {
|
|
41
|
+
if (!marker(value)) return false;
|
|
42
|
+
} else {
|
|
43
|
+
operationCount += 1;
|
|
44
|
+
if (operationCount > 1 || !isValidNode({ [key]: value })) return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
const stripMarkers = (rule) => {
|
|
50
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return rule;
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
Object.entries(rule).filter(([key]) => !markers[key])
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
const validate = (rule) => {
|
|
56
|
+
if (!isValidTopLevel(rule)) throw new Error("JSON Logic rule contains an operator, variable, or marker that is not allowed");
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
validate,
|
|
60
|
+
stripMarkers,
|
|
61
|
+
evaluate(rule, context) {
|
|
62
|
+
validate(rule);
|
|
63
|
+
const logic = stripMarkers(rule);
|
|
64
|
+
if (logic && typeof logic === "object" && Object.keys(logic).length === 0) return true;
|
|
65
|
+
return Boolean(jsonLogic.apply(logic, context));
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var legacyWhitelist = createJsonLogicWhitelist({
|
|
70
|
+
variables: ALLOWED_VARS,
|
|
71
|
+
operators: ALLOWED_OPS,
|
|
72
|
+
markers: {
|
|
73
|
+
club_members_only: (value) => typeof value === "boolean",
|
|
74
|
+
invite_list: (value) => Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
function validateRule(rule) {
|
|
78
|
+
legacyWhitelist.validate(rule);
|
|
79
|
+
}
|
|
80
|
+
function stripPolicyMarkers(rule) {
|
|
81
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return rule;
|
|
82
|
+
return Object.fromEntries(
|
|
83
|
+
Object.entries(rule).filter(
|
|
84
|
+
([key]) => !POLICY_MARKERS.includes(key)
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function evaluateRule(rule, context) {
|
|
89
|
+
validateRule(rule);
|
|
90
|
+
const stripped = stripPolicyMarkers(rule);
|
|
91
|
+
if (stripped && typeof stripped === "object" && !Array.isArray(stripped) && Object.keys(stripped).length === 0) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return legacyWhitelist.evaluate(stripped, context);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
ALLOWED_OPS,
|
|
98
|
+
ALLOWED_VARS,
|
|
99
|
+
createJsonLogicWhitelist,
|
|
100
|
+
evaluateRule,
|
|
101
|
+
stripPolicyMarkers,
|
|
102
|
+
validateRule
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import jsonLogic from 'json-logic-js'\n\nconst defaultOperators = ['var', '==', '!=', '<', '<=', '>', '>=', 'in', 'and', 'or', '!']\nexport const ALLOWED_OPS = defaultOperators\nexport const ALLOWED_VARS = [\n 'user.status',\n 'user.tier_app_singles',\n 'user.tier_app_doubles',\n 'user.tier_club_singles',\n 'user.tier_club_doubles',\n 'user.club_member_status',\n 'user.rating_app_singles',\n 'user.rating_app_doubles',\n 'user.rating_club_singles',\n 'user.rating_club_doubles',\n 'user.invite_list_member',\n 'club_members_only',\n] as const\nconst POLICY_MARKERS = ['club_members_only', 'invite_list'] as const\n\nexport interface JsonLogicWhitelist {\n validate(rule: unknown): void\n evaluate(rule: unknown, context: unknown): boolean\n stripMarkers(rule: unknown): unknown\n}\n\nexport function createJsonLogicWhitelist(config: {\n readonly variables: readonly string[]\n readonly operators?: readonly string[]\n readonly markers?: Readonly<Record<string, (value: unknown) => boolean>>\n}): JsonLogicWhitelist {\n const operators = new Set(config.operators ?? defaultOperators)\n const variables = new Set(config.variables)\n const markers = config.markers ?? {}\n\n const isValidNode = (node: unknown): boolean => {\n if (node === null || ['boolean', 'number', 'string'].includes(typeof node)) return true\n if (Array.isArray(node)) return node.every(isValidNode)\n if (typeof node !== 'object') return false\n const entries = Object.entries(node as Record<string, unknown>)\n if (entries.length !== 1) return false\n const [operator, value] = entries[0]!\n if (!operators.has(operator)) return false\n if (operator === 'var') return typeof value === 'string' && variables.has(value)\n return isValidNode(value)\n }\n\n const isValidTopLevel = (rule: unknown): boolean => {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return false\n let operationCount = 0\n for (const [key, value] of Object.entries(rule as Record<string, unknown>)) {\n const marker = markers[key]\n if (marker) {\n if (!marker(value)) return false\n } else {\n operationCount += 1\n if (operationCount > 1 || !isValidNode({ [key]: value })) return false\n }\n }\n return true\n }\n\n const stripMarkers = (rule: unknown): unknown => {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return rule\n return Object.fromEntries(\n Object.entries(rule as Record<string, unknown>).filter(([key]) => !markers[key]),\n )\n }\n\n const validate = (rule: unknown): void => {\n if (!isValidTopLevel(rule)) throw new Error('JSON Logic rule contains an operator, variable, or marker that is not allowed')\n }\n\n return {\n validate,\n stripMarkers,\n evaluate(rule, context) {\n validate(rule)\n const logic = stripMarkers(rule)\n if (logic && typeof logic === 'object' && Object.keys(logic).length === 0) return true\n return Boolean(jsonLogic.apply(logic, context))\n },\n }\n}\n\nconst legacyWhitelist = createJsonLogicWhitelist({\n variables: ALLOWED_VARS,\n operators: ALLOWED_OPS,\n markers: {\n club_members_only: (value) => typeof value === 'boolean',\n invite_list: (value) => Array.isArray(value) && value.every((item) => typeof item === 'string'),\n },\n})\n\nexport function validateRule(rule: unknown): void {\n legacyWhitelist.validate(rule)\n}\n\nexport function stripPolicyMarkers(rule: unknown): unknown {\n if (!rule || typeof rule !== 'object' || Array.isArray(rule)) return rule\n return Object.fromEntries(\n Object.entries(rule as Record<string, unknown>).filter(\n ([key]) => !(POLICY_MARKERS as readonly string[]).includes(key),\n ),\n )\n}\n\nexport function evaluateRule(rule: unknown, context: Record<string, unknown>): boolean {\n validateRule(rule)\n const stripped = stripPolicyMarkers(rule)\n if (\n stripped &&\n typeof stripped === 'object' &&\n !Array.isArray(stripped) &&\n Object.keys(stripped as Record<string, unknown>).length === 0\n ) {\n return true\n }\n return legacyWhitelist.evaluate(stripped, context)\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liam-public/json-logic-whitelist",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Configurable JSON Logic operator and variable whitelist.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"liamCompatibility": {
|
|
7
|
+
"runtime": [
|
|
8
|
+
"universal"
|
|
9
|
+
],
|
|
10
|
+
"framework": [
|
|
11
|
+
"agnostic"
|
|
12
|
+
]
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org/"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"json-logic-js": "^2.0.2"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsup src/index.ts --format esm --dts --sourcemap",
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"typecheck": "tsc --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|