@lunora/server 1.0.0-alpha.11 → 1.0.0-alpha.12
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/dist/index.mjs +3 -3
- package/dist/packem_shared/{buildRlsReadRegistry-1jexWrb3.mjs → buildRlsReadRegistry-Do1CSBTr.mjs} +1 -1
- package/dist/packem_shared/{mask-BV_jNzsN.mjs → mask-CWfMjb5Y.mjs} +46 -1
- package/dist/packem_shared/{rls-2Jhd0uev.mjs → rls-DhNgKFeW.mjs} +6 -3
- package/dist/rls/testing.mjs +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -17,11 +17,11 @@ export { defineShape } from './packem_shared/defineShape-CJ27Wx7o.mjs';
|
|
|
17
17
|
export { anyApi } from './types.mjs';
|
|
18
18
|
export { cronJobs } from '@lunora/scheduler';
|
|
19
19
|
export { ValidationError, v } from '@lunora/values';
|
|
20
|
-
export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-
|
|
20
|
+
export { buildRlsReadRegistry, composeShapeReadWhere } from './packem_shared/buildRlsReadRegistry-Do1CSBTr.mjs';
|
|
21
21
|
export { createPolicyDsl, definePermission, definePolicies, definePolicy, defineRole } from './packem_shared/createPolicyDsl-De67zPDS.mjs';
|
|
22
22
|
export { defineStorageRule, defineStorageRules } from './packem_shared/defineStorageRule-qu0mpilX.mjs';
|
|
23
|
-
export { mask } from './packem_shared/mask-
|
|
24
|
-
export { rls } from './packem_shared/rls-
|
|
23
|
+
export { mask } from './packem_shared/mask-CWfMjb5Y.mjs';
|
|
24
|
+
export { rls } from './packem_shared/rls-DhNgKFeW.mjs';
|
|
25
25
|
export { storageRules } from './packem_shared/storageRules-Cje6Woea.mjs';
|
|
26
26
|
|
|
27
27
|
const VERSION = "0.0.0";
|
package/dist/packem_shared/{buildRlsReadRegistry-1jexWrb3.mjs → buildRlsReadRegistry-Do1CSBTr.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-
|
|
1
|
+
import { indexRolePermissions, computeReadBaseWhere, permissionName } from './rls-DhNgKFeW.mjs';
|
|
2
2
|
import { r as readRlsTag } from './policy-tag-DvpVH2tv.mjs';
|
|
3
3
|
|
|
4
4
|
const FALSE_PREDICATE = { OR: [] };
|
|
@@ -60,8 +60,13 @@ const wrapDatabase = (base, perTable, context) => {
|
|
|
60
60
|
const rows = await reader.collect();
|
|
61
61
|
return rows.map((row) => maskRow(row, columns, context));
|
|
62
62
|
},
|
|
63
|
+
// SECURITY (value oracle): the predicate must see the MASKED row, not
|
|
64
|
+
// the raw stored row — otherwise a caller can `.filter(d => d.ssn ===
|
|
65
|
+
// guess)` to read the value the mask hides. Masking before the
|
|
66
|
+
// predicate keeps filtering on non-masked columns working while
|
|
67
|
+
// redacting masked cells the predicate can observe.
|
|
63
68
|
filter: (predicate) => wrapReader(
|
|
64
|
-
reader.filter((document) => predicate(document)),
|
|
69
|
+
reader.filter((document) => predicate(maskRow(document, columns, context))),
|
|
65
70
|
columns
|
|
66
71
|
),
|
|
67
72
|
first: async () => {
|
|
@@ -120,23 +125,63 @@ const wrapDatabase = (base, perTable, context) => {
|
|
|
120
125
|
throw new LunoraError("MASK_UNSUPPORTED", `${method}() over masked column "${offending}" on "${tableName}" is not supported`);
|
|
121
126
|
}
|
|
122
127
|
};
|
|
128
|
+
const collectWhereFields = (where, into) => {
|
|
129
|
+
if (!where || typeof where !== "object" || Array.isArray(where)) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
for (const [key, value] of Object.entries(where)) {
|
|
133
|
+
if (key === "AND" || key === "OR") {
|
|
134
|
+
if (Array.isArray(value)) {
|
|
135
|
+
for (const clause of value) {
|
|
136
|
+
collectWhereFields(clause, into);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} else if (key === "NOT") {
|
|
140
|
+
collectWhereFields(value, into);
|
|
141
|
+
} else if (!key.startsWith("__")) {
|
|
142
|
+
into.add(key);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const assertWhereAllowed = (tableName, where, method) => {
|
|
147
|
+
const columns = perTable.get(tableName);
|
|
148
|
+
if (!columns || where === void 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
152
|
+
collectWhereFields(where, referenced);
|
|
153
|
+
for (const field of referenced) {
|
|
154
|
+
if (field in columns) {
|
|
155
|
+
throw new LunoraError("MASK_UNSUPPORTED", `${method}() filtering "${tableName}" by masked column "${field}" is not supported`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
123
159
|
const wrapped = {
|
|
124
160
|
...base,
|
|
125
161
|
aggregate(tableName, options) {
|
|
126
162
|
assertReductionAllowed(tableName, [options.field], "aggregate");
|
|
127
163
|
return base.aggregate(tableName, options);
|
|
128
164
|
},
|
|
165
|
+
count(tableName, whereOrArgs) {
|
|
166
|
+
const wrapper = whereOrArgs && typeof whereOrArgs === "object" && !Array.isArray(whereOrArgs) ? whereOrArgs : void 0;
|
|
167
|
+
const where = wrapper && ("where" in wrapper || "baseWhere" in wrapper || "restrictsCounts" in wrapper) ? wrapper.where : whereOrArgs;
|
|
168
|
+
assertWhereAllowed(tableName, where, "count");
|
|
169
|
+
return base.count(tableName, whereOrArgs);
|
|
170
|
+
},
|
|
129
171
|
async findFirst(tableName, args) {
|
|
172
|
+
assertWhereAllowed(tableName, args?.where, "findFirst");
|
|
130
173
|
const row = await base.findFirst(tableName, args);
|
|
131
174
|
const columns = perTable.get(tableName);
|
|
132
175
|
return row && columns ? maskRow(row, columns, context) : row;
|
|
133
176
|
},
|
|
134
177
|
async findFirstOrThrow(tableName, args) {
|
|
178
|
+
assertWhereAllowed(tableName, args?.where, "findFirstOrThrow");
|
|
135
179
|
const row = await base.findFirstOrThrow(tableName, args);
|
|
136
180
|
const columns = perTable.get(tableName);
|
|
137
181
|
return columns ? maskRow(row, columns, context) : row;
|
|
138
182
|
},
|
|
139
183
|
async findMany(tableName, args) {
|
|
184
|
+
assertWhereAllowed(tableName, args?.where, "findMany");
|
|
140
185
|
const page = await base.findMany(tableName, args);
|
|
141
186
|
const columns = perTable.get(tableName);
|
|
142
187
|
return columns ? maskPage(page, columns, context) : page;
|
|
@@ -245,7 +245,6 @@ const isFacadeEntry = (value) => {
|
|
|
245
245
|
return typeof candidate["findMany"] === "function" && typeof candidate["withSearchIndex"] === "function";
|
|
246
246
|
};
|
|
247
247
|
const wrapDatabase = (base, raw, perTable, context) => {
|
|
248
|
-
const route = (tableName) => perTable.has(tableName) ? raw : base;
|
|
249
248
|
const readBaseCache = /* @__PURE__ */ new Map();
|
|
250
249
|
const readBase = (tableName) => {
|
|
251
250
|
const cached = readBaseCache.get(tableName);
|
|
@@ -263,6 +262,7 @@ const wrapDatabase = (base, raw, perTable, context) => {
|
|
|
263
262
|
readBaseCache.set(tableName, result);
|
|
264
263
|
return result;
|
|
265
264
|
};
|
|
265
|
+
const route = (tableName) => readBase(tableName).restricts ? raw : base;
|
|
266
266
|
const relationReadFilter = (table) => readBase(table).baseWhere;
|
|
267
267
|
const locateRow = async (id, expectedTable) => {
|
|
268
268
|
if (raw.lookupById) {
|
|
@@ -300,7 +300,7 @@ const wrapDatabase = (base, raw, perTable, context) => {
|
|
|
300
300
|
const nextRow = computeNextRow ? computeNextRow(located.row) : void 0;
|
|
301
301
|
const writeOk = evaluateWrite(policies, op, { ...context, row: located.row }, nextRow);
|
|
302
302
|
if (!writeOk) {
|
|
303
|
-
throw new LunoraError("FORBIDDEN", `${op}
|
|
303
|
+
throw new LunoraError("FORBIDDEN", `${op} denied by policy`);
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
return perform(raw);
|
|
@@ -366,7 +366,10 @@ const wrapDatabase = (base, raw, perTable, context) => {
|
|
|
366
366
|
return base.get(id, expectedTable);
|
|
367
367
|
}
|
|
368
368
|
const { baseWhere, restricts } = readBase(located.tableName);
|
|
369
|
-
if (!restricts
|
|
369
|
+
if (!restricts) {
|
|
370
|
+
return base.get(id, expectedTable);
|
|
371
|
+
}
|
|
372
|
+
if (!baseWhere) {
|
|
370
373
|
return located.row;
|
|
371
374
|
}
|
|
372
375
|
const allowed = await raw.findFirst(located.tableName, { baseWhere, limit: 1, where: { _id: id } });
|
package/dist/rls/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-
|
|
1
|
+
import { indexRolePermissions, computeReadBaseWhere, matchesWhere, evaluateWrite, permissionName } from '../packem_shared/rls-DhNgKFeW.mjs';
|
|
2
2
|
|
|
3
3
|
const expectPolicy = (policies, options = {}) => {
|
|
4
4
|
const rolePermissions = indexRolePermissions(options.roles);
|