@aooth/arbac-moost 0.1.71 → 0.1.72
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/atscript/index.d.mts +1 -1
- package/dist/atscript/index.mjs +1 -6
- package/dist/{user.provider-CYiwr7y3.mjs → decorateMetadata-CU_mqYGT.mjs} +6 -1
- package/dist/index.d.mts +709 -210
- package/dist/index.mjs +2122 -576
- package/dist/{user.provider-C5BrFNxg.d.mts → user.provider-NMSzynGK.d.mts} +264 -51
- package/package.json +19 -19
|
@@ -1,8 +1,53 @@
|
|
|
1
|
-
import { TClassConstructor } from "moost";
|
|
2
|
-
import { ControlGate, TProjection, TProjectionChildren, TScopeFilter } from "@aooth/arbac";
|
|
3
|
-
import { TMetaResponse } from "@atscript/db";
|
|
4
|
-
import { AsDbController } from "@atscript/moost-db";
|
|
5
|
-
import { NavPropsOf, OwnPropsOf, TAtscriptAnnotatedType } from "@atscript/typescript/utils";
|
|
1
|
+
import { Mate, TClassConstructor, TMateParamMeta, TMoostMetadata } from "moost";
|
|
2
|
+
import { ControlGate, TProjection, TProjectionChildren, TScopeFieldRules, TScopeFilter } from "@aooth/arbac";
|
|
3
|
+
import { TDbRemoveGuardContext, TDbWriteAction, TDbWriteCheckContext, TDbWriteGuardContext, TMetaResponse } from "@atscript/db";
|
|
4
|
+
import { AsDbController, TDbControlsType, TDbRequestContext } from "@atscript/moost-db";
|
|
5
|
+
import { NavPropsOf, OwnPropsOf, TAtscriptAnnotatedType, TAtscriptDataType } from "@atscript/typescript/utils";
|
|
6
|
+
//#region src/arbac.mate.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* ARBAC metadata fields attached to classes and methods by ARBAC decorators.
|
|
9
|
+
*
|
|
10
|
+
* Augments the shared `TMoostMetadata` workspace via TypeScript declaration
|
|
11
|
+
* merging so other moost-aware tooling (and `getArbacMate()` readers) sees
|
|
12
|
+
* these fields with full type safety.
|
|
13
|
+
*/
|
|
14
|
+
interface TArbacMeta {
|
|
15
|
+
arbacResourceId?: string;
|
|
16
|
+
arbacActionId?: string;
|
|
17
|
+
arbacPublic?: boolean;
|
|
18
|
+
}
|
|
19
|
+
declare module "moost" {
|
|
20
|
+
interface TMoostMetadata extends TArbacMeta {}
|
|
21
|
+
}
|
|
22
|
+
type ArbacMate = Mate<TMoostMetadata & {
|
|
23
|
+
params: TMateParamMeta[];
|
|
24
|
+
}, TMoostMetadata & {
|
|
25
|
+
params: TMateParamMeta[];
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Returns the shared `Mate` instance typed with ARBAC metadata fields.
|
|
29
|
+
*
|
|
30
|
+
* All ARBAC decorators write through this typed wrapper so reads and writes
|
|
31
|
+
* stay type-checked against `TArbacMeta`.
|
|
32
|
+
*/
|
|
33
|
+
declare function getArbacMate(): ArbacMate;
|
|
34
|
+
/**
|
|
35
|
+
* Marks a controller whose `prepareRequest` authorizes moost-db's delegated
|
|
36
|
+
* handlers (the ones `getDbEndpoint` tags, e.g. `GET meta/actions/:id`) with
|
|
37
|
+
* ARBAC: a method keyed by this symbol answers whether `method` is such a
|
|
38
|
+
* handler. The authorize interceptor then skips its own evaluation for that
|
|
39
|
+
* handler (the route has no grant of its own). `AsArbacDbController` /
|
|
40
|
+
* `AsArbacDbReadableController` carry it; a controller without it keeps the
|
|
41
|
+
* interceptor's normal (fail-closed) evaluation.
|
|
42
|
+
*
|
|
43
|
+
* @since 0.1.72
|
|
44
|
+
*/
|
|
45
|
+
declare const ARBAC_DELEGATED_AUTH: unique symbol;
|
|
46
|
+
/** A controller carrying {@link ARBAC_DELEGATED_AUTH}. @since 0.1.72 */
|
|
47
|
+
interface ArbacDelegatedAuth {
|
|
48
|
+
[ARBAC_DELEGATED_AUTH](method: string): boolean;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
6
51
|
//#region src/db/scope-types.d.ts
|
|
7
52
|
/**
|
|
8
53
|
* Unwrap a `NavPropsOf<T>[K]` value to its target model type:
|
|
@@ -54,6 +99,30 @@ type ControlsOf<T> = IsUnknown<T> extends true ? Record<string, ControlGate> : {
|
|
|
54
99
|
[key: `$${string}`]: ControlGate | Array<string> | undefined;
|
|
55
100
|
};
|
|
56
101
|
//#endregion
|
|
102
|
+
//#region src/db/write-fields.d.ts
|
|
103
|
+
/**
|
|
104
|
+
* Test-friendly internal helper — exported for unit tests and helper
|
|
105
|
+
* composition; regular consumers should not call this directly.
|
|
106
|
+
*
|
|
107
|
+
* Apply the union of `allowedFields` whitelists (with `preserveFields`
|
|
108
|
+
* always preserved) and overlay each scope's `set` overrides. Returns a
|
|
109
|
+
* copy; the original `data` is not mutated.
|
|
110
|
+
*
|
|
111
|
+
* Path-aware: a top-level entry (`profile`) keeps the whole subtree; a
|
|
112
|
+
* dotted entry (`profile.name`) keeps only that branch of a nested object
|
|
113
|
+
* payload (`profile: { name, tenant }` → `profile: { name }`) — a non-object
|
|
114
|
+
* value under a partially whitelisted key is dropped, never kept whole. A
|
|
115
|
+
* dotted `set` key sets the nested path, merging into the payload's nested
|
|
116
|
+
* object.
|
|
117
|
+
*
|
|
118
|
+
* Patches (`isMergePatch` given): a nested object is written as a whole
|
|
119
|
+
* unless its path is a `@db.patch.strategy 'merge'` block, so a partially
|
|
120
|
+
* whitelisted REPLACE block is dropped (a partial block would clear the
|
|
121
|
+
* non-whitelisted leaves), and a dotted `set` under a replace block the patch
|
|
122
|
+
* does not carry is skipped (adding the block would clear its other leaves).
|
|
123
|
+
*/
|
|
124
|
+
declare function applyAllowedFieldsAndSet(data: unknown, scopes: ArbacDbScope[], preserveFields?: readonly string[], isMergePatch?: (objectPath: string) => boolean): unknown;
|
|
125
|
+
//#endregion
|
|
57
126
|
//#region src/db/as-arbac-db-controller.d.ts
|
|
58
127
|
/**
|
|
59
128
|
* Contract returned by an ARBAC role's scope predicate on a DB-backed resource.
|
|
@@ -71,9 +140,23 @@ type ControlsOf<T> = IsUnknown<T> extends true ? Record<string, ControlGate> : {
|
|
|
71
140
|
* }
|
|
72
141
|
* }
|
|
73
142
|
* ```
|
|
143
|
+
*
|
|
144
|
+
* The framework never reads a custom field — the app unions it with its own
|
|
145
|
+
* rule. Where scope lists are CONJOINED (credential attenuation), each custom
|
|
146
|
+
* field needs a conjunction rule registered once with
|
|
147
|
+
* `MoostArbac.registerScopeFields`; a custom field without one fails the
|
|
148
|
+
* evaluation (it is never silently dropped). Since 0.1.72.
|
|
74
149
|
*/
|
|
75
150
|
interface ArbacDbScope<T = unknown> {
|
|
76
151
|
filter?: TScopeFilter;
|
|
152
|
+
/**
|
|
153
|
+
* Post-write WITH CHECK filter: every row an insert / replace / update
|
|
154
|
+
* writes must match it (Postgres RLS semantics). Defaults to `filter`;
|
|
155
|
+
* `{}` disables the check. Conjoined (`$and`) like `filter` by
|
|
156
|
+
* `defineTableAccess` part scopes and credential attenuation; unioned
|
|
157
|
+
* across roles. Enforced by the ARBAC DB controllers from 0.1.72.
|
|
158
|
+
*/
|
|
159
|
+
check?: TScopeFilter;
|
|
77
160
|
projection?: ProjectionOf<T>;
|
|
78
161
|
set?: Partial<Record<OwnFieldKey<T>, unknown>>;
|
|
79
162
|
allowedFields?: Array<OwnFieldKey<T>>;
|
|
@@ -100,21 +183,47 @@ interface ArbacDbScope<T = unknown> {
|
|
|
100
183
|
* `?$with=<name>`. Recursive — each sub-scope has the same shape and can
|
|
101
184
|
* declare its own `with` for nested expansions (e.g. tasks → comments → task).
|
|
102
185
|
*
|
|
103
|
-
* **
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
186
|
+
* **Which policy the joined rows obey** (0.1.72+):
|
|
187
|
+
* - Declared in any role → the union of those sub-scopes governs the
|
|
188
|
+
* joined rows (silent roles contribute nothing).
|
|
189
|
+
* - Not declared → the caller's own `query` grant on the related table's
|
|
190
|
+
* ARBAC resource (its filter, projection, controls and `with`); no grant
|
|
191
|
+
* → `Unknown relation` (400), identical to a nonexistent relation.
|
|
107
192
|
*
|
|
108
|
-
* **Union across roles**: when multiple roles
|
|
193
|
+
* **Union across roles**: when multiple roles declare the same relation,
|
|
109
194
|
* their `with[name]` sub-scopes are unioned at every nested level using the
|
|
110
195
|
* existing `unionProjections` / `mergeScopeFilters` / `unionControlsPolicy`
|
|
111
196
|
* primitives (additive: broader access wins, same rules as the parent).
|
|
112
|
-
*
|
|
113
|
-
* **Silence wins**: if no role declares `with.<name>`, expansion is
|
|
114
|
-
* unrestricted (matches `controls.$with` whitelist semantics; the gate
|
|
115
|
-
* still applies if declared).
|
|
197
|
+
* The `controls.$with` gate still applies either way.
|
|
116
198
|
*/
|
|
117
199
|
with?: WithOf<T>;
|
|
200
|
+
/**
|
|
201
|
+
* Nav relations (`TO` / `FROM` / `VIA` props) this scope may write THROUGH
|
|
202
|
+
* — nested inserts / updates of related rows in the parent's payload.
|
|
203
|
+
* Default deny: a write payload carrying a nav key not listed by any of
|
|
204
|
+
* the caller's write scopes is rejected with 403. Parent authority — the
|
|
205
|
+
* related rows are written under the PARENT scope, the related table's
|
|
206
|
+
* own ARBAC policy is not consulted. Enforced by the ARBAC DB controllers
|
|
207
|
+
* from 0.1.72.
|
|
208
|
+
*/
|
|
209
|
+
nestedWrites?: Array<NavRelationKey<T>>;
|
|
210
|
+
/**
|
|
211
|
+
* FK target checks (opt-in): a write that SETS one of these foreign keys
|
|
212
|
+
* (insert / replace rows; update patches touching it) must reference rows
|
|
213
|
+
* the caller can READ under its own `query` grant on the target table's
|
|
214
|
+
* ARBAC resource — else 403 `Referenced row "<field>" is outside your
|
|
215
|
+
* scope`. `true` = every FK of the table; a list names FK fields or the TO
|
|
216
|
+
* relations they back. A target without a registered ARBAC DB controller or
|
|
217
|
+
* without a read grant → 403. Null / absent FKs are not checked.
|
|
218
|
+
*
|
|
219
|
+
* **Union across roles**: enforced for an FK only when EVERY allowing write
|
|
220
|
+
* scope enables it — write scopes grant additively, so a role without the
|
|
221
|
+
* flag grants unconstrained writes of that FK. Credential attenuation
|
|
222
|
+
* enforces what either side enforces.
|
|
223
|
+
*
|
|
224
|
+
* @since 0.1.72
|
|
225
|
+
*/
|
|
226
|
+
checkRefs?: true | Array<OwnFieldKey<T> | NavRelationKey<T>>;
|
|
118
227
|
}
|
|
119
228
|
/**
|
|
120
229
|
* Per-relation sub-scope map. For `T = unknown` falls back to the legacy
|
|
@@ -125,9 +234,43 @@ interface ArbacDbScope<T = unknown> {
|
|
|
125
234
|
* because it must reference `ArbacDbScope` recursively.
|
|
126
235
|
*/
|
|
127
236
|
type WithOf<T> = unknown extends T ? Record<string, ArbacDbScope> : { [K in NavRelationKey<T>]?: K extends keyof NavPropsOf<T> ? ArbacDbScope<NavTarget<NavPropsOf<T>[K]>> : ArbacDbScope; };
|
|
237
|
+
/**
|
|
238
|
+
* ARBAC-enforcing CRUD controller over an atscript-db table.
|
|
239
|
+
*
|
|
240
|
+
* Every endpoint — `@DbAction` handlers included — resolves the caller's
|
|
241
|
+
* scopes first in `prepareRequest` (fail closed: no grant → 403, `@Public()`
|
|
242
|
+
* does not bypass it); every other hook reads them. Reads apply
|
|
243
|
+
* the scopes' row filter, projection, controls and `with` sub-scopes. Writes
|
|
244
|
+
* enforce, per request:
|
|
245
|
+
*
|
|
246
|
+
* - **nested writes** — a payload key naming a nav relation → 403 unless a
|
|
247
|
+
* write scope lists it in `nestedWrites`;
|
|
248
|
+
* - **`allowedFields` / `set`** — path-aware whitelist (identifiers, the
|
|
249
|
+
* version column and `$cas` always kept) and forced values;
|
|
250
|
+
* - **USING** — update / replace / delete target only rows matching the
|
|
251
|
+
* scope `filter`, checked on the exact pre-image inside the write's
|
|
252
|
+
* transaction (missing or out of scope → the same 404);
|
|
253
|
+
* - **`checkRefs`** (opt-in) — an FK the write sets must reference a row
|
|
254
|
+
* the caller can read on the target table (else 403);
|
|
255
|
+
* - **WITH CHECK** — every written row must match the scope `check`
|
|
256
|
+
* (default: `filter`; `{}` opts out) after the write, inside the
|
|
257
|
+
* transaction (→ 403, rolled back). On adapters without real transactions
|
|
258
|
+
* the check runs in memory before the write and fails closed on anything
|
|
259
|
+
* it cannot decide.
|
|
260
|
+
*/
|
|
128
261
|
declare class AsArbacDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType> extends AsDbController<T> {
|
|
129
|
-
|
|
130
|
-
protected
|
|
262
|
+
/** Makes this controller the `$with` policy source for its table's ARBAC resource. */
|
|
263
|
+
protected readonly _arbacRelationTarget: true;
|
|
264
|
+
/**
|
|
265
|
+
* Resolve the request's ARBAC scopes before anything else consults them:
|
|
266
|
+
* reuse the ones the authorize interceptor cached, else evaluate the
|
|
267
|
+
* handler's resource/action now — a deny is a 403 (`arbacPublic` never
|
|
268
|
+
* bypasses a DB controller). On read endpoints the `$with` relation policy
|
|
269
|
+
* is resolved for the requested relations.
|
|
270
|
+
*/
|
|
271
|
+
protected prepareRequest(ctx: TDbRequestContext): Promise<void>;
|
|
272
|
+
protected transformFilter(filter: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
273
|
+
protected transformProjection(projection?: TProjection): TProjection | undefined;
|
|
131
274
|
/**
|
|
132
275
|
* Translate `@uniqu/url` parser failures into HTTP 400 instead of letting them
|
|
133
276
|
* bubble as 500. The base `parseQueryString` calls `parseUrl()` directly with
|
|
@@ -152,32 +295,69 @@ declare class AsArbacDbController<T extends TAtscriptAnnotatedType = TAtscriptAn
|
|
|
152
295
|
* controls of a request. Runs after the base validator (which checks the
|
|
153
296
|
* controls DTO shape) and BEFORE the query/aggregation pipeline executes.
|
|
154
297
|
*
|
|
155
|
-
* Reads the scopes
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* On a violation we throw `HttpError(403)`. moost-db's `query` / `pages` /
|
|
160
|
-
* `getOne` handlers do NOT wrap `validateParsed` in try/catch, so the
|
|
161
|
-
* thrown error bubbles to moost which translates it to a 403 response.
|
|
298
|
+
* Reads the scopes `prepareRequest` resolved — unresolved scopes are a 403
|
|
299
|
+
* (never "unrestricted"). A violation throws `HttpError(403)`, which
|
|
300
|
+
* moost-db's read handlers let bubble.
|
|
162
301
|
*/
|
|
163
|
-
protected validateControls(controls: Record<string, unknown>, type:
|
|
302
|
+
protected validateControls(controls: Record<string, unknown>, type: TDbControlsType): string | undefined;
|
|
164
303
|
protected applyMetaOverlay(meta: TMetaResponse): Promise<TMetaResponse>;
|
|
165
304
|
/**
|
|
166
|
-
*
|
|
305
|
+
* `GET /meta/form/:name` serves a form only when the caller may run at
|
|
306
|
+
* least one of the actions taking it as input (else the unknown-form 404).
|
|
307
|
+
*/
|
|
308
|
+
protected authorizeForm(_name: string, actionNames: readonly string[]): Promise<boolean>;
|
|
309
|
+
/**
|
|
310
|
+
* The rows each `@DbAction` may run on: the caller's grant on that action
|
|
311
|
+
* (see `arbacActionRowScope`). Enforced by the action gate and reflected
|
|
312
|
+
* in `$actions` and `GET /meta/actions/:id`. Since 0.1.72.
|
|
313
|
+
*/
|
|
314
|
+
protected actionRowScope(name: string): Promise<Record<string, unknown> | undefined>;
|
|
315
|
+
/**
|
|
316
|
+
* The row-level actions `$actions` and `GET /meta/actions/:id` may list:
|
|
317
|
+
* those the caller holds a grant on (the `/meta` overlay's rule, without
|
|
318
|
+
* building the overlay). Since 0.1.72.
|
|
319
|
+
*/
|
|
320
|
+
protected allowedActions(names: readonly string[]): Promise<string[]>;
|
|
321
|
+
/**
|
|
322
|
+
* moost-db handlers that delegate their authorization to `prepareRequest`
|
|
323
|
+
* (`getDbEndpoint` — `GET /meta/actions/:id`): the authorize interceptor
|
|
324
|
+
* skips them; `prepareRequest` serves them iff the caller may run at least
|
|
325
|
+
* one row-level action (else 403). Since 0.1.72.
|
|
326
|
+
*/
|
|
327
|
+
[ARBAC_DELEGATED_AUTH](method: string): boolean;
|
|
328
|
+
/**
|
|
329
|
+
* Scope-aware field visibility (the twin of the `/meta` pruning above): a
|
|
167
330
|
* field outside the read-scope projection union answers `false`, which
|
|
168
331
|
* moost-db's visibility hook turns into the same `Unknown field "x"` 400 a
|
|
169
332
|
* nonexistent field gets at every gated query position — see the docs'
|
|
170
333
|
* "Column-scope security floor". Identifiers stay visible. A `rel.x` path
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
334
|
+
* (`$with=rel(x>1)`, `$with=rel($sort=x)`, `$with=rel($select=x)`) is
|
|
335
|
+
* checked as `x` against the relation's policy, recursively; the related
|
|
336
|
+
* table's own PK / `preferredId` stay visible. Unresolved scopes hide every
|
|
337
|
+
* field.
|
|
175
338
|
*/
|
|
176
339
|
protected hasField(path: string): boolean;
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
340
|
+
/**
|
|
341
|
+
* Untrusted-body stage: reject nested writes the scopes do not opt in
|
|
342
|
+
* (`nestedWrites`), then apply the `allowedFields` whitelist and `set`.
|
|
343
|
+
*/
|
|
344
|
+
protected onWrite(action: TDbWriteAction, data: unknown): unknown;
|
|
345
|
+
/** `true` when a patch merges into the nested object at `path` (`@db.patch.strategy 'merge'`). */
|
|
346
|
+
private isMergeBlock;
|
|
347
|
+
/** In-transaction stage — USING, `checkRefs`, and WITH CHECK on non-transactional adapters. */
|
|
348
|
+
protected guardWrite(ctx: TDbWriteGuardContext<TAtscriptDataType<T>>): Promise<void>;
|
|
349
|
+
/** USING for deletes: the exact row the delete targets must match the scope filter (else 404). */
|
|
350
|
+
protected guardRemove(ctx: TDbRemoveGuardContext<TAtscriptDataType<T>>): Promise<void>;
|
|
351
|
+
/**
|
|
352
|
+
* WITH CHECK: after the write, inside its transaction, every written row
|
|
353
|
+
* must match the scopes' `check` (default `filter`) — else 403 and the
|
|
354
|
+
* write rolls back. Non-transactional adapters were checked in
|
|
355
|
+
* {@link guardWrite}.
|
|
356
|
+
*/
|
|
357
|
+
protected checkWrite(ctx: TDbWriteCheckContext): Promise<void>;
|
|
358
|
+
/** `this.table` as the write guards see it (the generic `T` defeats structural matching). */
|
|
359
|
+
private get guardedTable();
|
|
360
|
+
private preservedWriteFields;
|
|
181
361
|
}
|
|
182
362
|
/**
|
|
183
363
|
* Test-friendly internal helper — exported for unit tests and helper
|
|
@@ -219,15 +399,22 @@ declare function enforceControlsPolicy(policy: Record<string, ControlGate>, cont
|
|
|
219
399
|
* gate, which throws if a non-whitelistable control receives a string[]).
|
|
220
400
|
*/
|
|
221
401
|
declare function extractUsedControlValues(key: string, value: unknown, controls?: Record<string, unknown>): string[];
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/db/write-refs.d.ts
|
|
404
|
+
/** A `@db.rel.FK` entry as atscript-db's readable exposes it. */
|
|
405
|
+
interface RefForeignKey {
|
|
406
|
+
readonly fields: readonly string[];
|
|
407
|
+
readonly targetFields: readonly string[];
|
|
408
|
+
readonly targetTable?: string;
|
|
409
|
+
readonly alias?: string;
|
|
410
|
+
readonly targetTypeRef?: () => unknown;
|
|
411
|
+
}
|
|
412
|
+
/** The table surface `checkRefs` resolves names against (`AtscriptDbReadable` satisfies it). */
|
|
413
|
+
interface RefTableSource {
|
|
414
|
+
readonly foreignKeys?: ReadonlyMap<string, RefForeignKey>;
|
|
415
|
+
/** The FK a TO relation is backed by (atscript-db's `readable.foreignKeyOf`). @since 0.1.72 */
|
|
416
|
+
foreignKeyOf?(relationName: string): RefForeignKey | undefined;
|
|
417
|
+
}
|
|
231
418
|
//#endregion
|
|
232
419
|
//#region src/attenuation.d.ts
|
|
233
420
|
/**
|
|
@@ -241,15 +428,23 @@ declare function applyAllowedFieldsAndSet(data: unknown, scopes: ArbacDbScope[],
|
|
|
241
428
|
*
|
|
242
429
|
* - `roles` — assume a SUBSET of the user's roles. `[]` = no roles (deny-all,
|
|
243
430
|
* fail-closed); an OMITTED key = keep all the user's roles (attrs-only
|
|
244
|
-
* narrowing); a role the user lacks is dropped by the intersection
|
|
431
|
+
* narrowing); a role the user lacks is dropped by the intersection (unless
|
|
432
|
+
* `allowUnheldRoles`).
|
|
245
433
|
* - `attrs` — extra/overriding inputs to scope predicates, keyed by the target
|
|
246
434
|
* user-attribute name, intended to narrow scopes. They are merged LOCALLY
|
|
247
435
|
* into the credential pass only and clipped by the scope conjunction, so they
|
|
248
436
|
* can never widen beyond the user.
|
|
437
|
+
* - `allowUnheldRoles` — "view as": evaluate the claimed `roles` as given,
|
|
438
|
+
* including roles the user does not hold (unknown role ids still grant
|
|
439
|
+
* nothing). The result is still conjoined with the user's full authority,
|
|
440
|
+
* so it never widens beyond the user — it previews the claimed roles'
|
|
441
|
+
* surface CLIPPED to the user's own. Opt-in: the app decides who may issue
|
|
442
|
+
* such a credential (e.g. gate it behind a privilege). Since 0.1.72.
|
|
249
443
|
*/
|
|
250
444
|
interface AoothArbacClaims {
|
|
251
445
|
roles?: string[];
|
|
252
446
|
attrs?: Record<string, unknown>;
|
|
447
|
+
allowUnheldRoles?: boolean;
|
|
253
448
|
}
|
|
254
449
|
/**
|
|
255
450
|
* Conjoin the full-authority scopes (`userScopes`, the ceiling) and the
|
|
@@ -257,11 +452,12 @@ interface AoothArbacClaims {
|
|
|
257
452
|
* `ArbacDbScope`. A row/field/control is admitted only if BOTH passes admit
|
|
258
453
|
* it — the normative restrict-only clip that closes the attr-widen hole.
|
|
259
454
|
*
|
|
260
|
-
* Each side is first UNIONed
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
* additive union helpers, which
|
|
455
|
+
* Each side is first UNIONed, then the two results are CONJOINED facet by
|
|
456
|
+
* facet with `conjoinScopes` from `@aooth/arbac` (`$and` filters and WITH
|
|
457
|
+
* CHECK filters, path-wise projection ∩, deny-wins controls, `allowedFields`
|
|
458
|
+
* / `nestedWrites` intersection, `set` combined with the USER winning a key
|
|
459
|
+
* conflict, recursive `with`) — never the additive union helpers, which
|
|
460
|
+
* would silently widen.
|
|
265
461
|
*
|
|
266
462
|
* Returned as a single-element list so every downstream scope-application
|
|
267
463
|
* site (which UNIONs the cached scope list per facet) sees the identity of a
|
|
@@ -274,9 +470,26 @@ interface AoothArbacClaims {
|
|
|
274
470
|
* closed). When NO field survives (`{a:1}` ∩ `{a:0}`, disjoint whitelists),
|
|
275
471
|
* the composite keeps the user's projection and gets a match-nothing filter —
|
|
276
472
|
* an empty field set must never read as the unrestricted `{}`. `with`
|
|
277
|
-
* sub-scopes are conjoined without a schema.
|
|
473
|
+
* sub-scopes are conjoined without a schema. `checkRefs` enforces what either
|
|
474
|
+
* side enforces (resolved against `refTable`'s foreign keys when given — the
|
|
475
|
+
* evaluated table). A custom (declaration-merged) scope field is conjoined by
|
|
476
|
+
* its `fields` rule; without one this THROWS — never dropped (fail closed).
|
|
477
|
+
*
|
|
478
|
+
* The third argument is the options object, or (legacy form) `childrenOf`.
|
|
278
479
|
*/
|
|
279
|
-
declare function conjoinArbacDbScopes(userScopes: ArbacDbScope[], credScopes: ArbacDbScope[],
|
|
480
|
+
declare function conjoinArbacDbScopes(userScopes: ArbacDbScope[], credScopes: ArbacDbScope[], opts?: TProjectionChildren | ConjoinArbacDbScopesOptions): ArbacDbScope[];
|
|
481
|
+
/** Options of {@link conjoinArbacDbScopes}. @since 0.1.72 */
|
|
482
|
+
interface ConjoinArbacDbScopesOptions {
|
|
483
|
+
/** The evaluated table's schema lookup — subtracts a nested exclusion exactly. */
|
|
484
|
+
childrenOf?: TProjectionChildren;
|
|
485
|
+
/** The evaluated table — resolves `checkRefs` names to its foreign keys. */
|
|
486
|
+
refTable?: RefTableSource;
|
|
487
|
+
/**
|
|
488
|
+
* Custom scope field rules (`MoostArbac.registerScopeFields`); a custom
|
|
489
|
+
* field without one throws.
|
|
490
|
+
*/
|
|
491
|
+
fields?: TScopeFieldRules<ArbacDbScope>;
|
|
492
|
+
}
|
|
280
493
|
//#endregion
|
|
281
494
|
//#region src/user.provider.d.ts
|
|
282
495
|
/**
|
|
@@ -334,4 +547,4 @@ declare abstract class ArbacUserProvider<TUserAttrs extends object = object> {
|
|
|
334
547
|
*/
|
|
335
548
|
declare const ArbacUserProviderToken: TClassConstructor<ArbacUserProvider>;
|
|
336
549
|
//#endregion
|
|
337
|
-
export {
|
|
550
|
+
export { ProjectionOf as _, conjoinArbacDbScopes as a, TArbacMeta as b, ArbacDbScope as c, extractUsedControlValues as d, applyAllowedFieldsAndSet as f, OwnFieldKey as g, NavTarget as h, ConjoinArbacDbScopesOptions as i, AsArbacDbController as l, NavRelationKey as m, ArbacUserProviderToken as n, RefForeignKey as o, ControlsOf as p, AoothArbacClaims as r, RefTableSource as s, ArbacUserProvider as t, enforceControlsPolicy as u, ARBAC_DELEGATED_AUTH as v, getArbacMate as x, ArbacDelegatedAuth as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aooth/arbac-moost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"description": "Moost RBAC integration for aoothjs (migrated from @moostjs/arbac)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"abac",
|
|
@@ -61,30 +61,30 @@
|
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@uniqu/core": "^0.1.11",
|
|
64
|
-
"@aooth/arbac": "0.1.
|
|
65
|
-
"@aooth/user": "0.1.
|
|
66
|
-
"@aooth/arbac-core": "0.1.
|
|
64
|
+
"@aooth/arbac": "0.1.72",
|
|
65
|
+
"@aooth/user": "0.1.72",
|
|
66
|
+
"@aooth/arbac-core": "0.1.72"
|
|
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.97",
|
|
70
|
+
"@atscript/db": "^0.1.145",
|
|
71
|
+
"@atscript/db-sql-tools": "^0.1.145",
|
|
72
|
+
"@atscript/db-sqlite": "^0.1.145",
|
|
73
|
+
"@atscript/moost-db": "^0.1.145",
|
|
74
|
+
"@atscript/typescript": "^0.1.97",
|
|
75
|
+
"@moostjs/event-http": "^0.6.41",
|
|
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.97"
|
|
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.145",
|
|
82
|
+
"@atscript/moost-db": "^0.1.145",
|
|
83
|
+
"@atscript/typescript": "^0.1.97",
|
|
84
|
+
"@moostjs/event-http": "^0.6.41",
|
|
85
|
+
"@wooksjs/event-core": "^0.7.24",
|
|
86
|
+
"@wooksjs/event-http": "^0.7.24",
|
|
87
|
+
"moost": "^0.6.41"
|
|
88
88
|
},
|
|
89
89
|
"peerDependenciesMeta": {
|
|
90
90
|
"@atscript/db": {
|