@aooth/arbac-moost 0.1.65 → 0.1.67

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-abwC0SjM.mjs";
1
+ import { r as AoothArbacClaims, t as ArbacUserProvider } from "../user.provider-DDwcuUsU.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,4 +1,4 @@
1
- import { a as ArbacDbScope, c as enforceControlsPolicy, d as NavRelationKey, f as NavTarget, i as conjoinArbacDbScopes, l as extractUsedControlValues, m as ProjectionOf, n as ArbacUserProviderToken, o as AsArbacDbController, p as OwnFieldKey, r as AoothArbacClaims, s as applyAllowedFieldsAndSet, t as ArbacUserProvider, u as ControlsOf } from "./user.provider-abwC0SjM.mjs";
1
+ import { a as ArbacDbScope, c as enforceControlsPolicy, d as NavRelationKey, f as NavTarget, i as conjoinArbacDbScopes, l as extractUsedControlValues, m as ProjectionOf, n as ArbacUserProviderToken, o as AsArbacDbController, p as OwnFieldKey, r as AoothArbacClaims, s as applyAllowedFieldsAndSet, t as ArbacUserProvider, u as ControlsOf } from "./user.provider-DDwcuUsU.mjs";
2
2
  import { Arbac, Arbac as Arbac$1, TArbacCompiledRule, TArbacEvalResult, TArbacRole, TArbacRoleForResource, TArbacRule, arbacPatternToRegex } from "@aooth/arbac-core";
3
3
  import { TAuthGuardDef } from "@moostjs/event-http";
4
4
  import { EventContext } from "@wooksjs/event-core";
@@ -129,11 +129,8 @@ declare class AsArbacDbReadableController<T extends TAtscriptAnnotatedType = TAt
129
129
  */
130
130
  protected applyMetaOverlay(meta: TMetaResponse): Promise<TMetaResponse>;
131
131
  /**
132
- * Scope-aware field-existence check — same contract as
133
- * {@link AsArbacDbController.hasField} (BUG-3): a field outside the
134
- * read-scope projection union answers `false`, so `validateInsights` rejects
135
- * `$select` / filter / sort references to it with the identical
136
- * `Unknown field "x"` 400 a nonexistent field gets.
132
+ * Scope-aware field visibility — same contract as
133
+ * {@link AsArbacDbController.hasField}.
137
134
  */
138
135
  protected hasField(path: string): boolean;
139
136
  }
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import { current, key } from "@wooksjs/event-core";
5
5
  import { Inherit, Injectable, TInterceptorPriority, defineBeforeInterceptor, getConstructor, getMoostMate, useControllerContext, useLogger } from "moost";
6
6
  import { conjoinScopeFilters, intersectControlsPolicy, isFieldAllowed, mergeScopeFilters, restrictProjection, unionControlsPolicy, unionProjections } from "@aooth/arbac";
7
7
  import { AsDbController, AsDbReadableController } from "@atscript/moost-db";
8
+ import { groupByFields } from "@uniqu/core";
8
9
  //#region src/attenuation.ts
9
10
  /**
10
11
  * Conjoin the full-authority scopes (`userScopes`, the ceiling) and the
@@ -716,9 +717,9 @@ let AsArbacDbController = class AsArbacDbController extends AsDbController {
716
717
  * controls of a request. Runs after the base validator (which checks the
717
718
  * controls DTO shape) and BEFORE the query/aggregation pipeline executes.
718
719
  *
719
- * `transformFilter` runs first on every read endpoint and caches the
720
- * evaluated scopes via `arbac.setScopes(...)`, so we read those scopes
721
- * directly here without re-evaluating ARBAC.
720
+ * Reads the scopes the route-level authorize interceptor cached via
721
+ * `arbac.setScopes(...)` — validation runs before `transformFilter`, so
722
+ * this hook never re-evaluates ARBAC.
722
723
  *
723
724
  * On a violation we throw `HttpError(403)`. moost-db's `query` / `pages` /
724
725
  * `getOne` handlers do NOT wrap `validateParsed` in try/catch, so the
@@ -735,18 +736,12 @@ let AsArbacDbController = class AsArbacDbController extends AsDbController {
735
736
  return applyArbacMetaOverlay(meta, metaAlwaysVisibleFields(this, this.readable));
736
737
  }
737
738
  /**
738
- * Field-existence check, scope-aware (BUG-3 twin of the `/meta` pruning
739
- * above): a field outside the read-scope projection union must be
740
- * indistinguishable from a field that does not exist. The base controller's
741
- * ONLY call site is `validateInsights`, which turns a `false` here into the
742
- * same `Unknown field "x"` HTTP 400 a truly nonexistent field gets — so
743
- * `$select`, filter, and sort references to a hidden field cannot be used
744
- * as an existence/value oracle. Identifier fields stay visible (reads
745
- * always return them — see {@link MetaVisibility.alwaysVisible}), and paths
746
- * under a `with`-granted relation pass through to the sub-scope's own
747
- * enforcement. Scopes were cached by the route-level authorize interceptor
748
- * before validation runs (`useArbac` setScopes), so the union reflects the
749
- * exact action being executed.
739
+ * Scope-aware field visibility (BUG-3 twin of the `/meta` pruning above): a
740
+ * field outside the read-scope projection union answers `false`, which
741
+ * moost-db's visibility hook turns into the same `Unknown field "x"` 400 a
742
+ * nonexistent field gets at every gated query position — see the docs'
743
+ * "Column-scope security floor". Identifiers stay visible; paths under a
744
+ * `with`-granted relation pass through to the sub-scope's own enforcement.
750
745
  */
