aethex-forge/api/web3/verify.ts
2025-11-08 11:03:25 +00:00

131 lines
3.9 KiB
TypeScript

import type { VercelRequest, VercelResponse } from "@vercel/node";
import { createClient } from "@supabase/supabase-js";
import { verifyMessage } from "ethers";
const supabase = createClient(
process.env.SUPABASE_URL || "",
process.env.SUPABASE_SERVICE_ROLE || "",
);
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 { wallet_address, signature, message, nonce } = req.body;
if (!wallet_address || !signature || !message || !nonce) {
return res.status(400).json({ error: "Missing required fields" });
}
const normalizedAddress = wallet_address.toLowerCase();
// Verify the signature
let recoveredAddress: string;
try {
recoveredAddress = verifyMessage(message, signature).toLowerCase();
} catch (error) {
console.error("Signature verification failed:", error);
return res.status(401).json({ error: "Invalid signature" });
}
// Check if recovered address matches provided address
if (recoveredAddress !== normalizedAddress) {
return res.status(401).json({ error: "Signature does not match wallet" });
}
// Verify nonce exists and is not expired
const { data: nonceData, error: nonceError } = await supabase
.from("web3_nonces")
.select("*")
.eq("wallet_address", normalizedAddress)
.eq("nonce", nonce)
.single();
if (nonceError || !nonceData) {
return res.status(401).json({ error: "Invalid or expired nonce" });
}
const expiresAt = new Date(nonceData.expires_at);
if (expiresAt < new Date()) {
return res.status(401).json({ error: "Nonce has expired" });
}
// Mark nonce as used
await supabase
.from("web3_nonces")
.update({ used_at: new Date().toISOString() })
.eq("nonce", nonce);
// Check if user already exists with this wallet
const { data: existingUser } = await supabase
.from("user_profiles")
.select("id, auth_id")
.eq("wallet_address", normalizedAddress)
.single();
if (existingUser) {
// User exists, link to their account
return res.status(200).json({
success: true,
existing_user: true,
user_id: existingUser.id,
});
}
// Create new Web3 user
const email = `${normalizedAddress.substring(2)}@web3.aethex.dev`;
const username = normalizedAddress.substring(2, 10);
// Create Supabase auth user
const { data: authData, error: authError } =
await supabase.auth.admin.createUser({
email,
password: require("crypto").randomBytes(32).toString("hex"),
email_confirm: true,
user_metadata: {
wallet_address: normalizedAddress,
auth_method: "web3",
},
});
if (authError || !authData.user) {
console.error("Failed to create auth user:", authError);
return res.status(500).json({ error: "Failed to create account" });
}
// Create user profile
const { data: profileData, error: profileError } = await supabase
.from("user_profiles")
.insert({
auth_id: authData.user.id,
email,
username,
wallet_address: normalizedAddress,
user_type: "game_developer",
full_name: `Web3 User ${username}`,
})
.select()
.single();
if (profileError) {
console.error("Failed to create user profile:", profileError);
return res.status(500).json({ error: "Failed to create user profile" });
}
return res.status(200).json({
success: true,
user_id: profileData.id,
email,
username,
wallet_address: normalizedAddress,
});
} catch (error: any) {
console.error("Web3 verification error:", error);
return res.status(500).json({
error: error?.message || "Verification failed",
});
}
}