@wxn0brp/vql 0.5.1 → 0.6.0-alpha.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.
- package/README.md +2 -27
- package/dist/config.d.ts +0 -2
- package/dist/config.js +4 -5
- package/dist/cpu/relation.js +1 -1
- package/dist/cpu/request.js +1 -1
- package/dist/cpu/string/index.d.ts +1 -7
- package/dist/cpu/string/index.js +140 -43
- package/dist/cpu/string/utils.d.ts +1 -0
- package/dist/cpu/string/utils.js +14 -0
- package/dist/falconFrame.js +2 -2
- package/dist/gw.d.ts +3 -0
- package/dist/gw.js +5 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/permissions/relation.d.ts +2 -2
- package/dist/permissions/relation.js +8 -7
- package/dist/permissions/request.d.ts +4 -5
- package/dist/permissions/request.js +8 -7
- package/dist/permissions/utils.d.ts +2 -1
- package/dist/permissions/utils.js +15 -3
- package/dist/processor.d.ts +8 -9
- package/dist/processor.js +12 -13
- package/dist/sheet.d.ts +2 -0
- package/dist/sheet.js +34 -0
- package/dist/types/perm.d.ts +5 -0
- package/dist/types/vql.d.ts +4 -13
- package/dist/valid.d.ts +2 -5
- package/dist/valid.js +55 -33
- package/dist/vql.d.ts +4 -13
- package/package.json +10 -12
- package/dist/ajv.d.ts +0 -2
- package/dist/ajv.js +0 -80
- package/dist/cpu/string/json5.d.ts +0 -2
- package/dist/cpu/string/json5.js +0 -8
- package/dist/cpu/string/middle.d.ts +0 -3
- package/dist/cpu/string/middle.js +0 -41
- package/dist/cpu/string/simple.d.ts +0 -2
- package/dist/cpu/string/simple.js +0 -155
- package/dist/cpu/string/yaml.d.ts +0 -2
- package/dist/cpu/string/yaml.js +0 -19
- package/dist/schema.json +0 -1
- package/dist/sheet/index.d.ts +0 -2
- package/dist/sheet/index.js +0 -70
- package/dist/sheet/load.d.ts +0 -3
- package/dist/sheet/load.js +0 -22
package/README.md
CHANGED
|
@@ -20,18 +20,14 @@ Here is an example of how to use the `VQLProcessor` to execute a query:
|
|
|
20
20
|
```typescript
|
|
21
21
|
import VQLProcessor from "@wxn0brp/vql";
|
|
22
22
|
import { Valthera } from "@wxn0brp/db";
|
|
23
|
-
import { GateWarden } from "@wxn0brp/gate-warden";
|
|
24
23
|
|
|
25
24
|
// Initialize database instances
|
|
26
25
|
const dbInstances = {
|
|
27
26
|
myDatabase: new Valthera("path/to/database"),
|
|
28
27
|
};
|
|
29
28
|
|
|
30
|
-
// Initialize Gate Warden
|
|
31
|
-
const gw = new GateWarden("path/to/gate-warden/database");
|
|
32
|
-
|
|
33
29
|
// Create a VQLProcessor instance
|
|
34
|
-
const processor = new VQLProcessor(dbInstances
|
|
30
|
+
const processor = new VQLProcessor(dbInstances);
|
|
35
31
|
|
|
36
32
|
// Define a query (VQLR)
|
|
37
33
|
const query = {
|
|
@@ -53,31 +49,11 @@ s.$gt.age = 18
|
|
|
53
49
|
f.name = 1
|
|
54
50
|
f.age = 1
|
|
55
51
|
`
|
|
56
|
-
// Or (use backticks like json)
|
|
57
|
-
const VQLB = `
|
|
58
|
-
myDatabase users
|
|
59
|
-
{
|
|
60
|
-
collection: "users",
|
|
61
|
-
search: { $gt: { age: 18 } },
|
|
62
|
-
fields: { name: 1, age: 1 },
|
|
63
|
-
}
|
|
64
|
-
`
|
|
65
|
-
// Or (use markup like yaml)
|
|
66
|
-
const VQLM = `
|
|
67
|
-
myDatabase users
|
|
68
|
-
collection: users
|
|
69
|
-
search:
|
|
70
|
-
$gt:
|
|
71
|
-
age: 18
|
|
72
|
-
fields:
|
|
73
|
-
name: 1
|
|
74
|
-
age: 1
|
|
75
|
-
`
|
|
76
52
|
|
|
77
53
|
// Execute the query
|
|
78
54
|
(async () => {
|
|
79
55
|
try {
|
|
80
|
-
const result = await processor.execute(query
|
|
56
|
+
const result = await processor.execute(query);
|
|
81
57
|
console.log("Query Result:", result);
|
|
82
58
|
} catch (error) {
|
|
83
59
|
console.error("Error executing query:", error);
|
|
@@ -89,7 +65,6 @@ fields:
|
|
|
89
65
|
|
|
90
66
|
- [Base/Map](./docs/lang/base.md)
|
|
91
67
|
- [VQLS](./docs/lang/VQLS.md)
|
|
92
|
-
- [VQLM/B](./docs/lang/VQLM.md)
|
|
93
68
|
- [VQLR](./docs/lang/VQLR.md)
|
|
94
69
|
|
|
95
70
|
## License
|
package/dist/config.d.ts
CHANGED
|
@@ -3,13 +3,11 @@ export interface VQLConfigInterface {
|
|
|
3
3
|
strictSelect: boolean;
|
|
4
4
|
strictACL: boolean;
|
|
5
5
|
noCheckPermissions: boolean;
|
|
6
|
-
formatAjv: boolean;
|
|
7
6
|
}
|
|
8
7
|
export declare class VQLConfig implements VQLConfigInterface {
|
|
9
8
|
hidePath: boolean;
|
|
10
9
|
strictSelect: boolean;
|
|
11
10
|
strictACL: boolean;
|
|
12
11
|
noCheckPermissions: boolean;
|
|
13
|
-
formatAjv: boolean;
|
|
14
12
|
constructor(config?: Partial<VQLConfigInterface>);
|
|
15
13
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
export class VQLConfig {
|
|
2
|
-
hidePath =
|
|
3
|
-
strictSelect =
|
|
4
|
-
strictACL =
|
|
5
|
-
noCheckPermissions =
|
|
6
|
-
formatAjv = true;
|
|
2
|
+
hidePath = false;
|
|
3
|
+
strictSelect = false;
|
|
4
|
+
strictACL = false;
|
|
5
|
+
noCheckPermissions = true;
|
|
7
6
|
constructor(config) {
|
|
8
7
|
if (config) {
|
|
9
8
|
Object.assign(this, config);
|
package/dist/cpu/relation.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function executeRelation(cpu, query, user) {
|
|
|
21
21
|
const checkDb = checkDBsExist(cpu, query.r);
|
|
22
22
|
if (checkDb.err)
|
|
23
23
|
return checkDb;
|
|
24
|
-
if (!cpu.config.noCheckPermissions && !await checkRelationPermission(cpu.config, cpu.
|
|
24
|
+
if (!cpu.config.noCheckPermissions && !await checkRelationPermission(cpu.config, cpu.validFn, user, query)) {
|
|
25
25
|
return { err: true, msg: "Permission denied", c: 403 };
|
|
26
26
|
}
|
|
27
27
|
const req = query.r;
|
package/dist/cpu/request.js
CHANGED
|
@@ -5,7 +5,7 @@ export async function executeQuery(cpu, query, user) {
|
|
|
5
5
|
return { err: true, msg: `Invalid query - db "${query.db || "undefined"}" not found`, c: 400 };
|
|
6
6
|
const db = cpu.dbInstances[query.db];
|
|
7
7
|
const operation = Object.keys(query.d)[0];
|
|
8
|
-
if (!cpu.config.noCheckPermissions && !await checkRequestPermission(cpu.config, cpu.
|
|
8
|
+
if (!cpu.config.noCheckPermissions && !await checkRequestPermission(cpu.config, cpu.validFn, user, query)) {
|
|
9
9
|
return { err: true, msg: "Permission denied", c: 403 };
|
|
10
10
|
}
|
|
11
11
|
if (operation === "find") {
|
|
@@ -1,8 +1,2 @@
|
|
|
1
1
|
import { VQL } from "../../types/vql.js";
|
|
2
|
-
export
|
|
3
|
-
export declare function guessParser(query: string): {
|
|
4
|
-
mode: VQLParserMode;
|
|
5
|
-
query: string;
|
|
6
|
-
};
|
|
7
|
-
declare function parseStringQuery(query: string): VQL;
|
|
8
|
-
export { parseStringQuery };
|
|
2
|
+
export declare function parseVQLS(query: string): VQL;
|
package/dist/cpu/string/index.js
CHANGED
|
@@ -1,56 +1,153 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { convertSearchObjToSearchArray, extractMeta } from "./utils.js";
|
|
2
|
+
const aliases = {
|
|
3
|
+
s: "search",
|
|
4
|
+
f: "fields",
|
|
5
|
+
o: "options",
|
|
6
|
+
r: "relations",
|
|
7
|
+
d: "data",
|
|
8
|
+
e: "select",
|
|
9
|
+
u: "updater",
|
|
10
|
+
};
|
|
11
|
+
function parseArgs(input) {
|
|
12
|
+
const result = {};
|
|
13
|
+
const tokens = [];
|
|
14
|
+
let current = "";
|
|
15
|
+
let inQuotes = false;
|
|
16
|
+
let escape = false;
|
|
17
|
+
for (let i = 0; i < input.length; i++) {
|
|
18
|
+
const char = input[i];
|
|
19
|
+
if (escape) {
|
|
20
|
+
current += char;
|
|
21
|
+
escape = false;
|
|
22
|
+
}
|
|
23
|
+
else if (char === "\\") {
|
|
24
|
+
escape = true;
|
|
25
|
+
}
|
|
26
|
+
else if (char === "\"") {
|
|
27
|
+
inQuotes = !inQuotes;
|
|
28
|
+
}
|
|
29
|
+
else if (!inQuotes && (char === " " || char === "=")) {
|
|
30
|
+
if (current !== "") {
|
|
31
|
+
tokens.push(current);
|
|
32
|
+
current = "";
|
|
33
|
+
}
|
|
15
34
|
}
|
|
16
35
|
else {
|
|
17
|
-
|
|
36
|
+
current += char;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (current !== "") {
|
|
40
|
+
tokens.push(current);
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < tokens.length; i += 2) {
|
|
43
|
+
const key = tokens[i];
|
|
44
|
+
let value = tokens[i + 1] ?? true;
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
if (trimmed === "") {
|
|
48
|
+
value = true;
|
|
49
|
+
}
|
|
50
|
+
else if (/^".*"$/.test(trimmed)) {
|
|
51
|
+
value = trimmed.slice(1, -1);
|
|
52
|
+
}
|
|
53
|
+
else if (trimmed.toLowerCase() === "true") {
|
|
54
|
+
value = true;
|
|
55
|
+
}
|
|
56
|
+
else if (trimmed.toLowerCase() === "false") {
|
|
57
|
+
value = false;
|
|
58
|
+
}
|
|
59
|
+
else if (!isNaN(Number(trimmed))) {
|
|
60
|
+
value = Number(trimmed);
|
|
61
|
+
}
|
|
62
|
+
else if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
63
|
+
try {
|
|
64
|
+
value = JSON.parse(trimmed);
|
|
65
|
+
}
|
|
66
|
+
catch { }
|
|
67
|
+
}
|
|
18
68
|
}
|
|
69
|
+
result[key] = value;
|
|
19
70
|
}
|
|
20
|
-
|
|
21
|
-
words.push(word);
|
|
22
|
-
return words[2] + (words[3] ? " " + words[3] : "");
|
|
71
|
+
return result;
|
|
23
72
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (
|
|
73
|
+
function buildVQL(db, op, collection, query) {
|
|
74
|
+
const hasRelations = "relations" in query;
|
|
75
|
+
if (hasRelations) {
|
|
76
|
+
const relations = {};
|
|
77
|
+
for (const key in query.relations) {
|
|
78
|
+
const value = query.relations[key];
|
|
79
|
+
relations[key] = {
|
|
80
|
+
path: [value.db || db, value.c || key],
|
|
81
|
+
...value
|
|
82
|
+
};
|
|
83
|
+
delete relations[key].db;
|
|
84
|
+
delete relations[key].c;
|
|
85
|
+
}
|
|
86
|
+
if ("select" in query) {
|
|
87
|
+
query.select = convertSearchObjToSearchArray(query.select);
|
|
88
|
+
}
|
|
27
89
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
90
|
+
r: {
|
|
91
|
+
path: [db, collection],
|
|
92
|
+
...query,
|
|
93
|
+
relations,
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
if (query.fields && !query.select) {
|
|
99
|
+
query.select = query.fields;
|
|
100
|
+
delete query.fields;
|
|
101
|
+
}
|
|
102
|
+
if ("select" in query) {
|
|
103
|
+
query.select = [...new Set(convertSearchObjToSearchArray(query.select).map(k => k[0]).flat())];
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
db,
|
|
107
|
+
d: {
|
|
108
|
+
[op]: {
|
|
109
|
+
collection,
|
|
110
|
+
...query,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
30
113
|
};
|
|
31
114
|
}
|
|
32
|
-
const _34word = get3rdAnd4thWord(query);
|
|
33
|
-
let mode = "VQLS";
|
|
34
|
-
if (_34word.includes("{"))
|
|
35
|
-
mode = "VQLB";
|
|
36
|
-
else if (_34word.includes(":"))
|
|
37
|
-
mode = "VQLM";
|
|
38
|
-
return {
|
|
39
|
-
mode,
|
|
40
|
-
query
|
|
41
|
-
};
|
|
42
115
|
}
|
|
43
|
-
function
|
|
44
|
-
const {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
116
|
+
export function parseVQLS(query) {
|
|
117
|
+
const { db, op, collection, body } = extractMeta(query);
|
|
118
|
+
const parsed = parseArgs(body);
|
|
119
|
+
for (const keysRaw of Object.keys(parsed)) {
|
|
120
|
+
const keys = keysRaw.split(".");
|
|
121
|
+
if (keys.length === 1) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
let obj = parsed;
|
|
125
|
+
for (let i = 0; i < keys.length; i++) {
|
|
126
|
+
const key = keys[i];
|
|
127
|
+
if (i < keys.length - 1) {
|
|
128
|
+
if (!(key in obj)) {
|
|
129
|
+
obj[key] = {};
|
|
130
|
+
}
|
|
131
|
+
obj = obj[key];
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
obj[key] = parsed[keysRaw];
|
|
135
|
+
delete parsed[keysRaw];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const key in aliases) {
|
|
140
|
+
if (key in parsed) {
|
|
141
|
+
parsed[aliases[key]] = parsed[key];
|
|
142
|
+
delete parsed[key];
|
|
143
|
+
}
|
|
48
144
|
}
|
|
49
|
-
|
|
50
|
-
|
|
145
|
+
if ((op === "find" || op === "findOne") && !("search" in parsed)) {
|
|
146
|
+
parsed.search = {};
|
|
51
147
|
}
|
|
52
|
-
|
|
53
|
-
|
|
148
|
+
if ((op === "update" || op === "remove") && !("updater" in parsed) && ("data" in parsed)) {
|
|
149
|
+
parsed.updater = parsed.data;
|
|
150
|
+
delete parsed.data;
|
|
54
151
|
}
|
|
152
|
+
return buildVQL(db, op, collection, parsed);
|
|
55
153
|
}
|
|
56
|
-
export { parseStringQuery };
|
package/dist/cpu/string/utils.js
CHANGED
|
@@ -87,3 +87,17 @@ function extendedCollectionToData(collection) {
|
|
|
87
87
|
}
|
|
88
88
|
return { op, collection: collectionName };
|
|
89
89
|
}
|
|
90
|
+
export function convertSearchObjToSearchArray(obj, parentKeys = []) {
|
|
91
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
92
|
+
const currentPath = [...parentKeys, key];
|
|
93
|
+
if (!value) {
|
|
94
|
+
return acc;
|
|
95
|
+
}
|
|
96
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
97
|
+
return [...acc, ...convertSearchObjToSearchArray(value, currentPath)];
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return [...acc, currentPath];
|
|
101
|
+
}
|
|
102
|
+
}, []);
|
|
103
|
+
}
|
package/dist/falconFrame.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseVQLS } from "./cpu/string/index.js";
|
|
2
2
|
export function FF_VQL(app, processor, options = {}) {
|
|
3
3
|
const path = options.path || "/VQL";
|
|
4
4
|
const getContext = options.getUser || (() => ({}));
|
|
@@ -18,7 +18,7 @@ export function FF_VQL(app, processor, options = {}) {
|
|
|
18
18
|
if (options.dev) {
|
|
19
19
|
app.get(path + "-query", (req, res) => {
|
|
20
20
|
try {
|
|
21
|
-
return res.json(
|
|
21
|
+
return res.json(parseVQLS(req.query?.query || ""));
|
|
22
22
|
}
|
|
23
23
|
catch (e) {
|
|
24
24
|
res.status(500);
|
package/dist/gw.d.ts
ADDED
package/dist/gw.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ import { createValtheraAdapter } from "./apiAbstract.js";
|
|
|
4
4
|
import logger from "./logger.js";
|
|
5
5
|
import { LogLevel } from "@wxn0brp/lucerna-log";
|
|
6
6
|
import { FF_VQL } from "./falconFrame.js";
|
|
7
|
+
import { createGwValidFn } from "./gw.js";
|
|
7
8
|
export default VQLProcessor;
|
|
8
|
-
export { createValtheraAdapter, VQLConfig, logger as VQLLogger, LogLevel as VQLLogLevel, FF_VQL };
|
|
9
|
-
export * as VQLSheet from "./sheet/load.js";
|
|
9
|
+
export { VQLProcessor, createValtheraAdapter, VQLConfig, logger as VQLLogger, LogLevel as VQLLogLevel, FF_VQL, createGwValidFn };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,6 @@ import { createValtheraAdapter } from "./apiAbstract.js";
|
|
|
4
4
|
import logger from "./logger.js";
|
|
5
5
|
import { LogLevel } from "@wxn0brp/lucerna-log";
|
|
6
6
|
import { FF_VQL } from "./falconFrame.js";
|
|
7
|
+
import { createGwValidFn } from "./gw.js";
|
|
7
8
|
export default VQLProcessor;
|
|
8
|
-
export { createValtheraAdapter, VQLConfig, logger as VQLLogger, LogLevel as VQLLogLevel, FF_VQL };
|
|
9
|
-
export * as VQLSheet from "./sheet/load.js";
|
|
9
|
+
export { VQLProcessor, createValtheraAdapter, VQLConfig, logger as VQLLogger, LogLevel as VQLLogLevel, FF_VQL, createGwValidFn };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValidFn } from "../types/perm.js";
|
|
2
2
|
import { RelationQuery } from "../types/vql.js";
|
|
3
3
|
import { VQLConfig } from "../config.js";
|
|
4
|
-
export declare function checkRelationPermission(config: VQLConfig,
|
|
4
|
+
export declare function checkRelationPermission(config: VQLConfig, validFn: ValidFn, user: any, query: RelationQuery): Promise<boolean>;
|
|
@@ -1,31 +1,32 @@
|
|
|
1
1
|
import { PermCRUD } from "../types/perm.js";
|
|
2
2
|
import { extractPathsFromData, hashKey } from "./utils.js";
|
|
3
|
-
export async function checkRelationPermission(config,
|
|
3
|
+
export async function checkRelationPermission(config, validFn, user, query) {
|
|
4
4
|
const { path, search, relations, select } = query.r;
|
|
5
5
|
// Helper function to recursively check permissions with fallback mechanism
|
|
6
6
|
const checkPermissionRecursively = async (entityId, fallbackLevels = []) => {
|
|
7
7
|
// Check if the user has access to the current entity
|
|
8
|
-
const result = await gw.hasAccess(user.id, entityId, PermCRUD.READ);
|
|
8
|
+
// const result = await gw.hasAccess(user.id, entityId, PermCRUD.READ);
|
|
9
|
+
const result = await validFn(entityId, PermCRUD.READ, user);
|
|
9
10
|
if (result.granted) {
|
|
10
11
|
return true;
|
|
11
12
|
}
|
|
12
13
|
// If the result is "entity-404", check the next fallback level
|
|
13
14
|
if (!config.strictACL && result.via === "entity-404" && fallbackLevels.length > 0) {
|
|
14
|
-
const nextFallbackEntityId = hashKey(config, fallbackLevels.slice(0, -1));
|
|
15
|
+
const nextFallbackEntityId = await hashKey(config, fallbackLevels.slice(0, -1));
|
|
15
16
|
return checkPermissionRecursively(nextFallbackEntityId, fallbackLevels.slice(0, -2));
|
|
16
17
|
}
|
|
17
18
|
// If no fallback levels are left or the result is not "entity-404", deny access
|
|
18
19
|
return false;
|
|
19
20
|
};
|
|
20
21
|
// Check permission for the relation field in the parent collection
|
|
21
|
-
if (!await checkPermissionRecursively(hashKey(config, path), path)) {
|
|
22
|
+
if (!await checkPermissionRecursively(await hashKey(config, path), path)) {
|
|
22
23
|
return false;
|
|
23
24
|
}
|
|
24
25
|
// Check permissions for search fields
|
|
25
26
|
const searchPaths = extractPathsFromData(search || {});
|
|
26
27
|
for (const searchPath of searchPaths) {
|
|
27
28
|
const key = [...path, ...searchPath.path, searchPath.key];
|
|
28
|
-
if (!await checkPermissionRecursively(hashKey(config, key), key)) {
|
|
29
|
+
if (!await checkPermissionRecursively(await hashKey(config, key), key)) {
|
|
29
30
|
return false;
|
|
30
31
|
}
|
|
31
32
|
}
|
|
@@ -33,7 +34,7 @@ export async function checkRelationPermission(config, gw, user, query) {
|
|
|
33
34
|
if (select) {
|
|
34
35
|
for (const fieldPath of select) {
|
|
35
36
|
const key = [...path, fieldPath];
|
|
36
|
-
if (!await checkPermissionRecursively(hashKey(config, key), key)) {
|
|
37
|
+
if (!await checkPermissionRecursively(await hashKey(config, key), key)) {
|
|
37
38
|
return false;
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -42,7 +43,7 @@ export async function checkRelationPermission(config, gw, user, query) {
|
|
|
42
43
|
if (relations) {
|
|
43
44
|
for (const relationKey in relations) {
|
|
44
45
|
const r = relations[relationKey];
|
|
45
|
-
if (!await checkRelationPermission(config,
|
|
46
|
+
if (!await checkRelationPermission(config, validFn, user, { r })) {
|
|
46
47
|
return false;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PermCRUD } from "../types/perm.js";
|
|
1
|
+
import { PermCRUD, ValidFn } from "../types/perm.js";
|
|
3
2
|
import { VQLRequest } from "../types/vql.js";
|
|
4
3
|
import { VQLConfig } from "../config.js";
|
|
5
|
-
export declare function extractPaths(config: VQLConfig, query: VQLRequest): {
|
|
4
|
+
export declare function extractPaths(config: VQLConfig, query: VQLRequest): Promise<{
|
|
6
5
|
db: string;
|
|
7
6
|
c: string;
|
|
8
7
|
paths: {
|
|
@@ -11,9 +10,9 @@ export declare function extractPaths(config: VQLConfig, query: VQLRequest): {
|
|
|
11
10
|
c?: PermCRUD;
|
|
12
11
|
path?: string[];
|
|
13
12
|
}[];
|
|
14
|
-
}
|
|
13
|
+
}>;
|
|
15
14
|
export declare function processFieldPath(pathObj: {
|
|
16
15
|
path: string[];
|
|
17
16
|
key: string;
|
|
18
17
|
}): string[];
|
|
19
|
-
export declare function checkRequestPermission(config: VQLConfig,
|
|
18
|
+
export declare function checkRequestPermission(config: VQLConfig, validFn: ValidFn, user: any, query: VQLRequest): Promise<boolean>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PermCRUD } from "../types/perm.js";
|
|
2
2
|
import { extractPathsFromData, hashKey } from "./utils.js";
|
|
3
|
-
export function extractPaths(config, query) {
|
|
3
|
+
export async function extractPaths(config, query) {
|
|
4
4
|
const operation = Object.keys(query.d)[0];
|
|
5
5
|
const collection = query.d[operation].collection;
|
|
6
6
|
const permPaths = {
|
|
7
|
-
db: hashKey(config, query.db),
|
|
7
|
+
db: await hashKey(config, query.db),
|
|
8
8
|
c: collection,
|
|
9
9
|
paths: []
|
|
10
10
|
};
|
|
@@ -79,20 +79,21 @@ export function processFieldPath(pathObj) {
|
|
|
79
79
|
}
|
|
80
80
|
return processedPath;
|
|
81
81
|
}
|
|
82
|
-
export async function checkRequestPermission(config,
|
|
82
|
+
export async function checkRequestPermission(config, validFn, user, query) {
|
|
83
83
|
if (!query)
|
|
84
84
|
return false;
|
|
85
|
-
const permPaths = extractPaths(config, query);
|
|
85
|
+
const permPaths = await extractPaths(config, query);
|
|
86
86
|
// Helper function to recursively check permissions
|
|
87
87
|
const checkPermissionRecursively = async (entityId, requiredPerm, fallbackLevels = []) => {
|
|
88
88
|
// Check if the user has access to the current entity
|
|
89
|
-
const result = await gw.hasAccess(user.id, entityId, requiredPerm);
|
|
89
|
+
// const result = await gw.hasAccess(user.id, entityId, requiredPerm);
|
|
90
|
+
const result = await validFn(entityId, requiredPerm, user);
|
|
90
91
|
if (result.granted) {
|
|
91
92
|
return true;
|
|
92
93
|
}
|
|
93
94
|
// If the result is "entity-404", check the next fallback level
|
|
94
95
|
if (!config.strictACL && result.via === "entity-404" && fallbackLevels.length > 0) {
|
|
95
|
-
const nextFallbackEntityId = hashKey(config, fallbackLevels.slice(0, -1));
|
|
96
|
+
const nextFallbackEntityId = await hashKey(config, fallbackLevels.slice(0, -1));
|
|
96
97
|
return checkPermissionRecursively(nextFallbackEntityId, requiredPerm, fallbackLevels.slice(0, -2));
|
|
97
98
|
}
|
|
98
99
|
// If no fallback levels are left or the result is not "entity-404", deny access
|
|
@@ -106,7 +107,7 @@ export async function checkRequestPermission(config, gw, user, query) {
|
|
|
106
107
|
let fallbackLevels = [];
|
|
107
108
|
if ("c" in path) {
|
|
108
109
|
// Collection-level permission: hash the combination of db and collection
|
|
109
|
-
entityId = hashKey(config, [query.db, permPaths.c]);
|
|
110
|
+
entityId = await hashKey(config, [query.db, permPaths.c]);
|
|
110
111
|
requiredPerm = path.c;
|
|
111
112
|
// Fallback to database level if needed
|
|
112
113
|
fallbackLevels = [query.db];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { VQLConfig } from "../config.js";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function getHash(json: string): Promise<string>;
|
|
3
|
+
export declare function hashKey(config: VQLConfig, path: any): Promise<string>;
|
|
3
4
|
export declare function extractPathsFromData(data: any, stack?: string[]): {
|
|
4
5
|
path: string[];
|
|
5
6
|
key: string;
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
export function
|
|
1
|
+
let cryptoRef = null;
|
|
2
|
+
export async function getHash(json) {
|
|
3
|
+
if (typeof window !== "undefined" && window.crypto?.subtle) {
|
|
4
|
+
const buffer = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(json));
|
|
5
|
+
return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, "0")).join("");
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
if (!cryptoRef) {
|
|
9
|
+
cryptoRef = await import("crypto");
|
|
10
|
+
}
|
|
11
|
+
return cryptoRef.createHash("sha256").update(json).digest("hex");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function hashKey(config, path) {
|
|
3
15
|
const json = JSON.stringify(path);
|
|
4
16
|
if (config.hidePath)
|
|
5
|
-
return
|
|
17
|
+
return await getHash(json);
|
|
6
18
|
else
|
|
7
19
|
return json;
|
|
8
20
|
}
|
package/dist/processor.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Relation, ValtheraCompatible } from "@wxn0brp/db-core";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
export declare class VQLProcessor
|
|
2
|
+
import { VQLConfig, VQLConfigInterface } from "./config.js";
|
|
3
|
+
import { VQLError, VqlQueryRaw } from "./types/vql.js";
|
|
4
|
+
import { ValidFn } from "./types/perm.js";
|
|
5
|
+
export declare class VQLProcessor {
|
|
6
6
|
dbInstances: Record<string, ValtheraCompatible>;
|
|
7
|
-
|
|
8
|
-
config: VQLConfig;
|
|
7
|
+
validFn: ValidFn;
|
|
9
8
|
relation: Relation;
|
|
10
|
-
|
|
11
|
-
constructor(dbInstances: Record<string, ValtheraCompatible>,
|
|
12
|
-
execute<T = any>(queryRaw: VqlQueryRaw<T>, user
|
|
9
|
+
config: VQLConfig;
|
|
10
|
+
constructor(dbInstances: Record<string, ValtheraCompatible>, config?: VQLConfig | Partial<VQLConfigInterface>, validFn?: ValidFn);
|
|
11
|
+
execute<T = any>(queryRaw: VqlQueryRaw<T>, user?: any): Promise<T | VQLError>;
|
|
13
12
|
}
|
package/dist/processor.js
CHANGED
|
@@ -2,23 +2,22 @@ import { Relation } from "@wxn0brp/db-core";
|
|
|
2
2
|
import { VQLConfig } from "./config.js";
|
|
3
3
|
import { executeRelation } from "./cpu/relation.js";
|
|
4
4
|
import { executeQuery } from "./cpu/request.js";
|
|
5
|
-
import { parseStringQuery } from "./cpu/string/index.js";
|
|
6
5
|
import logger from "./logger.js";
|
|
7
|
-
import {
|
|
6
|
+
import { replaceVars } from "./sheet.js";
|
|
8
7
|
import { validateRaw, validateVql } from "./valid.js";
|
|
8
|
+
import { parseVQLS } from "./cpu/string/index.js";
|
|
9
9
|
export class VQLProcessor {
|
|
10
10
|
dbInstances;
|
|
11
|
-
|
|
12
|
-
config;
|
|
11
|
+
validFn;
|
|
13
12
|
relation;
|
|
14
|
-
|
|
15
|
-
constructor(dbInstances,
|
|
13
|
+
config;
|
|
14
|
+
constructor(dbInstances, config = new VQLConfig(), validFn = async () => ({ granted: true, via: "" })) {
|
|
16
15
|
this.dbInstances = dbInstances;
|
|
17
|
-
this.
|
|
18
|
-
this.config = config;
|
|
16
|
+
this.validFn = validFn;
|
|
19
17
|
this.relation = new Relation(dbInstances);
|
|
18
|
+
this.config = config instanceof VQLConfig ? config : new VQLConfig(config);
|
|
20
19
|
}
|
|
21
|
-
async execute(queryRaw, user) {
|
|
20
|
+
async execute(queryRaw, user = { _id: "null-null-null" }) {
|
|
22
21
|
if (typeof queryRaw === "string" || "query" in queryRaw) {
|
|
23
22
|
logger.info("Incoming string query");
|
|
24
23
|
const q = typeof queryRaw === "string" ? queryRaw : queryRaw.query;
|
|
@@ -26,7 +25,7 @@ export class VQLProcessor {
|
|
|
26
25
|
const ref = typeof queryRaw === "string" ? null : queryRaw.ref;
|
|
27
26
|
logger.debug(q);
|
|
28
27
|
try {
|
|
29
|
-
queryRaw =
|
|
28
|
+
queryRaw = parseVQLS(q);
|
|
30
29
|
logger.debug("transformed query: ", queryRaw);
|
|
31
30
|
}
|
|
32
31
|
catch (e) {
|
|
@@ -43,14 +42,14 @@ export class VQLProcessor {
|
|
|
43
42
|
logger.info("Incoming object query");
|
|
44
43
|
logger.debug("Raw query: ", queryRaw);
|
|
45
44
|
}
|
|
46
|
-
const validateRawResult = validateRaw(
|
|
45
|
+
const validateRawResult = validateRaw(queryRaw);
|
|
47
46
|
if (validateRawResult !== true) {
|
|
48
47
|
logger.warn("Raw validation failed:", validateRawResult);
|
|
49
48
|
return validateRawResult;
|
|
50
49
|
}
|
|
51
|
-
const query =
|
|
50
|
+
const query = replaceVars(queryRaw, user);
|
|
52
51
|
logger.debug("Executed sheet (expanded query):", query);
|
|
53
|
-
const validateVqlResult = validateVql(
|
|
52
|
+
const validateVqlResult = validateVql(query);
|
|
54
53
|
if (validateVqlResult !== true) {
|
|
55
54
|
logger.warn("VQL validation failed:", validateVqlResult);
|
|
56
55
|
return validateVqlResult;
|
package/dist/sheet.d.ts
ADDED