From e25ae8607e3980426581a8d22d5cbfc5f35b31bd Mon Sep 17 00:00:00 2001 From: imnyang Date: Sat, 23 May 2026 23:22:59 +0900 Subject: [PATCH] feat: sanitize post metadata and implement webhook fallback for 400 errors --- apps/backend/src/routes/post.ts | 58 +++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/apps/backend/src/routes/post.ts b/apps/backend/src/routes/post.ts index aa3e0e5..21a0a87 100644 --- a/apps/backend/src/routes/post.ts +++ b/apps/backend/src/routes/post.ts @@ -76,25 +76,63 @@ async function sendDiscordNotification(payload: { return; } + const safeTitle = (payload.title || "New post").slice(0, 256); + const safeAuthor = (payload.author || "unknown").slice(0, 256); + const safeTags = (payload.tags.join(", ") || "None").slice(0, 1024); + + const isValidHttpUrl = (value?: string) => { + if (!value) return false; + try { + const parsed = new URL(value); + return parsed.protocol === "http:" || parsed.protocol === "https:"; + } catch { + return false; + } + }; + + const safeUrl = isValidHttpUrl(payload.url) ? payload.url : undefined; + const safeImageUrl = isValidHttpUrl(payload.imageUrl) ? payload.imageUrl : undefined; + + const embed: Record = { + title: safeTitle, + author: { name: safeAuthor }, + fields: [{ name: "Tags", value: safeTags }], + color: 0x00ff00, + }; + + if (safeUrl) embed.url = safeUrl; + if (safeImageUrl) embed.image = { url: safeImageUrl }; + + const body = { + content: `${safeTitle}${safeUrl ? `\n${safeUrl}` : ""}`, + embeds: [embed], + }; + try { const response = await fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - embeds: [{ - title: payload.title, - url: payload.url, - author: { name: payload.author }, - fields: [{ name: "Tags", value: payload.tags.join(", ") || "None" }], - image: payload.imageUrl ? { url: payload.imageUrl } : undefined, - color: 0x00ff00, - }], - }), + body: JSON.stringify(body), }); if (!response.ok) { const errorText = await response.text().catch(() => ""); console.error(`[Discord Webhook] failed status=${response.status} body=${errorText}`); + + if (response.status === 400) { + const fallbackResponse = await fetch(webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + content: `${safeTitle}${safeUrl ? `\n${safeUrl}` : ""}\nAuthor: ${safeAuthor}\nTags: ${safeTags}`, + }), + }); + + if (!fallbackResponse.ok) { + const fallbackErrorText = await fallbackResponse.text().catch(() => ""); + console.error(`[Discord Webhook] fallback failed status=${fallbackResponse.status} body=${fallbackErrorText}`); + } + } } } catch (error) { console.error("[Discord Webhook Error]", error);