@objectstack/plugin-security 9.8.0 → 9.9.0
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.js
CHANGED
|
@@ -2293,17 +2293,29 @@ var defaultPermissionSets = [
|
|
|
2293
2293
|
operation: "all",
|
|
2294
2294
|
using: "organization_id = current_user.organization_id"
|
|
2295
2295
|
},
|
|
2296
|
+
// Owner-scoped writes/deletes for rank-and-file members: you may modify
|
|
2297
|
+
// and delete the records you created, not other users'. Keyed on
|
|
2298
|
+
// `created_by` — the column the engine stamps on EVERY record — rather
|
|
2299
|
+
// than `owner_id`, which author-defined objects almost never declare. The
|
|
2300
|
+
// old `owner_id` key referenced a missing column on real objects, so
|
|
2301
|
+
// `computeRlsFilter` dropped the policy and the scoping silently no-op'd
|
|
2302
|
+
// (any member could edit/delete any record — #1985). These policies are
|
|
2303
|
+
// ENFORCED on writes via the security middleware's pre-image check (a
|
|
2304
|
+
// by-id update/delete never builds an RLS `where`, so the predicate is
|
|
2305
|
+
// verified against the target row before the mutation). Objects that
|
|
2306
|
+
// model transferable ownership with a dedicated owner field should
|
|
2307
|
+
// override these with a per-object policy.
|
|
2296
2308
|
{
|
|
2297
2309
|
name: "owner_only_writes",
|
|
2298
2310
|
object: "*",
|
|
2299
2311
|
operation: "update",
|
|
2300
|
-
using: "
|
|
2312
|
+
using: "created_by = current_user.id"
|
|
2301
2313
|
},
|
|
2302
2314
|
{
|
|
2303
2315
|
name: "owner_only_deletes",
|
|
2304
2316
|
object: "*",
|
|
2305
2317
|
operation: "delete",
|
|
2306
|
-
using: "
|
|
2318
|
+
using: "created_by = current_user.id"
|
|
2307
2319
|
},
|
|
2308
2320
|
// ── better-auth system tables that lack `organization_id` and would
|
|
2309
2321
|
// otherwise be left unprotected by the wildcard rule above. ────
|
|
@@ -2735,6 +2747,40 @@ var SecurityPlugin = class {
|
|
|
2735
2747
|
);
|
|
2736
2748
|
}
|
|
2737
2749
|
}
|
|
2750
|
+
if ((opCtx.operation === "update" || opCtx.operation === "delete") && permissionSets.length > 0 && !!opCtx.context?.userId && this.ql) {
|
|
2751
|
+
const targetId = this.extractSingleId(opCtx);
|
|
2752
|
+
if (targetId != null) {
|
|
2753
|
+
const writeFilter = await this.computeRlsFilter(
|
|
2754
|
+
permissionSets,
|
|
2755
|
+
opCtx.object,
|
|
2756
|
+
opCtx.operation,
|
|
2757
|
+
opCtx.context
|
|
2758
|
+
);
|
|
2759
|
+
if (writeFilter) {
|
|
2760
|
+
let visible = null;
|
|
2761
|
+
try {
|
|
2762
|
+
visible = await this.ql.findOne(opCtx.object, {
|
|
2763
|
+
where: { $and: [{ id: targetId }, writeFilter] },
|
|
2764
|
+
context: opCtx.context
|
|
2765
|
+
});
|
|
2766
|
+
} catch {
|
|
2767
|
+
visible = null;
|
|
2768
|
+
}
|
|
2769
|
+
if (!visible) {
|
|
2770
|
+
throw new PermissionDeniedError(
|
|
2771
|
+
`[Security] Access denied: not permitted to ${opCtx.operation} this '${opCtx.object}' record (row-level security)`,
|
|
2772
|
+
{
|
|
2773
|
+
operation: opCtx.operation,
|
|
2774
|
+
object: opCtx.object,
|
|
2775
|
+
roles,
|
|
2776
|
+
permissionSets: explicitPermissionSets,
|
|
2777
|
+
recordId: targetId
|
|
2778
|
+
}
|
|
2779
|
+
);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2738
2784
|
if ((opCtx.operation === "insert" || opCtx.operation === "update") && opCtx.data && permissionSets.length > 0) {
|
|
2739
2785
|
const fieldPerms = this.permissionEvaluator.getFieldPermissions(
|
|
2740
2786
|
opCtx.object,
|
|
@@ -2926,6 +2972,26 @@ var SecurityPlugin = class {
|
|
|
2926
2972
|
}
|
|
2927
2973
|
return permissionSets;
|
|
2928
2974
|
}
|
|
2975
|
+
/**
|
|
2976
|
+
* Resolve a single scalar primary-key id from an update/delete operation
|
|
2977
|
+
* context, mirroring the engine's "single-id vs predicate" rule
|
|
2978
|
+
* (`engine.ts` update/delete): only a scalar `data.id` or `where.id`
|
|
2979
|
+
* identifies one row. An operator object (`{ $in: [...] }`, …) is a
|
|
2980
|
+
* multi-row predicate and returns `null` (multi-row writes route through the
|
|
2981
|
+
* `*Many` paths, out of scope for the by-id pre-image check).
|
|
2982
|
+
*/
|
|
2983
|
+
extractSingleId(opCtx) {
|
|
2984
|
+
const isScalar = (v) => v !== null && (typeof v === "string" || typeof v === "number" || typeof v === "bigint");
|
|
2985
|
+
const data = opCtx?.data;
|
|
2986
|
+
if (data && typeof data === "object" && !Array.isArray(data) && isScalar(data.id)) {
|
|
2987
|
+
return data.id;
|
|
2988
|
+
}
|
|
2989
|
+
const where = opCtx?.options?.where;
|
|
2990
|
+
if (where && typeof where === "object" && "id" in where && isScalar(where.id)) {
|
|
2991
|
+
return where.id;
|
|
2992
|
+
}
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2929
2995
|
/**
|
|
2930
2996
|
* Compile the applicable RLS policies for (object, operation) into a single
|
|
2931
2997
|
* `FilterCondition`, applying the field-existence safety net (wildcard
|