@objectstack/plugin-security 9.8.0 → 9.9.1
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 +54 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +68 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +68 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -2261,17 +2261,29 @@ var defaultPermissionSets = [
|
|
|
2261
2261
|
operation: "all",
|
|
2262
2262
|
using: "organization_id = current_user.organization_id"
|
|
2263
2263
|
},
|
|
2264
|
+
// Owner-scoped writes/deletes for rank-and-file members: you may modify
|
|
2265
|
+
// and delete the records you created, not other users'. Keyed on
|
|
2266
|
+
// `created_by` — the column the engine stamps on EVERY record — rather
|
|
2267
|
+
// than `owner_id`, which author-defined objects almost never declare. The
|
|
2268
|
+
// old `owner_id` key referenced a missing column on real objects, so
|
|
2269
|
+
// `computeRlsFilter` dropped the policy and the scoping silently no-op'd
|
|
2270
|
+
// (any member could edit/delete any record — #1985). These policies are
|
|
2271
|
+
// ENFORCED on writes via the security middleware's pre-image check (a
|
|
2272
|
+
// by-id update/delete never builds an RLS `where`, so the predicate is
|
|
2273
|
+
// verified against the target row before the mutation). Objects that
|
|
2274
|
+
// model transferable ownership with a dedicated owner field should
|
|
2275
|
+
// override these with a per-object policy.
|
|
2264
2276
|
{
|
|
2265
2277
|
name: "owner_only_writes",
|
|
2266
2278
|
object: "*",
|
|
2267
2279
|
operation: "update",
|
|
2268
|
-
using: "
|
|
2280
|
+
using: "created_by = current_user.id"
|
|
2269
2281
|
},
|
|
2270
2282
|
{
|
|
2271
2283
|
name: "owner_only_deletes",
|
|
2272
2284
|
object: "*",
|
|
2273
2285
|
operation: "delete",
|
|
2274
|
-
using: "
|
|
2286
|
+
using: "created_by = current_user.id"
|
|
2275
2287
|
},
|
|
2276
2288
|
// ── better-auth system tables that lack `organization_id` and would
|
|
2277
2289
|
// otherwise be left unprotected by the wildcard rule above. ────
|
|
@@ -2703,6 +2715,40 @@ var SecurityPlugin = class {
|
|
|
2703
2715
|
);
|
|
2704
2716
|
}
|
|
2705
2717
|
}
|
|
2718
|
+
if ((opCtx.operation === "update" || opCtx.operation === "delete") && permissionSets.length > 0 && !!opCtx.context?.userId && this.ql) {
|
|
2719
|
+
const targetId = this.extractSingleId(opCtx);
|
|
2720
|
+
if (targetId != null) {
|
|
2721
|
+
const writeFilter = await this.computeRlsFilter(
|
|
2722
|
+
permissionSets,
|
|
2723
|
+
opCtx.object,
|
|
2724
|
+
opCtx.operation,
|
|
2725
|
+
opCtx.context
|
|
2726
|
+
);
|
|
2727
|
+
if (writeFilter) {
|
|
2728
|
+
let visible = null;
|
|
2729
|
+
try {
|
|
2730
|
+
visible = await this.ql.findOne(opCtx.object, {
|
|
2731
|
+
where: { $and: [{ id: targetId }, writeFilter] },
|
|
2732
|
+
context: opCtx.context
|
|
2733
|
+
});
|
|
2734
|
+
} catch {
|
|
2735
|
+
visible = null;
|
|
2736
|
+
}
|
|
2737
|
+
if (!visible) {
|
|
2738
|
+
throw new PermissionDeniedError(
|
|
2739
|
+
`[Security] Access denied: not permitted to ${opCtx.operation} this '${opCtx.object}' record (row-level security)`,
|
|
2740
|
+
{
|
|
2741
|
+
operation: opCtx.operation,
|
|
2742
|
+
object: opCtx.object,
|
|
2743
|
+
roles,
|
|
2744
|
+
permissionSets: explicitPermissionSets,
|
|
2745
|
+
recordId: targetId
|
|
2746
|
+
}
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2706
2752
|
if ((opCtx.operation === "insert" || opCtx.operation === "update") && opCtx.data && permissionSets.length > 0) {
|
|
2707
2753
|
const fieldPerms = this.permissionEvaluator.getFieldPermissions(
|
|
2708
2754
|
opCtx.object,
|
|
@@ -2894,6 +2940,26 @@ var SecurityPlugin = class {
|
|
|
2894
2940
|
}
|
|
2895
2941
|
return permissionSets;
|
|
2896
2942
|
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Resolve a single scalar primary-key id from an update/delete operation
|
|
2945
|
+
* context, mirroring the engine's "single-id vs predicate" rule
|
|
2946
|
+
* (`engine.ts` update/delete): only a scalar `data.id` or `where.id`
|
|
2947
|
+
* identifies one row. An operator object (`{ $in: [...] }`, …) is a
|
|
2948
|
+
* multi-row predicate and returns `null` (multi-row writes route through the
|
|
2949
|
+
* `*Many` paths, out of scope for the by-id pre-image check).
|
|
2950
|
+
*/
|
|
2951
|
+
extractSingleId(opCtx) {
|
|
2952
|
+
const isScalar = (v) => v !== null && (typeof v === "string" || typeof v === "number" || typeof v === "bigint");
|
|
2953
|
+
const data = opCtx?.data;
|
|
2954
|
+
if (data && typeof data === "object" && !Array.isArray(data) && isScalar(data.id)) {
|
|
2955
|
+
return data.id;
|
|
2956
|
+
}
|
|
2957
|
+
const where = opCtx?.options?.where;
|
|
2958
|
+
if (where && typeof where === "object" && "id" in where && isScalar(where.id)) {
|
|
2959
|
+
return where.id;
|
|
2960
|
+
}
|
|
2961
|
+
return null;
|
|
2962
|
+
}
|
|
2897
2963
|
/**
|
|
2898
2964
|
* Compile the applicable RLS policies for (object, operation) into a single
|
|
2899
2965
|
* `FilterCondition`, applying the field-existence safety net (wildcard
|