Files
GSM/auth.js
2026-01-07 02:41:37 +01:00

212 lines
6.3 KiB
JavaScript

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;