prisma-guard 1.32.1 → 1.33.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/runtime/index.cjs
CHANGED
|
@@ -227,7 +227,7 @@ function isJsonSafe(value) {
|
|
|
227
227
|
}
|
|
228
228
|
return true;
|
|
229
229
|
}
|
|
230
|
-
var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE]
|
|
230
|
+
var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
231
231
|
var decimalStringSchema = import_zod.z.string().refine((s) => DECIMAL_REGEX.test(s), "Invalid decimal string");
|
|
232
232
|
var decimalObjectSchema = import_zod.z.custom(
|
|
233
233
|
(v) => v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function",
|
|
@@ -987,8 +987,13 @@ function matchVariantPatterns(keys, caller) {
|
|
|
987
987
|
continue;
|
|
988
988
|
let matchesCaller = true;
|
|
989
989
|
for (let index = 0; index < patternParts.length; index++) {
|
|
990
|
-
if (patternParts[index].startsWith(":"))
|
|
990
|
+
if (patternParts[index].startsWith(":")) {
|
|
991
|
+
if (callerParts[index].length === 0) {
|
|
992
|
+
matchesCaller = false;
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
991
995
|
continue;
|
|
996
|
+
}
|
|
992
997
|
if (patternParts[index] !== callerParts[index]) {
|
|
993
998
|
matchesCaller = false;
|
|
994
999
|
break;
|
|
@@ -1083,8 +1088,12 @@ function deepClone(value) {
|
|
|
1083
1088
|
case "object": {
|
|
1084
1089
|
if (value instanceof Date)
|
|
1085
1090
|
return new Date(value.getTime());
|
|
1086
|
-
if (value instanceof Uint8Array)
|
|
1091
|
+
if (value instanceof Uint8Array) {
|
|
1092
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(value)) {
|
|
1093
|
+
return Buffer.from(value);
|
|
1094
|
+
}
|
|
1087
1095
|
return value.slice();
|
|
1096
|
+
}
|
|
1088
1097
|
if (value instanceof RegExp)
|
|
1089
1098
|
return new RegExp(value.source, value.flags);
|
|
1090
1099
|
if (Array.isArray(value))
|
|
@@ -1094,7 +1103,17 @@ function deepClone(value) {
|
|
|
1094
1103
|
return value;
|
|
1095
1104
|
const result = {};
|
|
1096
1105
|
for (const [k, v] of Object.entries(value)) {
|
|
1097
|
-
|
|
1106
|
+
const cloned = deepClone(v);
|
|
1107
|
+
if (k === "__proto__") {
|
|
1108
|
+
Object.defineProperty(result, k, {
|
|
1109
|
+
value: cloned,
|
|
1110
|
+
enumerable: true,
|
|
1111
|
+
writable: true,
|
|
1112
|
+
configurable: true
|
|
1113
|
+
});
|
|
1114
|
+
} else {
|
|
1115
|
+
result[k] = cloned;
|
|
1116
|
+
}
|
|
1098
1117
|
}
|
|
1099
1118
|
return result;
|
|
1100
1119
|
}
|
|
@@ -1104,6 +1123,9 @@ function deepClone(value) {
|
|
|
1104
1123
|
}
|
|
1105
1124
|
|
|
1106
1125
|
// src/shared/deep-equal.ts
|
|
1126
|
+
function isDecimalLike(v) {
|
|
1127
|
+
return v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function";
|
|
1128
|
+
}
|
|
1107
1129
|
function deepEqual(a, b) {
|
|
1108
1130
|
if (a === b)
|
|
1109
1131
|
return true;
|
|
@@ -1129,6 +1151,28 @@ function deepEqual(a, b) {
|
|
|
1129
1151
|
}
|
|
1130
1152
|
if (a instanceof RegExp || b instanceof RegExp)
|
|
1131
1153
|
return false;
|
|
1154
|
+
if (a instanceof Uint8Array || b instanceof Uint8Array) {
|
|
1155
|
+
if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array))
|
|
1156
|
+
return false;
|
|
1157
|
+
if (a.byteLength !== b.byteLength)
|
|
1158
|
+
return false;
|
|
1159
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
1160
|
+
if (a[i] !== b[i])
|
|
1161
|
+
return false;
|
|
1162
|
+
}
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
if (isDecimalLike(a) || isDecimalLike(b)) {
|
|
1166
|
+
if (!isDecimalLike(a) || !isDecimalLike(b))
|
|
1167
|
+
return false;
|
|
1168
|
+
try {
|
|
1169
|
+
if (typeof a.equals === "function")
|
|
1170
|
+
return a.equals(b) === true;
|
|
1171
|
+
return a.toString() === b.toString();
|
|
1172
|
+
} catch {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1132
1176
|
if (Array.isArray(a)) {
|
|
1133
1177
|
if (!Array.isArray(b))
|
|
1134
1178
|
return false;
|
|
@@ -2233,8 +2277,27 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2233
2277
|
);
|
|
2234
2278
|
forcedOnlyKeys.add(key);
|
|
2235
2279
|
}
|
|
2280
|
+
const selectorKeys = Object.keys(fieldSchemas);
|
|
2281
|
+
let schema;
|
|
2282
|
+
if (selectorKeys.length === 0) {
|
|
2283
|
+
schema = null;
|
|
2284
|
+
} else if (selectorKeys.length === 1) {
|
|
2285
|
+
schema = import_zod5.z.object(fieldSchemas).strict();
|
|
2286
|
+
} else {
|
|
2287
|
+
const optionalSchemas = {};
|
|
2288
|
+
for (const [k, s] of Object.entries(fieldSchemas)) {
|
|
2289
|
+
optionalSchemas[k] = s.optional();
|
|
2290
|
+
}
|
|
2291
|
+
const objectSchema = import_zod5.z.object(optionalSchemas).strict();
|
|
2292
|
+
schema = Object.keys(forcedConditions).length > 0 ? objectSchema : objectSchema.refine(
|
|
2293
|
+
(v) => selectorKeys.some((k) => v[k] !== void 0),
|
|
2294
|
+
{
|
|
2295
|
+
message: `Unique where for model "${model}" must specify at least one selector: ${selectorKeys.join(", ")}`
|
|
2296
|
+
}
|
|
2297
|
+
);
|
|
2298
|
+
}
|
|
2236
2299
|
return {
|
|
2237
|
-
schema
|
|
2300
|
+
schema,
|
|
2238
2301
|
forced: {
|
|
2239
2302
|
conditions: forcedConditions,
|
|
2240
2303
|
relations: {}
|
|
@@ -3695,6 +3758,12 @@ function pickMissingFksFromResult(result, fks) {
|
|
|
3695
3758
|
return missing;
|
|
3696
3759
|
}
|
|
3697
3760
|
function validateScopeValue(root, value) {
|
|
3761
|
+
const t = typeof value;
|
|
3762
|
+
if (t !== "string" && t !== "number" && t !== "bigint") {
|
|
3763
|
+
throw new PolicyError(
|
|
3764
|
+
`Non-primitive scope value for root "${root}" (${t}). Only string, number, and bigint are accepted \u2014 an object or array here would silently become a Prisma filter and could bypass scope isolation.`
|
|
3765
|
+
);
|
|
3766
|
+
}
|
|
3698
3767
|
if (typeof value === "string" && value.length === 0) {
|
|
3699
3768
|
throw new PolicyError(
|
|
3700
3769
|
`Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
|
|
@@ -3737,10 +3806,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
|
|
|
3737
3806
|
}
|
|
3738
3807
|
var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
|
|
3739
3808
|
var VALID_ON_SCOPE_RELATION_WRITES = /* @__PURE__ */ new Set(["error", "warn", "strip"]);
|
|
3809
|
+
var VALID_ON_MISSING_SCOPE_CONTEXT = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
|
|
3740
3810
|
function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
3741
3811
|
const log = logger ?? { warn: (msg) => console.warn(msg) };
|
|
3742
3812
|
const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
|
|
3743
3813
|
const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
|
|
3814
|
+
const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
|
|
3744
3815
|
if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
|
|
3745
3816
|
throw new ShapeError(
|
|
3746
3817
|
`prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
|
|
@@ -3751,6 +3822,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3751
3822
|
`prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
|
|
3752
3823
|
);
|
|
3753
3824
|
}
|
|
3825
|
+
if (!VALID_ON_MISSING_SCOPE_CONTEXT.has(onMissingScopeContext)) {
|
|
3826
|
+
throw new ShapeError(
|
|
3827
|
+
`prisma-guard: Invalid onMissingScopeContext "${onMissingScopeContext}". Allowed: ${[...VALID_ON_MISSING_SCOPE_CONTEXT].join(", ")}`
|
|
3828
|
+
);
|
|
3829
|
+
}
|
|
3754
3830
|
const scopeFkSets = /* @__PURE__ */ new Map();
|
|
3755
3831
|
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
3756
3832
|
const fks = /* @__PURE__ */ new Set();
|
|
@@ -3776,12 +3852,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3776
3852
|
const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
|
|
3777
3853
|
const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
|
|
3778
3854
|
if (missingRoots.length > 0) {
|
|
3779
|
-
if (isMutation ||
|
|
3855
|
+
if (isMutation || onMissingScopeContext === "error") {
|
|
3780
3856
|
throw new PolicyError(
|
|
3781
3857
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
|
|
3782
3858
|
);
|
|
3783
3859
|
}
|
|
3784
|
-
if (
|
|
3860
|
+
if (onMissingScopeContext === "warn") {
|
|
3785
3861
|
log.warn(
|
|
3786
3862
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
|
|
3787
3863
|
);
|