import { timingSafeEqual } from "node:crypto"; import { cdnUrl } from "@/lib/cdn-images"; import { ensureDiscordUser } from "@/lib/auth/discord-user"; import { getCanonicalUrl, getShareBotSecret } from "@/lib/config/server"; import { getDiscordMemberProfile } from "@/lib/discord/discord"; import { createShare } from "@/lib/share/create"; import { ShareHttpError, shareErrorResponse } from "@/lib/share/http-error"; import { enforceShareRateLimit } from "@/lib/share/rate-limit"; import { validateOptionalImage, validateShareDescription, validateTextFile, } from "@/lib/share/validation"; export const dynamic = "force-dynamic"; function hasValidBearerToken(request: Request) { const authorization = request.headers.get("authorization"); if (!authorization?.startsWith("Bearer ")) return false; let expected: string; try { expected = getShareBotSecret(); } catch { throw new ShareHttpError("Share bot API is not configured", 503); } const supplied = authorization.slice("Bearer ".length); const suppliedBytes = Buffer.from(supplied); const expectedBytes = Buffer.from(expected); return suppliedBytes.length === expectedBytes.length && timingSafeEqual(suppliedBytes, expectedBytes); } export async function POST(request: Request) { try { if (!hasValidBearerToken(request)) { throw new ShareHttpError("Unauthorized", 401); } await enforceShareRateLimit({ key: "bot:global", limit: 60, windowSeconds: 60 * 60, }); const formData = await request.formData(); const file = formData.get("file"); const senderDiscordId = formData.get("senderDiscordId"); if (!(file instanceof File)) { throw new ShareHttpError("A .txt file is required", 400); } if ( typeof senderDiscordId !== "string" || !/^\d{15,22}$/.test(senderDiscordId) ) { throw new ShareHttpError("A valid senderDiscordId is required", 400); } const [content, image] = await Promise.all([ validateTextFile(file), validateOptionalImage(formData.get("image")), ]); const description = validateShareDescription(formData.get("description")); const profile = await getDiscordMemberProfile(senderDiscordId); if (!profile) { throw new ShareHttpError( "Discord sender was not found in the configured guild", 422 ); } await enforceShareRateLimit({ key: `sender:${senderDiscordId}`, limit: 10, windowSeconds: 60 * 60, }); const displayName = profile.globalName?.trim() || profile.username; const user = await ensureDiscordUser({ discordId: senderDiscordId, displayName, avatarUrl: profile.avatarUrl, }); const share = await createShare({ content, description, image, source: "bot", author: { userId: user.id, discordId: senderDiscordId, displayName, avatarUrl: profile.avatarUrl, }, }); const url = `${getCanonicalUrl()}/share/${share.id}`; return Response.json( { success: true, share: { id: share.id, url, author: { displayName, avatarUrl: profile.avatarUrl, }, imageUrl: cdnUrl(share.imageCdnId) || null, createdAt: share.createdAt.toISOString(), }, }, { status: 201 } ); } catch (error) { return shareErrorResponse(error); } }