test: verify share bot API contract

This commit is contained in:
2026-08-16 01:19:20 +07:00 Unverified
parent c9f01231c6
commit f32171a576
3 changed files with 161 additions and 56 deletions
+19 -56
View File
@@ -1,11 +1,13 @@
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 { getDiscordSenderProfile } from "@/lib/discord/discord";
import {
DiscordProfileLookupError,
getDiscordSenderProfile,
} from "@/lib/discord/discord";
buildBotShareResponse,
hasValidShareBotBearer,
rejectSuppliedBotProfile,
resolveBotSenderProfile,
} from "@/lib/share/bot";
import { createShare } from "@/lib/share/create";
import { ShareHttpError, shareErrorResponse } from "@/lib/share/http-error";
import { enforceShareRateLimit } from "@/lib/share/rate-limit";
@@ -19,19 +21,13 @@ 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);
return hasValidShareBotBearer(authorization, expected);
}
export async function POST(request: Request) {
@@ -53,19 +49,7 @@ export async function POST(request: Request) {
} catch {
throw new ShareHttpError("Malformed multipart form data", 400);
}
for (const field of [
"displayName",
"avatarUrl",
"authorName",
"authorAvatarUrl",
]) {
if (formData.has(field)) {
throw new ShareHttpError(
`Profile field ${field} must not be supplied`,
400,
);
}
}
rejectSuppliedBotProfile(formData);
const file = formData.get("file");
const senderDiscordId = formData.get("senderDiscordId");
if (!(file instanceof File)) {
@@ -83,21 +67,10 @@ export async function POST(request: Request) {
validateOptionalImage(formData.get("image")),
]);
const description = validateShareDescription(formData.get("description"));
let profile;
try {
profile = await getDiscordSenderProfile(senderDiscordId);
} catch (error) {
if (error instanceof DiscordProfileLookupError) {
throw new ShareHttpError("Discord profile lookup is unavailable", 503);
}
throw error;
}
if (!profile) {
throw new ShareHttpError(
"Discord sender was not found in the configured guild",
422
);
}
const profile = await resolveBotSenderProfile(
senderDiscordId,
getDiscordSenderProfile,
);
await enforceShareRateLimit({
key: `sender:${senderDiscordId}`,
@@ -123,24 +96,14 @@ export async function POST(request: Request) {
avatarUrl: profile.avatarUrl,
},
});
const url = `${baseUrl}/share/${share.id}`;
return Response.json(
{
success: true,
share: {
id: share.id,
url,
author: {
displayName,
avatarUrl: profile.avatarUrl,
},
imageUrl: share.imageCdnId
? `${baseUrl}${cdnUrl(share.imageCdnId)}`
: null,
createdAt: share.createdAt.toISOString(),
},
},
buildBotShareResponse({
baseUrl,
share,
displayName,
avatarUrl: profile.avatarUrl,
imagePath: share.imageCdnId ? cdnUrl(share.imageCdnId) : null,
}),
{ status: 201 }
);
} catch (error) {