From ed0f77ca7cbd88e7ad47f0b303416a65c5427d57 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 8 Nov 2025 09:54:13 +0000 Subject: [PATCH] Create Roblox OAuth callback handler cgen-69b8987cff38461d9c00d268861cf6bb --- api/roblox/oauth-callback.ts | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 api/roblox/oauth-callback.ts diff --git a/api/roblox/oauth-callback.ts b/api/roblox/oauth-callback.ts new file mode 100644 index 00000000..37003438 --- /dev/null +++ b/api/roblox/oauth-callback.ts @@ -0,0 +1,88 @@ +import type { VercelRequest, VercelResponse } from "@vercel/node"; + +interface RobloxTokenResponse { + access_token: string; + token_type: string; + expires_in: number; + refresh_token?: string; +} + +interface RobloxUserInfo { + sub: string; + preferred_username: string; + name: string; + email?: string; + picture?: string; +} + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== "POST") { + res.setHeader("Allow", "POST"); + return res.status(405).json({ error: "Method not allowed" }); + } + + try { + const { code, state } = req.body; + + if (!code) { + return res.status(400).json({ error: "Authorization code is required" }); + } + + const clientId = process.env.ROBLOX_OAUTH_CLIENT_ID; + const clientSecret = process.env.ROBLOX_OAUTH_CLIENT_SECRET; + const redirectUri = + process.env.ROBLOX_OAUTH_REDIRECT_URI || "https://aethex.dev/roblox-callback"; + + if (!clientId || !clientSecret) { + return res.status(500).json({ error: "Roblox OAuth not configured" }); + } + + // Exchange code for token + const tokenResponse = await fetch("https://apis.roblox.com/oauth/token", { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + client_id: clientId, + client_secret: clientSecret, + redirect_uri: redirectUri, + }).toString(), + }); + + if (!tokenResponse.ok) { + const errorData = await tokenResponse.text(); + console.error("Roblox token exchange failed:", errorData); + return res.status(401).json({ error: "Token exchange failed" }); + } + + const tokenData: RobloxTokenResponse = await tokenResponse.json(); + + // Get user info with access token + const userResponse = await fetch("https://apis.roblox.com/oauth/v1/userinfo", { + headers: { Authorization: `Bearer ${tokenData.access_token}` }, + }); + + if (!userResponse.ok) { + console.error("Failed to fetch Roblox user info"); + return res.status(401).json({ error: "Failed to fetch user info" }); + } + + const userInfo: RobloxUserInfo = await userResponse.json(); + + // Return user info to frontend + return res.status(200).json({ + roblox_user_id: userInfo.sub, + roblox_username: userInfo.preferred_username, + roblox_name: userInfo.name, + roblox_email: userInfo.email || null, + roblox_avatar: userInfo.picture || null, + state, + }); + } catch (error: any) { + console.error("Roblox OAuth callback error:", error); + return res.status(500).json({ + error: error?.message || "Authentication failed", + }); + } +}