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.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;
|
|
@@ -1486,12 +1530,15 @@ function applyForcedCountWhere(container, forcedCountWhere) {
|
|
|
1486
1530
|
}
|
|
1487
1531
|
function resolvedWhereCoversConstraint(where, constraint) {
|
|
1488
1532
|
if (constraint.fields.length === 1) {
|
|
1489
|
-
|
|
1533
|
+
const value2 = where[constraint.fields[0]];
|
|
1534
|
+
return value2 !== null && value2 !== void 0;
|
|
1490
1535
|
}
|
|
1491
1536
|
const value = where[constraint.selector];
|
|
1492
1537
|
if (!isPlainObject(value))
|
|
1493
1538
|
return false;
|
|
1494
|
-
return constraint.fields.every(
|
|
1539
|
+
return constraint.fields.every(
|
|
1540
|
+
(field) => value[field] !== null && value[field] !== void 0
|
|
1541
|
+
);
|
|
1495
1542
|
}
|
|
1496
1543
|
function validateResolvedUniqueWhere(model, where, method, uniqueMap) {
|
|
1497
1544
|
const constraints = uniqueMap[model];
|
|
@@ -1518,17 +1565,16 @@ function assertDirectUniqueShapeValue(model, field, value, typeMap) {
|
|
|
1518
1565
|
);
|
|
1519
1566
|
}
|
|
1520
1567
|
}
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
const keys = Object.keys(value);
|
|
1568
|
+
const actual = isForcedValue(value) ? value.value : value;
|
|
1569
|
+
if (isPlainObject(actual)) {
|
|
1570
|
+
const keys = Object.keys(actual);
|
|
1525
1571
|
throw new ShapeError(
|
|
1526
1572
|
`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.`
|
|
1527
1573
|
);
|
|
1528
1574
|
}
|
|
1529
|
-
if (
|
|
1575
|
+
if (actual === null || actual === void 0) {
|
|
1530
1576
|
throw new ShapeError(
|
|
1531
|
-
`Invalid unique where shape for "${model ?? "unknown"}.${field}". Unique fields must use true or a forced value.`
|
|
1577
|
+
`Invalid unique where shape for "${model ?? "unknown"}.${field}". Unique fields must use true or a forced non-null value.`
|
|
1532
1578
|
);
|
|
1533
1579
|
}
|
|
1534
1580
|
}
|
|
@@ -1542,9 +1588,8 @@ function shapeCoversConstraint(where, constraint, model, typeMap) {
|
|
|
1542
1588
|
}
|
|
1543
1589
|
if (!(constraint.selector in where))
|
|
1544
1590
|
return false;
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
return true;
|
|
1591
|
+
const rawSelectorValue = where[constraint.selector];
|
|
1592
|
+
const selectorValue = isForcedValue(rawSelectorValue) ? rawSelectorValue.value : rawSelectorValue;
|
|
1548
1593
|
if (!isPlainObject(selectorValue)) {
|
|
1549
1594
|
throw new ShapeError(
|
|
1550
1595
|
`Compound unique selector "${model}.${constraint.selector}" must be an object with fields: ${constraint.fields.join(", ")}`
|
|
@@ -2047,8 +2092,18 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2047
2092
|
(constraint) => constraint.selector === selector
|
|
2048
2093
|
) ?? null;
|
|
2049
2094
|
}
|
|
2050
|
-
function
|
|
2095
|
+
function buildUniqueWhereFieldSchema(fieldMeta, allowNullableFilter) {
|
|
2051
2096
|
const schema = buildDirectScalarSchema(fieldMeta, enumMap, scalarBase);
|
|
2097
|
+
if (allowNullableFilter && !fieldMeta.isRequired) {
|
|
2098
|
+
return schema.nullable();
|
|
2099
|
+
}
|
|
2100
|
+
return schema;
|
|
2101
|
+
}
|
|
2102
|
+
function parseForcedUniqueValue(model, fieldName, fieldMeta, value, allowNullableFilter) {
|
|
2103
|
+
const schema = buildUniqueWhereFieldSchema(
|
|
2104
|
+
fieldMeta,
|
|
2105
|
+
allowNullableFilter
|
|
2106
|
+
);
|
|
2052
2107
|
const actual = isForcedValue(value) ? value.value : value;
|
|
2053
2108
|
try {
|
|
2054
2109
|
return schema.parse(actual);
|
|
@@ -2126,10 +2181,9 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2126
2181
|
`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.`
|
|
2127
2182
|
);
|
|
2128
2183
|
}
|
|
2129
|
-
const directSchema2 =
|
|
2184
|
+
const directSchema2 = buildUniqueWhereFieldSchema(
|
|
2130
2185
|
fieldMeta2,
|
|
2131
|
-
|
|
2132
|
-
scalarBase
|
|
2186
|
+
false
|
|
2133
2187
|
);
|
|
2134
2188
|
if (fieldValue === true) {
|
|
2135
2189
|
nestedSchemas[fieldName] = directSchema2;
|
|
@@ -2138,7 +2192,8 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2138
2192
|
model,
|
|
2139
2193
|
fieldName,
|
|
2140
2194
|
fieldMeta2,
|
|
2141
|
-
fieldValue
|
|
2195
|
+
fieldValue,
|
|
2196
|
+
false
|
|
2142
2197
|
);
|
|
2143
2198
|
}
|
|
2144
2199
|
}
|
|
@@ -2177,7 +2232,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2177
2232
|
`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 }.`
|
|
2178
2233
|
);
|
|
2179
2234
|
}
|
|
2180
|
-
const directSchema =
|
|
2235
|
+
const directSchema = buildUniqueWhereFieldSchema(fieldMeta, true);
|
|
2181
2236
|
if (value === true) {
|
|
2182
2237
|
fieldSchemas[key] = directSchema;
|
|
2183
2238
|
continue;
|
|
@@ -2186,12 +2241,32 @@ function createWhereBuilder(typeMap, enumMap, scalarBase, uniqueMap = {}) {
|
|
|
2186
2241
|
model,
|
|
2187
2242
|
key,
|
|
2188
2243
|
fieldMeta,
|
|
2189
|
-
value
|
|
2244
|
+
value,
|
|
2245
|
+
true
|
|
2190
2246
|
);
|
|
2191
2247
|
forcedOnlyKeys.add(key);
|
|
2192
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
|
+
}
|
|
2193
2268
|
return {
|
|
2194
|
-
schema
|
|
2269
|
+
schema,
|
|
2195
2270
|
forced: {
|
|
2196
2271
|
conditions: forcedConditions,
|
|
2197
2272
|
relations: {}
|
|
@@ -3539,7 +3614,21 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
3539
3614
|
built = builtCache.get(resolution.key);
|
|
3540
3615
|
}
|
|
3541
3616
|
}
|
|
3542
|
-
|
|
3617
|
+
const result = applyBuiltShape(
|
|
3618
|
+
built,
|
|
3619
|
+
normalizedBody,
|
|
3620
|
+
isUnique,
|
|
3621
|
+
model
|
|
3622
|
+
);
|
|
3623
|
+
if (isUnique && result.where) {
|
|
3624
|
+
validateResolvedUniqueWhere(
|
|
3625
|
+
model,
|
|
3626
|
+
result.where,
|
|
3627
|
+
method,
|
|
3628
|
+
uniqueMap
|
|
3629
|
+
);
|
|
3630
|
+
}
|
|
3631
|
+
return result;
|
|
3543
3632
|
}
|
|
3544
3633
|
};
|
|
3545
3634
|
}
|
|
@@ -3638,6 +3727,12 @@ function pickMissingFksFromResult(result, fks) {
|
|
|
3638
3727
|
return missing;
|
|
3639
3728
|
}
|
|
3640
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
|
+
}
|
|
3641
3736
|
if (typeof value === "string" && value.length === 0) {
|
|
3642
3737
|
throw new PolicyError(
|
|
3643
3738
|
`Empty string scope value for root "${root}". This is almost certainly a bug in the context function.`
|
|
@@ -3680,10 +3775,12 @@ function enforceDataScope(data, scopes, overrides, log, model, operation, onScop
|
|
|
3680
3775
|
}
|
|
3681
3776
|
var VALID_FIND_UNIQUE_MODES = /* @__PURE__ */ new Set(["verify", "reject"]);
|
|
3682
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"]);
|
|
3683
3779
|
function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
3684
3780
|
const log = logger ?? { warn: (msg) => console.warn(msg) };
|
|
3685
3781
|
const findUniqueMode = guardConfig.findUniqueMode ?? "reject";
|
|
3686
3782
|
const onScopeRelationWrite = guardConfig.onScopeRelationWrite ?? "error";
|
|
3783
|
+
const onMissingScopeContext = guardConfig.onMissingScopeContext ?? "error";
|
|
3687
3784
|
if (!VALID_FIND_UNIQUE_MODES.has(findUniqueMode)) {
|
|
3688
3785
|
throw new ShapeError(
|
|
3689
3786
|
`prisma-guard: Invalid findUniqueMode "${findUniqueMode}". Allowed: ${[...VALID_FIND_UNIQUE_MODES].join(", ")}`
|
|
@@ -3694,6 +3791,11 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3694
3791
|
`prisma-guard: Invalid onScopeRelationWrite "${onScopeRelationWrite}". Allowed: ${[...VALID_ON_SCOPE_RELATION_WRITES].join(", ")}`
|
|
3695
3792
|
);
|
|
3696
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
|
+
}
|
|
3697
3799
|
const scopeFkSets = /* @__PURE__ */ new Map();
|
|
3698
3800
|
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
3699
3801
|
const fks = /* @__PURE__ */ new Set();
|
|
@@ -3719,12 +3821,12 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
3719
3821
|
const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
|
|
3720
3822
|
const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
|
|
3721
3823
|
if (missingRoots.length > 0) {
|
|
3722
|
-
if (isMutation ||
|
|
3824
|
+
if (isMutation || onMissingScopeContext === "error") {
|
|
3723
3825
|
throw new PolicyError(
|
|
3724
3826
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. All scope roots must be present.`
|
|
3725
3827
|
);
|
|
3726
3828
|
}
|
|
3727
|
-
if (
|
|
3829
|
+
if (onMissingScopeContext === "warn") {
|
|
3728
3830
|
log.warn(
|
|
3729
3831
|
`prisma-guard: Missing scope context for model "${model}": roots ${missingRoots.map((r) => `"${r}"`).join(", ")} not provided. Read proceeding with partial scope.`
|
|
3730
3832
|
);
|