751
746
  hasField(path) {
752
747
  return super.hasField(path) && isScopedFieldVisible(readCachedScopes(), path, metaAlwaysVisibleFields(this, this.readable));
@@ -808,7 +803,7 @@ function enforceControlsPolicy(policy, controls) {
808
803
  if (Array.isArray(used) && used.length === 0) continue;
809
804
  if (gate === false) throw new HttpError(403, `Control "${key}" is not allowed for your role`);
810
805
  if (Array.isArray(gate)) {
811
- const usedValues = extractUsedControlValues(key, used);
806
+ const usedValues = extractUsedControlValues(key, used, controls);
812
807
  for (const v of usedValues) if (!gate.includes(v)) throw new HttpError(403, `Control "${key}=${v}" is not allowed for your role`);
813
808
  }
814
809
  }
@@ -824,13 +819,16 @@ function enforceControlsPolicy(policy, controls) {
824
819
  * - `$with` — array of `{ name, … }` objects (per `TypedWithRelation`,
825
820
  * see `@uniqu/core` parser at `parseWithSegment`); we extract `name`.
826
821
  * Bare strings are tolerated for forward compatibility.
827
- * - `$groupBy` — array of column names (strings). Returned as-is.
822
+ * - `$groupBy` — array of column names (strings). With `controls` given,
823
+ * a calendar-bucket alias (`$select: [{ $bucket, $field, $as }]`) is
824
+ * mapped to its source `$field` (`groupByFields` from `@uniqu/core`),
825
+ * so the whitelist is checked against the column actually grouped on.
828
826
  *
829
827
  * For unknown controls we return an empty array; the caller then enforces
830
828
  * `false`-only semantics (controlled by `unionControlsPolicy`'s whitelist
831
829
  * gate, which throws if a non-whitelistable control receives a string[]).
832
830
  */
833
- function extractUsedControlValues(key, value) {
831
+ function extractUsedControlValues(key, value, controls) {
834
832
  if (!Array.isArray(value)) return [];
835
833
  if (key === "$with") {
836
834
  const out = [];
@@ -838,7 +836,10 @@ function extractUsedControlValues(key, value) {
838
836
  else if (typeof entry?.name === "string") out.push(entry.name);
839
837
  return out;
840
838
  }
841
- if (key === "$groupBy") return value.filter((x) => typeof x === "string");
839
+ if (key === "$groupBy") return controls ? groupByFields({
840
+ $select: controls.$select,
841
+ $groupBy: value
842
+ }) : value.filter((x) => typeof x === "string");
842
843
  return [];
843
844
  }
844
845
  /**
@@ -925,11 +926,8 @@ let AsArbacDbReadableController = class AsArbacDbReadableController extends AsDb
925
926
  return applyArbacMetaOverlay(meta, metaAlwaysVisibleFields(this, this.readable));
926
927
  }
927
928
  /**
928
- * Scope-aware field-existence check — same contract as
929
- * {@link AsArbacDbController.hasField} (BUG-3): a field outside the
930
- * read-scope projection union answers `false`, so `validateInsights` rejects
931
- * `$select` / filter / sort references to it with the identical
932
- * `Unknown field "x"` 400 a nonexistent field gets.
929
+ * Scope-aware field visibility — same contract as
930
+ * {@link AsArbacDbController.hasField}.
933
931
  */
934
932
  hasField(path) {
935
933
  return super.hasField(path) && isScopedFieldVisible(readCachedScopes(), path, metaAlwaysVisibleFields(this, this.readable));
@@ -152,9 +152,9 @@ declare class AsArbacDbController<T extends TAtscriptAnnotatedType = TAtscriptAn
152
152
  * controls of a request. Runs after the base validator (which checks the
153
153
  * controls DTO shape) and BEFORE the query/aggregation pipeline executes.
154
154
  *
155
- * `transformFilter` runs first on every read endpoint and caches the
156
- * evaluated scopes via `arbac.setScopes(...)`, so we read those scopes
157
- * directly here without re-evaluating ARBAC.
155
+ * Reads the scopes the route-level authorize interceptor cached via
156
+ * `arbac.setScopes(...)` — validation runs before `transformFilter`, so
157
+ * this hook never re-evaluates ARBAC.
158
158
  *
159
159
  * On a violation we throw `HttpError(403)`. moost-db's `query` / `pages` /
160
160
  * `getOne` handlers do NOT wrap `validateParsed` in try/catch, so the
@@ -163,18 +163,12 @@ declare class AsArbacDbController<T extends TAtscriptAnnotatedType = TAtscriptAn
163
163
  protected validateControls(controls: Record<string, unknown>, type: "query" | "pages" | "getOne"): string | undefined;
164
164
  protected applyMetaOverlay(meta: TMetaResponse): Promise<TMetaResponse>;
165
165
  /**
166
- * Field-existence check, scope-aware (BUG-3 twin of the `/meta` pruning
167
- * above): a field outside the read-scope projection union must be
168
- * indistinguishable from a field that does not exist. The base controller's
169
- * ONLY call site is `validateInsights`, which turns a `false` here into the
170
- * same `Unknown field "x"` HTTP 400 a truly nonexistent field gets — so
171
- * `$select`, filter, and sort references to a hidden field cannot be used
172
- * as an existence/value oracle. Identifier fields stay visible (reads
173
- * always return them — see {@link MetaVisibility.alwaysVisible}), and paths
174
- * under a `with`-granted relation pass through to the sub-scope's own
175
- * enforcement. Scopes were cached by the route-level authorize interceptor
176
- * before validation runs (`useArbac` setScopes), so the union reflects the
177
- * exact action being executed.
166
+ * Scope-aware field visibility (BUG-3 twin of the `/meta` pruning above): a
167
+ * field outside the read-scope projection union answers `false`, which
168
+ * moost-db's visibility hook turns into the same `Unknown field "x"` 400 a
169
+ * nonexistent field gets at every gated query position — see the docs'
170
+ * "Column-scope security floor". Identifiers stay visible; paths under a
171
+ * `with`-granted relation pass through to the sub-scope's own enforcement.
178
172
  */
179
173
  protected hasField(path: string): boolean;
180
174
  protected onWrite(action: "insert" | "insertMany" | "replace" | "replaceMany" | "update" | "updateMany", data: unknown): Promise<unknown>;
@@ -212,13 +206,16 @@ declare function enforceControlsPolicy(policy: Record<string, ControlGate>, cont
212
206
  * - `$with` — array of `{ name, … }` objects (per `TypedWithRelation`,
213
207
  * see `@uniqu/core` parser at `parseWithSegment`); we extract `name`.
214
208
  * Bare strings are tolerated for forward compatibility.
215
- * - `$groupBy` — array of column names (strings). Returned as-is.
209
+ * - `$groupBy` — array of column names (strings). With `controls` given,
210
+ * a calendar-bucket alias (`$select: [{ $bucket, $field, $as }]`) is
211
+ * mapped to its source `$field` (`groupByFields` from `@uniqu/core`),
212
+ * so the whitelist is checked against the column actually grouped on.
216
213
  *
217
214
  * For unknown controls we return an empty array; the caller then enforces
218
215
  * `false`-only semantics (controlled by `unionControlsPolicy`'s whitelist
219
216
  * gate, which throws if a non-whitelistable control receives a string[]).
220
217
  */
221
- declare function extractUsedControlValues(key: string, value: unknown): string[];
218
+ declare function extractUsedControlValues(key: string, value: unknown, controls?: Record<string, unknown>): string[];
222
219
  /**
223
220
  * Test-friendly internal helper — exported for unit tests and helper
224
221
  * composition; regular consumers should not call this directly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/arbac-moost",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "Moost RBAC integration for aoothjs (migrated from @moostjs/arbac)",
5
5
  "keywords": [
6
6
  "abac",
@@ -60,26 +60,26 @@
60
60
  "access": "public"
61
61
  },
62
62
  "dependencies": {
63
- "@aooth/arbac": "0.1.65",
64
- "@aooth/arbac-core": "0.1.65",
65
- "@aooth/user": "0.1.65"
63
+ "@uniqu/core": "^0.1.10",
64
+ "@aooth/arbac": "0.1.67",
65
+ "@aooth/user": "0.1.67",
66
+ "@aooth/arbac-core": "0.1.67"
66
67
  },
67
68
  "devDependencies": {
68
69
  "@atscript/core": "^0.1.93",
69
- "@atscript/db": "^0.1.131",
70
- "@atscript/db-sql-tools": "^0.1.131",
71
- "@atscript/db-sqlite": "^0.1.131",
72
- "@atscript/moost-db": "^0.1.131",
70
+ "@atscript/db": "^0.1.133",
71
+ "@atscript/db-sql-tools": "^0.1.133",
72
+ "@atscript/db-sqlite": "^0.1.133",
73
+ "@atscript/moost-db": "^0.1.133",
73
74
  "@atscript/typescript": "^0.1.93",
74
75
  "@moostjs/event-http": "^0.6.37",
75
76
  "@types/better-sqlite3": "^7.6.13",
76
- "@uniqu/core": "^0.1.8",
77
77
  "better-sqlite3": "^12.6.2",
78
78
  "unplugin-atscript": "^0.1.93"
79
79
  },
80
80
  "peerDependencies": {
81
- "@atscript/db": "^0.1.131",
82
- "@atscript/moost-db": "^0.1.131",
81
+ "@atscript/db": "^0.1.133",
82
+ "@atscript/moost-db": "^0.1.133",
83
83
  "@atscript/typescript": "^0.1.93",
84
84
  "@moostjs/event-http": "^0.6.37",
85
85
  "@wooksjs/event-core": "^0.7.23",