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.js
CHANGED
|
@@ -196,7 +196,7 @@ function isJsonSafe(value) {
|
|
|
196
196
|
}
|
|
197
197
|
return true;
|
|
198
198
|
}
|
|
199
|
-
var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE]
|
|
199
|
+
var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/;
|
|
200
200
|
var decimalStringSchema = z.string().refine((s) => DECIMAL_REGEX.test(s), "Invalid decimal string");
|
|
201
201
|
var decimalObjectSchema = z.custom(
|
|
202
202
|
(v) => v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function",
|
|
@@ -956,8 +956,13 @@ function matchVariantPatterns(keys, caller) {
|
|
|
956
956
|
continue;
|
|
957
957
|
let matchesCaller = true;
|
|
958
958
|
for (let index = 0; index < patternParts.length; index++) {
|
|
959
|
-
if (patternParts[index].startsWith(":"))
|
|
959
|
+
if (patternParts[index].startsWith(":")) {
|
|
960
|
+
if (callerParts[index].length === 0) {
|
|
961
|
+
matchesCaller = false;
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
960
964
|
continue;
|
|
965
|
+
}
|
|
961
966
|
if (patternParts[index] !== callerParts[index]) {
|
|
962
967
|
matchesCaller = false;
|
|
963
968
|
break;
|
|
@@ -1052,8 +1057,12 @@ function deepClone(value) {
|
|
|
1052
1057
|
case "object": {
|
|
1053
1058
|
if (value instanceof Date)
|
|
1054
1059
|
return new Date(value.getTime());
|
|
1055
|
-
if (value instanceof Uint8Array)
|
|
1060
|
+
if (value instanceof Uint8Array) {
|
|
1061
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer?.(value)) {
|
|
1062
|
+
return Buffer.from(value);
|
|
1063
|
+
}
|
|
1056
1064
|
return value.slice();
|
|
1065
|
+
}
|
|
1057
1066
|
if (value instanceof RegExp)
|
|
1058
1067
|
return new RegExp(value.source, value.flags);
|
|
1059
1068
|
if (Array.isArray(value))
|
|
@@ -1063,7 +1072,17 @@ function deepClone(value) {
|
|
|
1063
1072
|
return value;
|
|
1064
1073
|
const result = {};
|
|
1065
1074
|
for (const [k, v] of Object.entries(value)) {
|
|
1066
|
-
|
|
1075
|
+
const cloned = deepClone(v);
|
|
1076
|
+
if (k === "__proto__") {
|
|
1077
|
+
Object.defineProperty(result, k, {
|
|
1078
|
+
value: cloned,
|
|
1079
|
+
enumerable: true,
|
|
1080
|
+
writable: true,
|
|
1081
|
+
configurable: true
|
|
1082
|
+
});
|
|
1083
|
+
} else {
|
|
1084
|
+
result[k] = cloned;
|
|
1085
|
+
}
|
|
1067
1086
|
}
|
|
1068
1087
|
return result;
|
|
1069
1088
|
}
|
|
@@ -1073,6 +1092,9 @@ function deepClone(value) {
|
|
|
1073
1092
|
}
|
|
1074
1093
|
|
|
1075
1094
|
// src/shared/deep-equal.ts
|
|
1095
|
+
function isDecimalLike(v) {
|
|
1096
|
+
return v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function";
|
|
1097
|
+
}
|
|
1076
1098
|
function deepEqual(a, b) {
|
|
1077
1099
|
if (a === b)
|
|
1078
1100
|
return true;
|
|
@@ -1098,6 +1120,28 @@ function deepEqual(a, b) {
|
|
|
1098
1120
|
}
|
|
1099
1121
|
if (a instanceof RegExp || b instanceof RegExp)
|
|
1100
1122
|
return false;
|
|
1123
|
+
if (a instanceof Uint8Array || b instanceof Uint8Array) {
|
|
1124
|
+
if (!(a instanceof Uint8Array) || !(b instanceof Uint8Array))
|
|
1125
|
+
return false;
|
|
1126
|
+
if (a.byteLength !== b.byteLength)
|
|
1127
|
+
return false;
|
|
1128
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
1129
|
+
if (a[i] !== b[i])
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
if (isDecimalLike(a) || isDecimalLike(b)) {
|
|
1135
|
+
if (!isDecimalLike(a) || !isDecimalLike(b))
|
|
1136
|
+
return false;
|
|
1137
|
+
try {
|
|
1138
|
+
if (typeof a.equals === "function")
|
|
1139
|
+
return a.equals(b) === true;
|
|
1140
|
+
return a.toString() === b.toString();
|
|
1141
|
+
} catch {
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1101
1145
|
if (Array.isArray(a)) {
|
|
1102
1146
|
if (!Array.isArray(b))
|
|
1103
1147
|
return false;
|
|
@@ -2202,8 +2246,27 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2202
2246
|
);
|
|
2203
2247
|
forcedOnlyKeys.add(key);
|
|
2204
2248
|
}
|
|
2249
|
+
const selectorKeys = Object.keys(fieldSchemas);
|
|
2250
|
+
let schema;
|
|
2251
|
+
if (selectorKeys.length === 0) {
|
|
2252
|
+
schema = null;
|
|
2253
|
+
} else if (selectorKeys.length === 1) {
|
|
2254
|
+
schema = z5.object(fieldSchemas).strict();
|
|
2255
|
+
} else {
|
|
2256
|
+
const optionalSchemas = {};
|
|
2257
|
+
for (const [k, s] of Object.entries(fieldSchemas)) {
|
|
2258
|
+
optionalSchemas[k] = s.optional();
|
|
2259
|
+
}
|
|
2260
|
+
const objectSchema = z5.object(optionalSchemas).strict();
|
|
2261
|
+
schema = Object.keys(forcedConditions).length > 0 ? objectSchema : objectSchema.refine(
|
|
2262
|
+
(v) => selectorKeys.some((k) => v[k] !== void 0),
|
|
2263
|
+
{
|
|
2264
|
+
message: `Unique where for model "${model}" must specify at least one selector: ${selectorKeys.join(", ")}`
|
|
2265
|
+
}
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2205
2268
|
return {
|
|
2206
|
-
schema
|
|
2269
|
+
schema,
|
|
2207
2270
|
forced: {
|
|
2208
2271
|
conditions: forcedConditions,
|
|
2209
2272
|
relations: {}
|
|
@@ -3664,6 +3727,12 @@ function pickMissingFksFromResult(result, fks) {
|
|
|
3664
3727
|
return missing;
|
|
3665
3728
|
}
|
|
3666
3729
|
function validateScopeValue(root, value) {
|
|
3730
|
+
const t = typeof value;
|
|
3731
|
+
if (t !== "string" && t !== "number" && t !== "bigint") {
|
|
3732
|
+
throw new PolicyError(
|
|
3733
|
+
`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.`
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3667
3736
|
if (typeof value === "string" && value.length === 0) {
|
|
3668
3737
|
throw new PolicyError(
|
|
3669
3738
|
`Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
|
|
@@ -3706,10 +3775,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
|
|
|
3706
3775
|
}
|
|
3707
3776
|
var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
|
|
3708
3777
|
var VALID_ON_SCOPE_RELATION_WRITES = /* @__PURE__ */ new Set(["error", "warn", "strip"]);
|
|
3778
|
+
var VALID_ON_MISSING_SCOPE_CONTEXT = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
|
|
3709
3779
|
function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
3710
3780
|
const log = logger ?? { warn: (msg) => console.warn(msg) };
|
|
3711
3781
|
const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
|
|
3712
3782
|
const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
|
|
3783
|
+
const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
|
|
3713
3784
|
if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
|
|
3714
3785
|
throw new ShapeError(
|
|
3715
3786
|
`prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
|
|
@@ -3720,6 +3791,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3720
3791
|
`prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
|
|
3721
3792
|
);
|
|
3722
3793
|
}
|
|
3794
|
+
if (!VALID_ON_MISSING_SCOPE_CONTEXT.has(onMissingScopeContext)) {
|
|
3795
|
+
throw new ShapeError(
|
|
3796
|
+
`prisma-guard: Invalid onMissingScopeContext "${onMissingScopeContext}". Allowed: ${[...VALID_ON_MISSING_SCOPE_CONTEXT].join(", ")}`
|
|
3797
|
+
);
|
|
3798
|
+
}
|
|
3723
3799
|
const scopeFkSets = /* @__PURE__ */ new Map();
|
|
3724
3800
|
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
3725
3801
|
const fks = /* @__PURE__ */ new Set();
|
|
@@ -3745,12 +3821,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3745
3821
|
const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
|
|
3746
3822
|
const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
|
|
3747
3823
|
if (missingRoots.length > 0) {
|
|
3748
|
-
if (isMutation ||
|
|
3824
|
+
if (isMutation || onMissingScopeContext === "error") {
|
|
3749
3825
|
throw new PolicyError(
|
|
3750
3826
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
|
|
3751
3827
|
);
|
|
3752
3828
|
}
|
|
3753
|
-
if (
|
|
3829
|
+
if (onMissingScopeContext === "warn") {
|
|
3754
3830
|
log.warn(
|
|
3755
3831
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
|
|
3756
3832
|
);
|