prisma-guard 1.32.0 → 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/generator/index.js +23 -20
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +128 -26
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +128 -26
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
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;
|
|
@@ -1517,12 +1561,15 @@ function applyForcedCountWhere(container, forcedCountWhere) {
|
|
|
1517
1561
|
}
|
|
1518
1562
|
function resolvedWhereCoversConstraint(where, constraint) {
|
|
1519
1563
|
if (constraint.fields.length === 1) {
|
|
1520
|
-
|
|
1564
|
+
const value2 = where[constraint.fields[0]];
|
|
1565
|
+
return value2 !== null && value2 !== void 0;
|
|
1521
1566
|
}
|
|
1522
1567
|
const value = where[constraint.selector];
|
|
1523
1568
|
if (!isPlainObject(value))
|
|
1524
1569
|
return false;
|
|
1525
|
-
return constraint.fields.every(
|
|
1570
|
+
return constraint.fields.every(
|
|
1571
|
+
(field) => value[field] !== null && value[field] !== void 0
|
|
1572
|
+
);
|
|
1526
1573
|
}
|
|
1527
1574
|
function validateResolvedUniqueWhere(model, where, method, uniqueMap) {
|
|
1528
1575
|
const constraints = uniqueMap[model];
|
|
@@ -1549,17 +1596,16 @@ function assertDirectUniqueShapeValue(model, field, value, typeMap) {
|
|
|
1549
1596
|
);
|
|
1550
1597
|
}
|
|
1551
1598
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
const keys = Object.keys(value);
|
|
1599
|
+
const actual = isForcedValue(value) ? value.value : value;
|
|
1600
|
+
if (isPlainObject(actual)) {
|
|
1601
|
+
const keys = Object.keys(actual);
|
|
1556
1602
|
throw new ShapeError(
|
|
1557
1603
|
`Invalid unique where shape for "${model ?? "unknown"}.${field}". Prisma WhereUniqueInput does not accept filter operator objects${keys.length ? `: ${keys.join(", ")}` : ""}. Use { ${field}: true } in guard shape and { ${field}: value } in request args.`
|
|
1558
1604
|
);
|
|
1559
1605
|
}
|
|
1560
|
-
if (
|
|
1606
|
+
if (actual === null || actual === void 0) {
|
|
1561
1607
|
throw new ShapeError(
|
|
1562
|
-
`Invalid unique where shape for "${model ?? "unknown"}.${field}". Unique fields must use true or a forced value.`
|
|
1608
|
+
`Invalid unique where shape for "${model ?? "unknown"}.${field}". Unique fields must use true or a forced non-null value.`
|
|
1563
1609
|
);
|
|
1564
1610
|
}
|
|
1565
1611
|
}
|
|
@@ -1573,9 +1619,8 @@ function shapeCoversConstraint(where, constraint, model, typeMap) {
|
|
|
1573
1619
|
}
|
|
1574
1620
|
if (!(constraint.selector in where))
|
|
1575
1621
|
return false;
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
return true;
|
|
1622
|
+
const rawSelectorValue = where[constraint.selector];
|
|
1623
|
+
const selectorValue = isForcedValue(rawSelectorValue) ? rawSelectorValue.value : rawSelectorValue;
|
|
1579
1624
|
if (!isPlainObject(selectorValue)) {
|
|
1580
1625
|
throw new ShapeError(
|
|
1581
1626
|
`Compound unique selector "${model}.${constraint.selector}" must be an object with fields: ${constraint.fields.join(", ")}`
|
|
@@ -2078,8 +2123,18 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2078
2123
|
(constraint) => constraint.selector === selector
|
|
2079
2124
|
) ?? null;
|
|
2080
2125
|
}
|
|
2081
|
-
function
|
|
2126
|
+
function buildUniqueWhereFieldSchema(fieldMeta, allowNullableFilter) {
|
|
2082
2127
|
const schema = buildDirectScalarSchema(fieldMeta, enumMap, scalarBase);
|
|
2128
|
+
if (allowNullableFilter && !fieldMeta.isRequired) {
|
|
2129
|
+
return schema.nullable();
|
|
2130
|
+
}
|
|
2131
|
+
return schema;
|
|
2132
|
+
}
|
|
2133
|
+
function parseForcedUniqueValue(model, fieldName, fieldMeta, value, allowNullableFilter) {
|
|
2134
|
+
const schema = buildUniqueWhereFieldSchema(
|
|
2135
|
+
fieldMeta,
|
|
2136
|
+
allowNullableFilter
|
|
2137
|
+
);
|
|
2083
2138
|
const actual = isForcedValue(value) ? value.value : value;
|
|
2084
2139
|
try {
|
|
2085
2140
|
return schema.parse(actual);
|
|
@@ -2157,10 +2212,9 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2157
2212
|
`Invalid compound unique where shape for "${model}.${key}.${fieldName}". Prisma compound unique selectors do not accept filter operator objects${operators.length ? `: ${operators.join(", ")}` : ""}. Use direct values only.`
|
|
2158
2213
|
);
|
|
2159
2214
|
}
|
|
2160
|
-
const directSchema2 =
|
|
2215
|
+
const directSchema2 = buildUniqueWhereFieldSchema(
|
|
2161
2216
|
fieldMeta2,
|
|
2162
|
-
|
|
2163
|
-
scalarBase
|
|
2217
|
+
false
|
|
2164
2218
|
);
|
|
2165
2219
|
if (fieldValue === true) {
|
|
2166
2220
|
nestedSchemas[fieldName] = directSchema2;
|
|
@@ -2169,7 +2223,8 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2169
2223
|
model,
|
|
2170
2224
|
fieldName,
|
|
2171
2225
|
fieldMeta2,
|
|
2172
|
-
fieldValue
|
|
2226
|
+
fieldValue,
|
|
2227
|
+
false
|
|
2173
2228
|
);
|
|
2174
2229
|
}
|
|
2175
2230
|
}
|
|
@@ -2208,7 +2263,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2208
2263
|
`Invalid unique where shape for "${model}.${key}". Prisma WhereUniqueInput does not accept operators: ${keys.join(", ")}. Use a direct unique value shape, for example { ${key}: true }.`
|
|
2209
2264
|
);
|
|
2210
2265
|
}
|
|
2211
|
-
const directSchema =
|
|
2266
|
+
const directSchema = buildUniqueWhereFieldSchema(fieldMeta, true);
|
|
2212
2267
|
if (value === true) {
|
|
2213
2268
|
fieldSchemas[key] = directSchema;
|
|
2214
2269
|
continue;
|
|
@@ -2217,12 +2272,32 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2217
2272
|
model,
|
|
2218
2273
|
key,
|
|
2219
2274
|
fieldMeta,
|
|
2220
|
-
value
|
|
2275
|
+
value,
|
|
2276
|
+
true
|
|
2221
2277
|
);
|
|
2222
2278
|
forcedOnlyKeys.add(key);
|
|
2223
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
|
+
}
|
|
2224
2299
|
return {
|
|
2225
|
-
schema
|
|
2300
|
+
schema,
|
|
2226
2301
|
forced: {
|
|
2227
2302
|
conditions: forcedConditions,
|
|
2228
2303
|
relations: {}
|
|
@@ -3570,7 +3645,21 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
3570
3645
|
built = builtCache.get(resolution.key);
|
|
3571
3646
|
}
|
|
3572
3647
|
}
|
|
3573
|
-
|
|
3648
|
+
const result = applyBuiltShape(
|
|
3649
|
+
built,
|
|
3650
|
+
normalizedBody,
|
|
3651
|
+
isUnique,
|
|
3652
|
+
model
|
|
3653
|
+
);
|
|
3654
|
+
if (isUnique && result.where) {
|
|
3655
|
+
validateResolvedUniqueWhere(
|
|
3656
|
+
model,
|
|
3657
|
+
result.where,
|
|
3658
|
+
method,
|
|
3659
|
+
uniqueMap
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
return result;
|
|
3574
3663
|
}
|
|
3575
3664
|
};
|
|
3576
3665
|
}
|
|
@@ -3669,6 +3758,12 @@ function pickMissingFksFromResult(result, fks) {
|
|
|
3669
3758
|
return missing;
|
|
3670
3759
|
}
|
|
3671
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
|
+
}
|
|
3672
3767
|
if (typeof value === "string" && value.length === 0) {
|
|
3673
3768
|
throw new PolicyError(
|
|
3674
3769
|
`Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
|
|
@@ -3711,10 +3806,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
|
|
|
3711
3806
|
}
|
|
3712
3807
|
var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
|
|
3713
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"]);
|
|
3714
3810
|
function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
3715
3811
|
const log = logger ?? { warn: (msg) => console.warn(msg) };
|
|
3716
3812
|
const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
|
|
3717
3813
|
const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
|
|
3814
|
+
const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
|
|
3718
3815
|
if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
|
|
3719
3816
|
throw new ShapeError(
|
|
3720
3817
|
`prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
|
|
@@ -3725,6 +3822,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3725
3822
|
`prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
|
|
3726
3823
|
);
|
|
3727
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
|
+
}
|
|
3728
3830
|
const scopeFkSets = /* @__PURE__ */ new Map();
|
|
3729
3831
|
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
3730
3832
|
const fks = /* @__PURE__ */ new Set();
|
|
@@ -3750,12 +3852,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3750
3852
|
const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
|
|
3751
3853
|
const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
|
|
3752
3854
|
if (missingRoots.length > 0) {
|
|
3753
|
-
if (isMutation ||
|
|
3855
|
+
if (isMutation || onMissingScopeContext === "error") {
|
|
3754
3856
|
throw new PolicyError(
|
|
3755
3857
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
|
|
3756
3858
|
);
|
|
3757
3859
|
}
|
|
3758
|
-
if (
|
|
3860
|
+
if (onMissingScopeContext === "warn") {
|
|
3759
3861
|
log.warn(
|
|
3760
3862
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
|
|
3761
3863
|
);
|