import { Router } from 'express'; import jwt from 'jsonwebtoken'; import { db, VALID_ROLES, initDiscordUsers } from '../db/init.js'; import { authenticateToken, requireRole } from '../middleware/auth.js'; import { getDiscordAuthUrl, exchangeCode, getDiscordUser, getGuildMember, getUserRole } from '../services/discord.js'; const router = Router(); // Initialize Discord users table initDiscordUsers(); // ===== Discord OAuth2 ===== // Start Discord OAuth2 flow router.get('/discord', (req, res) => { res.redirect(getDiscordAuthUrl()); }); // Discord OAuth2 callback router.get('/discord/callback', async (req, res) => { const { code, error } = req.query; // Redirect URL for frontend const frontendUrl = process.env.FRONTEND_URL || 'https://gsm.dimension47.de'; if (error) { return res.redirect(`${frontendUrl}/login?error=discord_denied`); } if (!code) { return res.redirect(`${frontendUrl}/login?error=no_code`); } try { // Exchange code for access token const tokenData = await exchangeCode(code); // Get Discord user info const discordUser = await getDiscordUser(tokenData.access_token); // Check if user is in the guild const member = await getGuildMember(discordUser.id); if (!member) { return res.redirect(`${frontendUrl}/login?error=not_in_guild`); } // Determine role based on Discord roles const role = getUserRole(member.roles); // Get display name (nickname or username) const displayName = member.nick || discordUser.global_name || discordUser.username; // Upsert user in database const existingUser = db.prepare('SELECT * FROM discord_users WHERE discord_id = ?').get(discordUser.id); let userId; if (existingUser) { // Update existing user db.prepare(` UPDATE discord_users SET username = ?, discriminator = ?, avatar = ?, role = ?, updated_at = CURRENT_TIMESTAMP WHERE discord_id = ? `).run(displayName, discordUser.discriminator || '0', discordUser.avatar, role, discordUser.id); userId = existingUser.id; } else { // Create new user const result = db.prepare(` INSERT INTO discord_users (discord_id, username, discriminator, avatar, role) VALUES (?, ?, ?, ?, ?) `).run(discordUser.id, displayName, discordUser.discriminator || '0', discordUser.avatar, role); userId = result.lastInsertRowid; } // Create JWT token const token = jwt.sign( { id: userId, discordId: discordUser.id, username: displayName, role, avatar: discordUser.avatar }, process.env.JWT_SECRET, { expiresIn: '7d' } ); // Redirect to frontend with token res.redirect(`${frontendUrl}/auth/callback?token=${token}`); } catch (err) { console.error('Discord OAuth error:', err); res.redirect(`${frontendUrl}/login?error=oauth_failed`); } }); // Get current user info router.get('/me', authenticateToken, (req, res) => { // Check if it's a Discord user if (req.user.discordId) { const user = db.prepare('SELECT id, discord_id, username, avatar, role FROM discord_users WHERE id = ?').get(req.user.id); if (!user) { return res.status(404).json({ error: 'User not found' }); } return res.json({ id: user.id, discordId: user.discord_id, username: user.username, avatar: user.avatar, role: user.role }); } // Fallback for old users (shouldn't happen after migration) const user = db.prepare('SELECT id, username, role FROM users WHERE id = ?').get(req.user.id); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json({ id: user.id, username: user.username, role: user.role }); }); // Refresh user role from Discord (useful if roles changed) router.post('/refresh-role', authenticateToken, async (req, res) => { if (!req.user.discordId) { return res.status(400).json({ error: 'Not a Discord user' }); } try { const member = await getGuildMember(req.user.discordId); if (!member) { return res.status(403).json({ error: 'No longer in guild' }); } const newRole = getUserRole(member.roles); db.prepare('UPDATE discord_users SET role = ?, updated_at = CURRENT_TIMESTAMP WHERE discord_id = ?') .run(newRole, req.user.discordId); // Generate new token with updated role const token = jwt.sign( { id: req.user.id, discordId: req.user.discordId, username: req.user.username, role: newRole, avatar: req.user.avatar }, process.env.JWT_SECRET, { expiresIn: '7d' } ); res.json({ token, role: newRole }); } catch (err) { console.error('Failed to refresh role:', err); res.status(500).json({ error: 'Failed to refresh role' }); } }); // ===== User Management (superadmin only) ===== // Get all Discord users router.get('/users', authenticateToken, requireRole('superadmin'), (req, res) => { const users = db.prepare(` SELECT id, discord_id, username, avatar, role, created_at, updated_at FROM discord_users ORDER BY created_at DESC `).all(); res.json(users); }); // Update user role (override Discord role) router.patch('/users/:id/role', authenticateToken, requireRole('superadmin'), (req, res) => { const userId = parseInt(req.params.id); const { role } = req.body; if (!VALID_ROLES.includes(role)) { return res.status(400).json({ error: 'Invalid role' }); } if (userId === req.user.id) { return res.status(400).json({ error: 'Cannot change your own role' }); } const user = db.prepare('SELECT id FROM discord_users WHERE id = ?').get(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } db.prepare('UPDATE discord_users SET role = ? WHERE id = ?').run(role, userId); res.json({ message: 'Role updated' }); }); // Delete user router.delete('/users/:id', authenticateToken, requireRole('superadmin'), (req, res) => { const userId = parseInt(req.params.id); if (userId === req.user.id) { return res.status(400).json({ error: 'Cannot delete yourself' }); } const user = db.prepare('SELECT id FROM discord_users WHERE id = ?').get(userId); if (!user) { return res.status(404).json({ error: 'User not found' }); } db.prepare('DELETE FROM discord_users WHERE id = ?').run(userId); res.json({ message: 'User deleted' }); }); export default router;