import { Router } from 'express'; import { v4 as uuidv4 } from 'uuid'; import db from '../db.js'; import { generateToken } from '../middleware/auth.js'; const router = Router(); const DISCORD_API = 'https://discord.com/api'; const DISCORD_CLIENT_ID = process.env.DISCORD_CLIENT_ID; const DISCORD_CLIENT_SECRET = process.env.DISCORD_CLIENT_SECRET; const DISCORD_REDIRECT_URI = process.env.DISCORD_REDIRECT_URI; const WHITELIST = new Set([ '1207017997173137481' ]); function getRedirectOrigin() { return process.env.FRONTEND_URL || 'http://localhost:3000'; } router.get('/discord', (req, res) => { const next = req.query.next || '/'; const safeNext = next.startsWith('/') && !next.startsWith('//') ? next : '/'; const params = new URLSearchParams({ client_id: DISCORD_CLIENT_ID, redirect_uri: DISCORD_REDIRECT_URI, response_type: 'code', scope: 'identify', state: safeNext, }); res.redirect(`${DISCORD_API}/oauth2/authorize?${params.toString()}`); }); router.get('/discord/callback', async (req, res) => { const { code, state, error } = req.query; if (error) { return res.redirect(`${getRedirectOrigin()}/login?error=${encodeURIComponent(error)}`); } if (!code || !state) { return res.redirect(`${getRedirectOrigin()}/login?error=missing_code_or_state`); } try { const tokenRes = await fetch(`${DISCORD_API}/oauth2/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: DISCORD_CLIENT_ID, client_secret: DISCORD_CLIENT_SECRET, grant_type: 'authorization_code', code, redirect_uri: DISCORD_REDIRECT_URI, }).toString(), }); if (!tokenRes.ok) { const text = await tokenRes.text(); throw new Error(`Discord token exchange failed: ${tokenRes.status} ${text}`); } const token = await tokenRes.json(); const userRes = await fetch(`${DISCORD_API}/users/@me`, { headers: { Authorization: `Bearer ${token.access_token}` }, }); if (!userRes.ok) { const text = await userRes.text(); throw new Error(`Discord user fetch failed: ${userRes.status} ${text}`); } const discordUser = await userRes.json(); if (!WHITELIST.has(discordUser.id)) { return res.redirect(`${getRedirectOrigin()}/login?error=not_allowed`); } const existing = db.prepare('SELECT id FROM users WHERE discord_id = ?').get(discordUser.id); let userId; if (existing) { userId = existing.id; db.prepare( "UPDATE users SET username = ?, global_name = ?, avatar = ?, updated_at = datetime('now') WHERE id = ?" ).run(discordUser.username, discordUser.global_name, discordUser.avatar, userId); } else { userId = uuidv4(); db.prepare( 'INSERT INTO users (id, username, global_name, avatar, discord_id) VALUES (?, ?, ?, ?, ?)' ).run(userId, discordUser.username, discordUser.global_name, discordUser.avatar, discordUser.id); } const jwt = generateToken(userId); const redirectTo = state.startsWith('/') ? state : '/'; res.redirect(`${getRedirectOrigin()}${redirectTo}?token=${jwt}`); } catch (e) { const msg = e instanceof Error ? e.message : 'unknown error'; res.redirect(`${getRedirectOrigin()}/login?error=${encodeURIComponent(msg.slice(0, 200))}`); } }); export default router;