@aooth/arbac-moost 0.1.56 → 0.1.57

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.d.mts CHANGED
@@ -170,7 +170,23 @@ interface MetaVisibility {
170
170
  alwaysVisible: ReadonlySet<string>;
171
171
  /** Relation names granted via `with.<name>` (see {@link collectWithGrantNames}). */
172
172
  withGrants: ReadonlySet<string>;
173
+ /**
174
+ * Fields the principal's WRITE scopes allow (union of `allowedFields`, or
175
+ * `"all"` for an unrestricted write grant). A field that is writable but not
176
+ * read-visible is NOT pruned from `/meta` — it survives with a `db.writeOnly`
177
+ * stamp (type only; rows/projections never carry it), so generic forms and
178
+ * client preflight validators can still SET sealed fields (e.g. credentials
179
+ * behind a read projection). Absent → nothing extra survives.
180
+ */
181
+ writable?: ReadonlySet<string> | "all";
173
182
  }
183
+ /**
184
+ * Union of write-scope `allowedFields`; `"all"` for field-unrestricted write
185
+ * access. Mirrors `prepareScopeOverlay`: the whitelist exists only when at
186
+ * least one scope carries an `allowedFields` array — scoped-but-unlisted
187
+ * writes are field-unrestricted.
188
+ */
189
+ declare function collectWritableFields(scopes: ArbacDbScope[], unrestricted: boolean): ReadonlySet<string> | "all" | undefined;
174
190
  /**
175
191
  * Whether a flattened dot-path field exists for this principal. A path under
176
192
  * a `with`-granted relation passes unconditionally — the sub-scope governs
@@ -247,4 +263,4 @@ declare function applyArbacMetaOverlay(meta: TMetaResponse, alwaysVisible: Reado
247
263
  */
248
264
  declare class MoostArbac<TUserAttrs extends object, TScope extends object> extends Arbac$1<TUserAttrs, TScope> {}
249
265
  //#endregion
250
- export { type AoothArbacClaims, Arbac, ArbacAction, ArbacAuthorize, ArbacDbScope, ArbacResource, ArbacUserProvider, ArbacUserProviderToken, AsArbacDbController, AsArbacDbReadableController, type ControlGate, type ControlsOf, MetaVisibility, MoostArbac, type NavRelationKey, type NavTarget, type OwnFieldKey, type ProjectionOf, type TArbacCompiledRule, type TArbacEvalResult, TArbacMeta, type TArbacRole, type TArbacRoleForResource, type TArbacRule, applyAllowedFieldsAndSet, applyArbacMetaOverlay, arbacAuthorizeInterceptor, arbacPatternToRegex, collectWithGrantNames, conjoinArbacDbScopes, enforceControlsPolicy, extractUsedControlValues, getArbacMate, isMetaFieldVisible, isScopedFieldVisible, metaAlwaysVisibleFields, pruneMetaByVisibility, unionScopeProjection, useArbac };
266
+ export { type AoothArbacClaims, Arbac, ArbacAction, ArbacAuthorize, ArbacDbScope, ArbacResource, ArbacUserProvider, ArbacUserProviderToken, AsArbacDbController, AsArbacDbReadableController, type ControlGate, type ControlsOf, MetaVisibility, MoostArbac, type NavRelationKey, type NavTarget, type OwnFieldKey, type ProjectionOf, type TArbacCompiledRule, type TArbacEvalResult, TArbacMeta, type TArbacRole, type TArbacRoleForResource, type TArbacRule, applyAllowedFieldsAndSet, applyArbacMetaOverlay, arbacAuthorizeInterceptor, arbacPatternToRegex, collectWithGrantNames, collectWritableFields, conjoinArbacDbScopes, enforceControlsPolicy, extractUsedControlValues, getArbacMate, isMetaFieldVisible, isScopedFieldVisible, metaAlwaysVisibleFields, pruneMetaByVisibility, unionScopeProjection, useArbac };
package/dist/index.mjs CHANGED
@@ -263,6 +263,33 @@ function collectWithGrantNames(scopes) {
263
263
  for (const s of scopes) if (s.with) for (const name of Object.keys(s.with)) out.add(name);
264
264
  return out;
265
265
  }
