@aooth/arbac-moost 0.1.78 → 0.1.80
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,4 +1,4 @@
|
|
|
1
|
-
import { r as AoothArbacClaims, t as ArbacUserProvider } from "../user.provider-
|
|
1
|
+
import { r as AoothArbacClaims, t as ArbacUserProvider } from "../user.provider-BZkeBV5s.mjs";
|
|
2
2
|
import { TAtscriptAnnotatedType, TAtscriptTypeObject, TMetadataMap, TValidatorOptions, Validator } from "@atscript/typescript/utils";
|
|
3
3
|
import { AoothUserCredentials } from "@aooth/user/atscript-db/model.as";
|
|
4
4
|
//#region src/atscript/auto-provider.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _ as ProjectionOf, a as conjoinArbacDbScopes, b as TArbacMeta, c as ArbacDbScope, d as extractUsedControlValues, f as applyAllowedFieldsAndSet, g as OwnFieldKey, h as NavTarget, i as ConjoinArbacDbScopesOptions, l as AsArbacDbController, m as NavRelationKey, n as ArbacUserProviderToken, o as RefForeignKey, p as ControlsOf, r as AoothArbacClaims, s as RefTableSource, t as ArbacUserProvider, u as enforceControlsPolicy, v as ARBAC_DELEGATED_AUTH, x as getArbacMate, y as ArbacDelegatedAuth } from "./user.provider-
|
|
1
|
+
import { _ as ProjectionOf, a as conjoinArbacDbScopes, b as TArbacMeta, c as ArbacDbScope, d as extractUsedControlValues, f as applyAllowedFieldsAndSet, g as OwnFieldKey, h as NavTarget, i as ConjoinArbacDbScopesOptions, l as AsArbacDbController, m as NavRelationKey, n as ArbacUserProviderToken, o as RefForeignKey, p as ControlsOf, r as AoothArbacClaims, s as RefTableSource, t as ArbacUserProvider, u as enforceControlsPolicy, v as ARBAC_DELEGATED_AUTH, x as getArbacMate, y as ArbacDelegatedAuth } from "./user.provider-BZkeBV5s.mjs";
|
|
2
2
|
import { Arbac, Arbac as Arbac$1, TArbacCompiledRule, TArbacEvalResult, TArbacEvalResult as TArbacEvalResult$1, TArbacRole, TArbacRoleForResource, TArbacRule, arbacPatternToRegex } from "@aooth/arbac-core";
|
|
3
|
-
import { ControlGate, TProjection, TScopeFieldRules, TScopeFilter, conjoinScopeFilters } from "@aooth/arbac";
|
|
3
|
+
import { ControlGate, ControlGate as ControlGate$1, TProjection, TScopeFieldRules, TScopeFilter, conjoinScopeFilters } from "@aooth/arbac";
|
|
4
4
|
import { TAuthGuardDef } from "@moostjs/event-http";
|
|
5
5
|
import { EventContext } from "@wooksjs/event-core";
|
|
6
6
|
import { FilterExpr, TDbRemoveGuardContext, TDbWriteCheckContext, TDbWriteGuardContext, TDeleteOptions, TMetaResponse, TRowResolveOptions, TWriteOptions, UniqueryControls } from "@atscript/db";
|
|
@@ -490,8 +490,13 @@ declare function pruneMetaByVisibility(meta: TMetaResponse, vis: MetaVisibility,
|
|
|
490
490
|
* still need `type` for their insert/update forms. PK + `preferredId` are
|
|
491
491
|
* never pruned.
|
|
492
492
|
*
|
|
493
|
+
* 3. The capabilities (`crud[op]` control lists, field `groupable` /
|
|
494
|
+
* `bucketable` / `numeric` / `sortable`, relation `filterable`, the
|
|
495
|
+
* aggregate and search envelope keys) follow each read op's `controls` policy
|
|
496
|
+
* ({@link gateMetaControls}).
|
|
497
|
+
*
|
|
493
498
|
* The search surface (`searchIndexes`, `searchable`, `vectorSearchable`,
|
|
494
|
-
* `geoSearchable`) is narrowed by moost-db itself (≥ 0.1.147, under the
|
|
499
|
+
* `geoSearchable`) is also narrowed by moost-db itself (≥ 0.1.147, under the
|
|
495
500
|
* overridden `hasField`), after this overlay.
|
|
496
501
|
*
|
|
497
502
|
* @param source - the controller's `this.readable` (identifiers, relations
|
package/dist/index.mjs
CHANGED
|
@@ -130,14 +130,14 @@ function identifierSet(table) {
|
|
|
130
130
|
function metaAlwaysVisibleFields(_controller, source) {
|
|
131
131
|
return identifierSet(source);
|
|
132
132
|
}
|
|
133
|
-
const NO_POLICY = {
|
|
133
|
+
const NO_POLICY$1 = {
|
|
134
134
|
json: NO_NAMES$1,
|
|
135
135
|
derived: /* @__PURE__ */ new Map(),
|
|
136
136
|
relations: void 0
|
|
137
137
|
};
|
|
138
138
|
const tablePolicyCache = /* @__PURE__ */ new WeakMap();
|
|
139
139
|
function tablePolicy(table) {
|
|
140
|
-
if (!table) return NO_POLICY;
|
|
140
|
+
if (!table) return NO_POLICY$1;
|
|
141
141
|
return getOrCreate(tablePolicyCache, table, () => {
|
|
142
142
|
const derived = /* @__PURE__ */ new Map();
|
|
143
143
|
for (const fd of table.fieldDescriptors ?? []) if (fd.derived?.sourcePath) derived.set(fd.path, fd.derived.sourcePath);
|
|
@@ -1242,15 +1242,25 @@ function metaDecisionKey(input, ctx) {
|
|
|
1242
1242
|
const vis = p.refTargets.get(type);
|
|
1243
1243
|
refTargets.push(idOf(type), ctx.isServed(type), vis ? encodeLevel(vis, NO_TREE, ctx, seen, 1) : 0);
|
|
1244
1244
|
}
|
|
1245
|
-
const { writable, nestedWrites
|
|
1246
|
-
|
|
1245
|
+
const { writable, nestedWrites } = p;
|
|
1246
|
+
const controls = [];
|
|
1247
|
+
for (const op of Object.keys(p.controls).toSorted()) {
|
|
1248
|
+
const policy = p.controls[op] ?? {};
|
|
1249
|
+
const gates = [op];
|
|
1250
|
+
for (const key of Object.keys(policy).toSorted()) {
|
|
1251
|
+
const gate = policy[key];
|
|
1252
|
+
if (typeof gate !== "boolean" && !Array.isArray(gate)) refuse();
|
|
1253
|
+
gates.push(key, Array.isArray(gate) ? sortedNames(gate) : gate);
|
|
1254
|
+
}
|
|
1255
|
+
controls.push(gates);
|
|
1256
|
+
}
|
|
1247
1257
|
return JSON.stringify([
|
|
1248
1258
|
bits(input.actions),
|
|
1249
1259
|
bits(input.crud),
|
|
1250
1260
|
encodeLevel(p.vis, p.tree, ctx, seen, 0),
|
|
1251
1261
|
writable === void 0 ? 0 : writable === "all" ? 1 : sortedNames(writable),
|
|
1252
1262
|
nestedWrites ? sortedNames(nestedWrites) : 0,
|
|
1253
|
-
|
|
1263
|
+
controls,
|
|
1254
1264
|
refTargets,
|
|
1255
1265
|
p.vis.table?.type ? ctx.isServed(p.vis.table.type) : 0
|
|
1256
1266
|
]);
|
|
@@ -1856,8 +1866,13 @@ function crudHandlers(op, methods) {
|
|
|
1856
1866
|
* still need `type` for their insert/update forms. PK + `preferredId` are
|
|
1857
1867
|
* never pruned.
|
|
1858
1868
|
*
|
|
1869
|
+
* 3. The capabilities (`crud[op]` control lists, field `groupable` /
|
|
1870
|
+
* `bucketable` / `numeric` / `sortable`, relation `filterable`, the
|
|
1871
|
+
* aggregate and search envelope keys) follow each read op's `controls` policy
|
|
1872
|
+
* ({@link gateMetaControls}).
|
|
1873
|
+
*
|
|
1859
1874
|
* The search surface (`searchIndexes`, `searchable`, `vectorSearchable`,
|
|
1860
|
-
* `geoSearchable`) is narrowed by moost-db itself (≥ 0.1.147, under the
|
|
1875
|
+
* `geoSearchable`) is also narrowed by moost-db itself (≥ 0.1.147, under the
|
|
1861
1876
|
* overridden `hasField`), after this overlay.
|
|
1862
1877
|
*
|
|
1863
1878
|
* @param source - the controller's `this.readable` (identifiers, relations
|
|
@@ -1880,10 +1895,15 @@ async function applyArbacMetaOverlay(meta, source, _indexes) {
|
|
|
1880
1895
|
const crudBits = crudScopes.map((scopes) => scopes !== void 0);
|
|
1881
1896
|
const readScopes = [];
|
|
1882
1897
|
const writeScopes = [];
|
|
1898
|
+
const controls = {};
|
|
1883
1899
|
for (let i = 0; i < crudKeys.length; i++) {
|
|
1884
1900
|
const scopes = crudScopes[i];
|
|
1885
1901
|
if (!scopes) continue;
|
|
1886
|
-
(WRITE_CRUD_OPS.has(crudKeys[i])
|
|
1902
|
+
if (WRITE_CRUD_OPS.has(crudKeys[i])) writeScopes.push(...scopes);
|
|
1903
|
+
else {
|
|
1904
|
+
readScopes.push(...scopes);
|
|
1905
|
+
controls[crudKeys[i]] = effectiveScope(scopes).controls;
|
|
1906
|
+
}
|
|
1887
1907
|
}
|
|
1888
1908
|
const overlay = () => {
|
|
1889
1909
|
const crud = {};
|
|
@@ -1900,18 +1920,17 @@ async function applyArbacMetaOverlay(meta, source, _indexes) {
|
|
|
1900
1920
|
readScopes,
|
|
1901
1921
|
writeScopes
|
|
1902
1922
|
}, RECORD_CTX) : void 0, memoMax, overlay);
|
|
1903
|
-
const groupBy = effectiveScope(readScopes).controls.$groupBy;
|
|
1904
1923
|
const writable = collectWritableFields(writeScopes, false);
|
|
1905
1924
|
const nestedWrites = effectiveScope(writeScopes).nestedWrites;
|
|
1906
1925
|
if (isIdentifierSet(source)) {
|
|
1907
1926
|
const vis = buildScopeVisibility(readScopes, void 0, source);
|
|
1908
1927
|
const visible = Object.keys(vis.allowed).length > 0 || vis.withGrants.size > 0;
|
|
1909
1928
|
const overlaid = overlay();
|
|
1910
|
-
return
|
|
1929
|
+
return gateMetaControls(visible ? pruneMetaByVisibility(overlaid, {
|
|
1911
1930
|
...vis,
|
|
1912
1931
|
writable,
|
|
1913
1932
|
nestedWrites
|
|
1914
|
-
}) : overlaid,
|
|
1933
|
+
}) : overlaid, controls);
|
|
1915
1934
|
}
|
|
1916
1935
|
const vis = buildScopeVisibility(readScopes, source, { relations: await resolveRelationTree(readScopes, source, metaRelationNames(meta), { listSiblings: false }) });
|
|
1917
1936
|
const refTargets = await resolveRefTargets(vis, meta);
|
|
@@ -1925,14 +1944,14 @@ async function applyArbacMetaOverlay(meta, source, _indexes) {
|
|
|
1925
1944
|
tree: metaRelationNames(meta),
|
|
1926
1945
|
writable,
|
|
1927
1946
|
nestedWrites,
|
|
1928
|
-
|
|
1947
|
+
controls,
|
|
1929
1948
|
refTargets
|
|
1930
1949
|
}
|
|
1931
|
-
}, RECORD_CTX) : void 0, memoMax, () =>
|
|
1950
|
+
}, RECORD_CTX) : void 0, memoMax, () => gateMetaControls(pruneMetaByVisibility(overlay(), {
|
|
1932
1951
|
...vis,
|
|
1933
1952
|
writable,
|
|
1934
1953
|
nestedWrites
|
|
1935
|
-
}, refTargets),
|
|
1954
|
+
}, refTargets), controls));
|
|
1936
1955
|
}
|
|
1937
1956
|
/** What the `/meta` memo's decision record reads from the pruning rules. */
|
|
1938
1957
|
const RECORD_CTX = {
|
|
@@ -2095,28 +2114,143 @@ function stripFormTargets(runtime, node, readable, depth) {
|
|
|
2095
2114
|
type: changed
|
|
2096
2115
|
} : out;
|
|
2097
2116
|
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
*
|
|
2103
|
-
*
|
|
2104
|
-
*
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2117
|
+
const NO_POLICY = {};
|
|
2118
|
+
/** `crud[op]` entries that are query parts of their own, never seen by the controls gate. */
|
|
2119
|
+
const NOT_CONTROLS = /* @__PURE__ */ new Set(["filter", "insights"]);
|
|
2120
|
+
/**
|
|
2121
|
+
* Whether `control` is unusable in an op listing `list` under `policy`: its
|
|
2122
|
+
* gate is `false`, or everything it qualifies is unusable (`$index` /
|
|
2123
|
+
* `$fuzzy` / `$vector` name how `$search` runs, `$threshold` bounds
|
|
2124
|
+
* `$vector`, `$rowOrder` orders `first()` / `last()` in an aggregate
|
|
2125
|
+
* `$select`, `$nulls` places NULLs in `$sort` and `$rowOrder` keys).
|
|
2126
|
+
*/
|
|
2127
|
+
function isControlOff(control, list, policy) {
|
|
2128
|
+
if (!NOT_CONTROLS.has(control) && policy[`$${control}`] === false) return true;
|
|
2129
|
+
const off = (c) => isControlOff(c, list, policy);
|
|
2130
|
+
switch (control) {
|
|
2131
|
+
case "index":
|
|
2132
|
+
case "fuzzy": return list.includes("search") && off("search");
|
|
2133
|
+
case "vector": return off("search");
|
|
2134
|
+
case "threshold": return off("vector");
|
|
2135
|
+
case "rowOrder": return off("groupBy") || off("select");
|
|
2136
|
+
case "nulls": return off("sort") && (!list.includes("rowOrder") || off("rowOrder"));
|
|
2137
|
+
default: return false;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
/** Envelope keys that advertise aggregate queries (`$groupBy`, aggregate / bucket `$select`). */
|
|
2141
|
+
const AGGREGATE_ENVELOPE_KEYS = [
|
|
2142
|
+
"aggregateFns",
|
|
2143
|
+
"aggregateExpressions",
|
|
2144
|
+
"bucketUnits"
|
|
2145
|
+
];
|
|
2146
|
+
/**
|
|
2147
|
+
* The `/meta` capabilities follow each read op's `controls` policy (`policies`;
|
|
2148
|
+
* an op without one is unrestricted), mirroring the 403 `applyArbacControls`
|
|
2149
|
+
* raises on that op's requests:
|
|
2150
|
+
*
|
|
2151
|
+
* - `crud[op]` loses every control the op's policy makes unusable
|
|
2152
|
+
* ({@link isControlOff});
|
|
2153
|
+
* - a capability flag stays when SOME op listing its controls keeps them
|
|
2154
|
+
* (no op lists them → left as built): `groupable` (`groupBy`, and the
|
|
2155
|
+
* field on that op's `$groupBy` whitelist, if any), `bucketable` (the same
|
|
2156
|
+
* plus `select` — a bucket is checked as the field it buckets), `numeric`
|
|
2157
|
+
* and the aggregate envelope keys (`groupBy` + `select`: every aggregate,
|
|
2158
|
+
* calendar bucket or arithmetic `$select` is `$groupBy` use), `sortable`
|
|
2159
|
+
* (`sort`), `nullsPlacement` (`nulls`), `searchable` + `searchIndexes`
|
|
2160
|
+
* (`search`), `vectorSearchable` + the vector indexes (`search` +
|
|
2161
|
+
* `vector`), the non-default text indexes (`search` + `index`), a
|
|
2162
|
+
* relation's `filterable` (`filter` + `with`, and the relation on that
|
|
2163
|
+
* op's `$with` whitelist, if any).
|
|
2164
|
+
*
|
|
2165
|
+
* Returns `meta` itself when nothing changes.
|
|
2166
|
+
*
|
|
2167
|
+
* @since 0.1.75 (`groupable`), 0.1.80 (the rest)
|
|
2168
|
+
*/
|
|
2169
|
+
function gateMetaControls(meta, policies) {
|
|
2170
|
+
if (!Object.values(policies).some((p) => p && Object.keys(p).length > 0)) return meta;
|
|
2171
|
+
let changed = false;
|
|
2172
|
+
const crud = {};
|
|
2173
|
+
const ops = [];
|
|
2174
|
+
for (const [op, list] of Object.entries(meta.crud)) {
|
|
2175
|
+
const policy = policies[op] ?? NO_POLICY;
|
|
2176
|
+
const kept = list.filter((c) => !isControlOff(c, list, policy));
|
|
2177
|
+
if (kept.length !== list.length) changed = true;
|
|
2178
|
+
crud[op] = kept;
|
|
2179
|
+
ops.push({
|
|
2180
|
+
list,
|
|
2181
|
+
kept,
|
|
2182
|
+
policy
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
/** Some op listing `controls` keeps them all (and `name` passes its `whitelist` gate). */
|
|
2186
|
+
const served = (controls, name, whitelist = "$groupBy") => {
|
|
2187
|
+
const listing = ops.filter((o) => controls.every((c) => o.list.includes(c)));
|
|
2188
|
+
return listing.length === 0 || listing.some(({ kept, policy }) => {
|
|
2189
|
+
const gate = policy[whitelist];
|
|
2190
|
+
return controls.every((c) => kept.includes(c)) && (name === void 0 || !Array.isArray(gate) || gate.includes(name));
|
|
2191
|
+
});
|
|
2192
|
+
};
|
|
2193
|
+
const aggregates = served(["groupBy", "select"]);
|
|
2194
|
+
const sortable = served(["sort"]);
|
|
2111
2195
|
const fields = {};
|
|
2112
|
-
for (const [path, field] of entries
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2196
|
+
for (const [path, field] of Object.entries(meta.fields)) {
|
|
2197
|
+
let next = field;
|
|
2198
|
+
const strip = (flag, keep) => {
|
|
2199
|
+
if (!next[flag] || keep()) return;
|
|
2200
|
+
const { [flag]: _dropped, ...rest } = next;
|
|
2201
|
+
next = rest;
|
|
2202
|
+
};
|
|
2203
|
+
strip("groupable", () => served(["groupBy"], path));
|
|
2204
|
+
strip("bucketable", () => served(["groupBy", "select"], path));
|
|
2205
|
+
strip("numeric", () => aggregates);
|
|
2206
|
+
if (!sortable && next.sortable) next = {
|
|
2207
|
+
...next,
|
|
2208
|
+
sortable: false
|
|
2209
|
+
};
|
|
2210
|
+
if (next !== field) changed = true;
|
|
2211
|
+
fields[path] = next;
|
|
2212
|
+
}
|
|
2213
|
+
const out = {
|
|
2117
2214
|
...meta,
|
|
2118
|
-
fields
|
|
2215
|
+
fields,
|
|
2216
|
+
crud
|
|
2119
2217
|
};
|
|
2218
|
+
const unfilterable = (r) => r.filterable && !served(["filter", "with"], r.name, "$with");
|
|
2219
|
+
if (out.relations.some(unfilterable)) {
|
|
2220
|
+
out.relations = out.relations.map((r) => {
|
|
2221
|
+
if (!unfilterable(r)) return r;
|
|
2222
|
+
const { filterable: _filterable, ...rest } = r;
|
|
2223
|
+
return rest;
|
|
2224
|
+
});
|
|
2225
|
+
changed = true;
|
|
2226
|
+
}
|
|
2227
|
+
if (!aggregates) for (const key of AGGREGATE_ENVELOPE_KEYS) {
|
|
2228
|
+
if (out[key] === void 0) continue;
|
|
2229
|
+
delete out[key];
|
|
2230
|
+
changed = true;
|
|
2231
|
+
}
|
|
2232
|
+
if (out.nullsPlacement && !served(["nulls"])) {
|
|
2233
|
+
delete out.nullsPlacement;
|
|
2234
|
+
changed = true;
|
|
2235
|
+
}
|
|
2236
|
+
const search = served(["search"]);
|
|
2237
|
+
const vector = search && served(["search", "vector"]);
|
|
2238
|
+
const named = served(["search", "index"]);
|
|
2239
|
+
const all = out.searchIndexes ?? [];
|
|
2240
|
+
const indexes = all.filter((i) => i.type === "vector" ? vector : search && (named || !!i.isDefault));
|
|
2241
|
+
if (indexes.length !== all.length) {
|
|
2242
|
+
out.searchIndexes = indexes;
|
|
2243
|
+
changed = true;
|
|
2244
|
+
}
|
|
2245
|
+
if (out.searchable && !search) {
|
|
2246
|
+
out.searchable = false;
|
|
2247
|
+
changed = true;
|
|
2248
|
+
}
|
|
2249
|
+
if (out.vectorSearchable && !vector) {
|
|
2250
|
+
out.vectorSearchable = false;
|
|
2251
|
+
changed = true;
|
|
2252
|
+
}
|
|
2253
|
+
return changed ? out : meta;
|
|
2120
2254
|
}
|
|
2121
2255
|
const metaNamesCache = /* @__PURE__ */ new WeakMap();
|
|
2122
2256
|
/** The relation names `/meta`'s serialized type reaches: root relations, then nav props of each nav body. */
|
|
@@ -3264,14 +3398,19 @@ const preservedFieldsCache = /* @__PURE__ */ new WeakMap();
|
|
|
3264
3398
|
* - `readonly string[]`: allow only the listed values; reject any other.
|
|
3265
3399
|
*
|
|
3266
3400
|
* "Used" means the control key is present AND non-empty (an empty array is
|
|
3267
|
-
* treated as not used, matching how the parser leaves missing controls)
|
|
3401
|
+
* treated as not used, matching how the parser leaves missing controls) —
|
|
3402
|
+
* except `$groupBy`, whose presence alone marks an aggregate query: moost-db
|
|
3403
|
+
* (≥ 0.1.155) gives an ungrouped aggregate (`$select=sum(amount):total`, no
|
|
3404
|
+
* `$groupBy`) `$groupBy: []`, so a `false` gate denies it and a whitelist
|
|
3405
|
+
* passes it (no column is grouped on; the aggregated fields go through the
|
|
3406
|
+
* field-visibility gate like any other read).
|
|
3268
3407
|
*/
|
|
3269
3408
|
function enforceControlsPolicy(policy, controls) {
|
|
3270
3409
|
if (Object.keys(policy).length === 0) return;
|
|
3271
3410
|
for (const [key, gate] of Object.entries(policy)) {
|
|
3272
3411
|
const used = controls[key];
|
|
3273
3412
|
if (used === void 0 || used === null) continue;
|
|
3274
|
-
if (Array.isArray(used) && used.length === 0) continue;
|
|
3413
|
+
if (Array.isArray(used) && used.length === 0 && key !== "$groupBy") continue;
|
|
3275
3414
|
if (gate === false) throw new HttpError(403, `Control "${key}" is not allowed for your role`);
|
|
3276
3415
|
if (Array.isArray(gate)) {
|
|
3277
3416
|
const usedValues = extractUsedControlValues(key, used, controls);
|
|
@@ -84,20 +84,21 @@ type NavRelationKey<T> = keyof NavPropsOf<T> extends never ? string : (keyof Nav
|
|
|
84
84
|
* `unionProjections` / `restrictProjection` keep compiling.
|
|
85
85
|
*/
|
|
86
86
|
type ProjectionOf<T> = IsUnknown<T> extends true ? TProjection : Partial<Record<OwnFieldKey<T>, 0 | 1>>;
|
|
87
|
+
/** The Uniquery controls a gate can only allow or deny (`true` / `false`). */
|
|
88
|
+
type BooleanControl = "$select" | "$sort" | "$nulls" | "$skip" | "$limit" | "$count" | "$page" | "$size" | "$search" | "$index" | "$fuzzy" | "$vector" | "$threshold" | "$actions" | "$rowOrder" | "$having" | "$center" | "$maxDistance" | "$minDistance";
|
|
87
89
|
/**
|
|
88
|
-
* Typed control gates
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* Typed control gates, matching what `unionControlsPolicy` accepts: only
|
|
91
|
+
* `$with` (nav relation names) and `$groupBy` (own field paths) take a
|
|
92
|
+
* whitelist; every other control is `true` / `false` (a whitelist there
|
|
93
|
+
* throws at scope merge). Unknown control keys are rejected. Falls back to
|
|
94
|
+
* the legacy untyped `Record<string, ControlGate>` for `T = unknown` so
|
|
95
|
+
* internal helpers (`unionControlsPolicy`) and existing untyped call sites
|
|
96
|
+
* keep compiling.
|
|
93
97
|
*/
|
|
94
98
|
type ControlsOf<T> = IsUnknown<T> extends true ? Record<string, ControlGate> : {
|
|
95
|
-
$with?:
|
|
96
|
-
$
|
|
97
|
-
|
|
98
|
-
$having?: ControlGate;
|
|
99
|
-
[key: `$${string}`]: ControlGate | Array<string> | undefined;
|
|
100
|
-
};
|
|
99
|
+
$with?: ReadonlyArray<NavRelationKey<T>> | boolean;
|
|
100
|
+
$groupBy?: ReadonlyArray<OwnFieldKey<T>> | boolean;
|
|
101
|
+
} & Partial<Record<BooleanControl, boolean>>;
|
|
101
102
|
//#endregion
|
|
102
103
|
//#region src/db/write-fields.d.ts
|
|
103
104
|
/**
|
|
@@ -173,6 +174,10 @@ interface ArbacDbScope<T = unknown> {
|
|
|
173
174
|
* `?$with=comments`, rejects `?$with=tasks`). Supported only for `$with`
|
|
174
175
|
* (relation names) and `$groupBy` (column names) in v1.
|
|
175
176
|
*
|
|
177
|
+
* An ungrouped aggregate (`$select=sum(amount):total`, no `$groupBy`) counts
|
|
178
|
+
* as `$groupBy` use (0.1.80+, with atscript-db 0.1.155+): `$groupBy: false`
|
|
179
|
+
* denies it; a `$groupBy` whitelist allows it (no column is grouped on).
|
|
180
|
+
*
|
|
176
181
|
* Across roles, gates union additively (silence wins) via {@link unionControlsPolicy}.
|
|
177
182
|
*
|
|
178
183
|
* @example `{ controls: { $with: false } }` — disable $with for this role.
|
|
@@ -406,7 +411,12 @@ declare class AsArbacDbController<T extends TAtscriptAnnotatedType = TAtscriptAn
|
|
|
406
411
|
* - `readonly string[]`: allow only the listed values; reject any other.
|
|
407
412
|
*
|
|
408
413
|
* "Used" means the control key is present AND non-empty (an empty array is
|
|
409
|
-
* treated as not used, matching how the parser leaves missing controls)
|
|
414
|
+
* treated as not used, matching how the parser leaves missing controls) —
|
|
415
|
+
* except `$groupBy`, whose presence alone marks an aggregate query: moost-db
|
|
416
|
+
* (≥ 0.1.155) gives an ungrouped aggregate (`$select=sum(amount):total`, no
|
|
417
|
+
* `$groupBy`) `$groupBy: []`, so a `false` gate denies it and a whitelist
|
|
418
|
+
* passes it (no column is grouped on; the aggregated fields go through the
|
|
419
|
+
* field-visibility gate like any other read).
|
|
410
420
|
*/
|
|
411
421
|
declare function enforceControlsPolicy(policy: Record<string, ControlGate>, controls: Record<string, unknown>): void;
|
|
412
422
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/arbac-moost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.80",
|
|
4
4
|
"description": "Moost RBAC integration for aoothjs (migrated from @moostjs/arbac)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abac",
|
|
@@ -60,31 +60,31 @@
|
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@uniqu/core": "^0.1.
|
|
64
|
-
"@aooth/arbac": "0.1.
|
|
65
|
-
"@aooth/user": "0.1.
|
|
66
|
-
"@aooth/arbac-core": "0.1.
|
|
63
|
+
"@uniqu/core": "^0.1.15",
|
|
64
|
+
"@aooth/arbac": "0.1.80",
|
|
65
|
+
"@aooth/user": "0.1.80",
|
|
66
|
+
"@aooth/arbac-core": "0.1.80"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"@atscript/core": "^0.1.
|
|
70
|
-
"@atscript/db": "^0.1.
|
|
71
|
-
"@atscript/db-sql-tools": "^0.1.
|
|
72
|
-
"@atscript/db-sqlite": "^0.1.
|
|
73
|
-
"@atscript/moost-db": "^0.1.
|
|
74
|
-
"@atscript/typescript": "^0.1.
|
|
75
|
-
"@moostjs/event-http": "^0.6.
|
|
69
|
+
"@atscript/core": "^0.1.106",
|
|
70
|
+
"@atscript/db": "^0.1.156",
|
|
71
|
+
"@atscript/db-sql-tools": "^0.1.156",
|
|
72
|
+
"@atscript/db-sqlite": "^0.1.156",
|
|
73
|
+
"@atscript/moost-db": "^0.1.156",
|
|
74
|
+
"@atscript/typescript": "^0.1.106",
|
|
75
|
+
"@moostjs/event-http": "^0.6.51",
|
|
76
76
|
"@types/better-sqlite3": "^7.6.13",
|
|
77
77
|
"better-sqlite3": "^12.6.2",
|
|
78
|
-
"unplugin-atscript": "^0.1.
|
|
78
|
+
"unplugin-atscript": "^0.1.106"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@atscript/db": "^0.1.
|
|
82
|
-
"@atscript/moost-db": "^0.1.
|
|
83
|
-
"@atscript/typescript": "^0.1.
|
|
84
|
-
"@moostjs/event-http": "^0.6.
|
|
85
|
-
"@wooksjs/event-core": "^0.7.
|
|
86
|
-
"@wooksjs/event-http": "^0.7.
|
|
87
|
-
"moost": "^0.6.
|
|
81
|
+
"@atscript/db": "^0.1.156",
|
|
82
|
+
"@atscript/moost-db": "^0.1.156",
|
|
83
|
+
"@atscript/typescript": "^0.1.106",
|
|
84
|
+
"@moostjs/event-http": "^0.6.51",
|
|
85
|
+
"@wooksjs/event-core": "^0.7.29",
|
|
86
|
+
"@wooksjs/event-http": "^0.7.29",
|
|
87
|
+
"moost": "^0.6.51"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"@atscript/db": {
|