From 9a262b59d06d6889184b4c40e2d485a985c919c9 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Wed, 13 May 2026 12:06:31 +0200 Subject: [PATCH] Add proper Bloxlink API key validation Rework validate and lookup handlers to centralize API key/body parsing and improve upstream error handling. handleValidate now accepts a request context, queries the Bloxlink guild discord-to-roblox endpoint, and maps specific upstream errors (including treating "User not found" as a valid empty result). handleLookup signature was updated similarly and now relies on pre-parsed apiKey/body; request.json and header validation were moved into the main handler to avoid double-parsing and to return clearer 400/500 statuses when appropriate. --- bloxlink/index.js | 64 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/bloxlink/index.js b/bloxlink/index.js index bb00995..b453919 100644 --- a/bloxlink/index.js +++ b/bloxlink/index.js @@ -52,26 +52,48 @@ function buildPayload(user) { }; } -async function handleValidate(request) { - const apiKey = request.headers.get("X-Bloxlink-Api-Key"); - if (!apiKey) { - return jsonResponse({ error: "Missing X-Bloxlink-Api-Key header" }, { status: 400 }); +async function handleValidate({ apiKey, body }) { + const { guild_id: guildId } = body; + if (!guildId) { + return jsonResponse({ error: "Invalid request body" }, { status: 400 }); } if (!API_KEY_REGEX.test(apiKey)) { return jsonResponse({ error: "Invalid Bloxlink API key format" }, { status: 400 }); } - return jsonResponse({}); -} + const res = await fetch( + `${BLOXLINK_API}/guilds/${guildId}/discord-to-roblox`, + { headers: { Authorization: apiKey } }, + ); -async function handleLookup(request, env) { - let body; + let data = null; try { - body = await request.json(); + data = await res.json(); } catch { - return jsonResponse({ error: "Invalid request body" }, { status: 400 }); + // Some upstream errors may not be JSON. } + const upstreamError = data?.error; + if ( + upstreamError === "You must provide an api-key" || + upstreamError === "Invalid API Key" || + upstreamError === "Guild ID does not match API Key" + ) { + return jsonResponse({ error: upstreamError }, { status: 400 }); + } + + // "User not found" means the key is valid — no user was provided intentionally. + if (res.ok || upstreamError === "User not found") { + return jsonResponse({}); + } + + return jsonResponse( + { error: `Bloxlink API responded with ${res.status} — it may be experiencing an outage` }, + { status: 500 }, + ); +} + +async function handleLookup(env, { apiKey, body }) { const { guild_id: guildId, user_id: userId } = body; if (!guildId || !userId) { return jsonResponse({ error: "Invalid request body" }, { status: 400 }); @@ -86,11 +108,6 @@ async function handleLookup(request, env) { }); } - const apiKey = request.headers.get("X-Bloxlink-Api-Key"); - if (!apiKey) { - return jsonResponse({ error: "Missing X-Bloxlink-Api-Key header" }, { status: 400 }); - } - let robloxId; try { robloxId = await fetchRobloxId(apiKey, guildId, userId); @@ -119,12 +136,25 @@ async function handleRequest(request, env) { return jsonResponse({ error: "Method Not Allowed" }, { status: 405 }); } + const apiKey = request.headers.get("X-Bloxlink-Api-Key"); + if (!apiKey) { + return jsonResponse({ error: "Missing X-Bloxlink-Api-Key header" }, { status: 400 }); + } + + let body; + try { + body = await request.json(); + } catch { + return jsonResponse({ error: "Invalid request body" }, { status: 400 }); + } + const url = new URL(request.url); + const requestContext = { apiKey, body }; if (url.pathname === "/validate") { - return handleValidate(request); + return handleValidate(requestContext); } - return handleLookup(request, env); + return handleLookup(env, requestContext); } export default Sentry.withSentry(