summary refs log tree commit diff
path: root/api/src/util/route.ts
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-17 23:42:40 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-17 23:42:40 +0200
commitc20f4b4ef539a44c8e091815c5603acbf0fa8d04 (patch)
tree49e32c077cec3734d939ec4d4bb26980239f593a /api/src/util/route.ts
parent:bug: fix vanity url (diff)
downloadserver-c20f4b4ef539a44c8e091815c5603acbf0fa8d04.tar.xz
:bug: fix body parse treating null not as undefined (except for icons/avatars)
Diffstat (limited to '')
-rw-r--r--api/src/util/route.ts45
1 files changed, 36 insertions, 9 deletions
diff --git a/api/src/util/route.ts b/api/src/util/route.ts
index 6cd8f622..678ca64c 100644
--- a/api/src/util/route.ts
+++ b/api/src/util/route.ts
@@ -43,10 +43,37 @@ export interface RouteOptions {
 	};
 }
 
+// Normalizer is introduced to workaround https://github.com/ajv-validator/ajv/issues/1287
+// this removes null values as ajv doesn't treat them as undefined
+// normalizeBody allows to handle circular structures without issues
+// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license)
+const normalizeBody = (body: any = {}) => {
+	const normalizedObjectsSet = new WeakSet();
+	const normalizeObject = (object: any) => {
+		if (normalizedObjectsSet.has(object)) return;
+		normalizedObjectsSet.add(object);
+		if (Array.isArray(object)) {
+			for (const [index, value] of object.entries()) {
+				if (typeof value === "object") normalizeObject(value);
+			}
+		} else {
+			for (const [key, value] of Object.entries(object)) {
+				if (value == null) {
+					if (key === "icon" || key === "avatar" || key === "banner" || key === "splash") continue;
+					delete object[key];
+				} else if (typeof value === "object") {
+					normalizeObject(value);
+				}
+			}
+		}
+	};
+	normalizeObject(body);
+	return body;
+};
+
 export function route(opts: RouteOptions) {
-	var validate: AnyValidateFunction<any>;
+	var validate: AnyValidateFunction<any> | undefined;
 	if (opts.body) {
-		// @ts-ignore
 		validate = ajv.getSchema(opts.body);
 		if (!validate) throw new Error(`Body schema ${opts.body} not found`);
 	}
@@ -60,14 +87,14 @@ export function route(opts: RouteOptions) {
 			if (!permission.has(required)) {
 				throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string);
 			}
+		}
 
-			if (validate) {
-				const valid = validate(req.body);
-				if (!valid) {
-					const fields: Record<string, { code?: string; message: string }> = {};
-					validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" }));
-					throw FieldErrors(fields);
-				}
+		if (validate) {
+			const valid = validate(normalizeBody(req.body));
+			if (!valid) {
+				const fields: Record<string, { code?: string; message: string }> = {};
+				validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" }));
+				throw FieldErrors(fields);
 			}
 		}
 		next();