266
+ /** Exact-or-ancestor membership: `credit.credentials.user` matches a `credit.credentials` grant. */
267
+ function isPathWritable(path, writable) {
268
+ if (!writable) return false;
269
+ if (writable === "all") return true;
270
+ if (writable.has(path)) return true;
271
+ let pos = path.length;
272
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) if (writable.has(path.slice(0, pos))) return true;
273
+ return false;
274
+ }
275
+ /**
276
+ * Union of write-scope `allowedFields`; `"all"` for field-unrestricted write
277
+ * access. Mirrors `prepareScopeOverlay`: the whitelist exists only when at
278
+ * least one scope carries an `allowedFields` array — scoped-but-unlisted
279
+ * writes are field-unrestricted.
280
+ */
281
+ function collectWritableFields(scopes, unrestricted) {
282
+ if (unrestricted) return "all";
283
+ if (scopes.length === 0) return void 0;
284
+ const out = /* @__PURE__ */ new Set();
285
+ let sawWhitelist = false;
286
+ for (const s of scopes) if (Array.isArray(s.allowedFields)) {
287
+ sawWhitelist = true;
288
+ for (const f of s.allowedFields) out.add(f);
289
+ }
290
+ if (!sawWhitelist) return "all";
291
+ return out.size > 0 ? out : void 0;
292
+ }
266
293
  /**
267
294
  * Whether a flattened dot-path field exists for this principal. A path under
268
295
  * a `with`-granted relation passes unconditionally — the sub-scope governs
@@ -295,6 +322,12 @@ function isMetaFieldVisible(path, vis) {
295
322
  function pruneMetaByVisibility(meta, vis) {
296
323
  const fields = {};
297
324
  for (const [path, fieldMeta] of Object.entries(meta.fields)) if (isMetaFieldVisible(path, vis)) fields[path] = fieldMeta;
325
+ else if (isPathWritable(path, vis.writable)) fields[path] = {
326
+ ...fieldMeta,
327
+ writeOnly: true,
328
+ filterable: false,
329
+ sortable: false
330
+ };
298
331
  const relationNames = new Set(meta.relations.map((r) => r.name));
299
332
  const relations = meta.relations.filter((r) => isMetaFieldVisible(r.name, vis));
300
333
  const out = {
@@ -325,7 +358,16 @@ function pruneSerializedType(node, basePath, vis, relationNames) {
325
358
  if (isMetaFieldVisible(name, vis)) props[name] = prop;
326
359
  continue;
327
360
  }
328
- if (!isMetaFieldVisible(path, vis)) continue;
361
+ if (!isMetaFieldVisible(path, vis)) {
362
+ if (isPathWritable(path, vis.writable)) props[name] = {
363
+ ...prop,
364
+ metadata: {
365
+ ...prop.metadata,
366
+ "db.writeOnly": true
367
+ }
368
+ };
369
+ continue;
370
+ }
329
371
  props[name] = pruneSerializedType(prop, path, vis, relationNames);
330
372
  }
331
373
  return {
@@ -451,10 +493,17 @@ async function applyArbacMetaOverlay(meta, alwaysVisible) {
451
493
  crud: filteredCrud
452
494
  };
453
495
  let readUnrestricted = false;
496
+ let writeUnrestricted = false;
454
497
  const readScopes = [];
498
+ const writeScopes = [];
455
499
  for (let i = 0; i < crudKeys.length; i++) {
456
- if (!crudResults[i].allowed || WRITE_CRUD_OPS.has(crudKeys[i])) continue;
500
+ if (!crudResults[i].allowed) continue;
457
501
  const scopes = crudResults[i].scopes;
502
+ if (WRITE_CRUD_OPS.has(crudKeys[i])) {
503
+ if (!scopes || scopes.length === 0) writeUnrestricted = true;
504
+ else writeScopes.push(...scopes);
505
+ continue;
506
+ }
458
507
  if (!scopes || scopes.length === 0) readUnrestricted = true;
459
508
  else readScopes.push(...scopes);
460
509
  }
@@ -463,7 +512,8 @@ async function applyArbacMetaOverlay(meta, alwaysVisible) {
463
512
  if (allowed) overlaid = pruneMetaByVisibility(overlaid, {
464
513
  allowed,
465
514
  alwaysVisible,
466
- withGrants: collectWithGrantNames(readScopes)
515
+ withGrants: collectWithGrantNames(readScopes),
516
+ writable: collectWritableFields(writeScopes, writeUnrestricted)
467
517
  });
468
518
  }
469
519
  return overlaid;
@@ -871,4 +921,4 @@ let AsArbacDbReadableController = class AsArbacDbReadableController extends AsDb
871
921
  };
872
922
  AsArbacDbReadableController = __decorate([Inherit()], AsArbacDbReadableController);
873
923
  //#endregion
874
- export { Arbac, ArbacAction, ArbacAuthorize, ArbacResource, ArbacUserProvider, ArbacUserProviderToken, AsArbacDbController, AsArbacDbReadableController, MoostArbac, applyAllowedFieldsAndSet, applyArbacMetaOverlay, arbacAuthorizeInterceptor, arbacPatternToRegex, collectWithGrantNames, conjoinArbacDbScopes, enforceControlsPolicy, extractUsedControlValues, getArbacMate, isMetaFieldVisible, isScopedFieldVisible, metaAlwaysVisibleFields, pruneMetaByVisibility, unionScopeProjection, useArbac };
924
+ export { Arbac, ArbacAction, ArbacAuthorize, ArbacResource, ArbacUserProvider, ArbacUserProviderToken, AsArbacDbController, AsArbacDbReadableController, MoostArbac, applyAllowedFieldsAndSet, applyArbacMetaOverlay, arbacAuthorizeInterceptor, arbacPatternToRegex, collectWithGrantNames, collectWritableFields, conjoinArbacDbScopes, enforceControlsPolicy, extractUsedControlValues, getArbacMate, isMetaFieldVisible, isScopedFieldVisible, metaAlwaysVisibleFields, pruneMetaByVisibility, unionScopeProjection, useArbac };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aooth/arbac-moost",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "Moost RBAC integration for aoothjs (migrated from @moostjs/arbac)",
5
5
  "keywords": [
6
6
  "abac",
@@ -60,16 +60,16 @@
60
60
  "access": "public"
61
61
  },
62
62
  "dependencies": {
63
- "@aooth/arbac": "0.1.56",
64
- "@aooth/arbac-core": "0.1.56",
65
- "@aooth/user": "0.1.56"
63
+ "@aooth/arbac": "0.1.57",
64
+ "@aooth/user": "0.1.57",
65
+ "@aooth/arbac-core": "0.1.57"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@atscript/core": "^0.1.85",
69
- "@atscript/db": "^0.1.122",
70
- "@atscript/db-sql-tools": "^0.1.122",
71
- "@atscript/db-sqlite": "^0.1.122",
72
- "@atscript/moost-db": "^0.1.122",
69
+ "@atscript/db": "^0.1.123",
70
+ "@atscript/db-sql-tools": "^0.1.123",
71
+ "@atscript/db-sqlite": "^0.1.123",
72
+ "@atscript/moost-db": "^0.1.123",
73
73
  "@atscript/typescript": "^0.1.85",
74
74
  "@moostjs/event-http": "^0.6.33",
75
75
  "@types/better-sqlite3": "^7.6.13",
@@ -78,8 +78,8 @@
78
78
  "unplugin-atscript": "^0.1.85"
79
79
  },
80
80
  "peerDependencies": {
81
- "@atscript/db": "^0.1.122",
82
- "@atscript/moost-db": "^0.1.122",
81
+ "@atscript/db": "^0.1.123",
82
+ "@atscript/moost-db": "^0.1.123",
83
83
  "@atscript/typescript": "^0.1.85",
84
84
  "@moostjs/event-http": "^0.6.33",
85
85
  "@wooksjs/event-core": "^0.7.21",