114 lines
3.3 KiB
TypeScript
114 lines
3.3 KiB
TypeScript
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
import { createHmac } from "crypto";
|
|
|
|
const supabase = createClient(
|
|
process.env.SUPABASE_URL || "",
|
|
process.env.SUPABASE_SERVICE_ROLE || "",
|
|
);
|
|
|
|
// Verify Roblox signature for server-to-server requests
|
|
function verifyRobloxSignature(
|
|
payload: string,
|
|
signature: string,
|
|
secret: string,
|
|
): boolean {
|
|
const hmac = createHmac("sha256", secret);
|
|
hmac.update(payload);
|
|
const computedSignature = hmac.digest("base64");
|
|
return computedSignature === signature;
|
|
}
|
|
|
|
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 signature = req.headers["x-roblox-signature"] as string;
|
|
const payload = JSON.stringify(req.body);
|
|
const secret = process.env.ROBLOX_SHARED_SECRET || "";
|
|
|
|
// Verify signature for security
|
|
if (
|
|
signature &&
|
|
secret &&
|
|
!verifyRobloxSignature(payload, signature, secret)
|
|
) {
|
|
console.warn("Invalid Roblox signature");
|
|
return res.status(401).json({ error: "Invalid signature" });
|
|
}
|
|
|
|
const {
|
|
roblox_user_id,
|
|
roblox_username,
|
|
player_token,
|
|
action = "authenticate",
|
|
} = req.body;
|
|
|
|
if (!roblox_user_id || !roblox_username) {
|
|
return res.status(400).json({ error: "Missing Roblox user info" });
|
|
}
|
|
|
|
// Find or create user linked to Roblox account
|
|
let { data: userData } = await supabase
|
|
.from("user_profiles")
|
|
.select("id, auth_id, email")
|
|
.eq("roblox_user_id", String(roblox_user_id))
|
|
.single();
|
|
|
|
if (!userData) {
|
|
// Create new user linked to Roblox
|
|
const { data: newUser, error: createError } = await supabase
|
|
.from("user_profiles")
|
|
.insert({
|
|
roblox_user_id: String(roblox_user_id),
|
|
roblox_username,
|
|
username: roblox_username,
|
|
user_type: "game_developer",
|
|
full_name: roblox_username,
|
|
email: `${roblox_user_id}@roblox.aethex.dev`,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (createError) {
|
|
console.error("Failed to create Roblox user:", createError);
|
|
return res.status(500).json({ error: "Failed to create user" });
|
|
}
|
|
|
|
userData = newUser;
|
|
}
|
|
|
|
// Generate game token for in-game authentication
|
|
const gameToken = require("crypto").randomBytes(32).toString("hex");
|
|
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
|
|
// Store game token
|
|
await supabase.from("game_auth_tokens").insert({
|
|
user_id: userData.id,
|
|
game: "roblox",
|
|
token: gameToken,
|
|
player_token,
|
|
expires_at: expiresAt.toISOString(),
|
|
metadata: {
|
|
roblox_user_id,
|
|
roblox_username,
|
|
},
|
|
});
|
|
|
|
return res.status(200).json({
|
|
success: true,
|
|
game_token: gameToken,
|
|
user_id: userData.id,
|
|
username: userData.username || roblox_username,
|
|
expires_in: 86400, // seconds
|
|
});
|
|
} catch (error: any) {
|
|
console.error("Roblox game auth error:", error);
|
|
return res.status(500).json({
|
|
error: error?.message || "Authentication failed",
|
|
});
|
|
}
|
|
}
|