diff --git a/gsm-backend/middleware/auth.js b/gsm-backend/middleware/auth.js index 49d1c76..3642d4b 100644 --- a/gsm-backend/middleware/auth.js +++ b/gsm-backend/middleware/auth.js @@ -48,10 +48,20 @@ export function requireRole(minRole) { const userRole = req.user?.role || 'user'; const userLevel = ROLE_HIERARCHY[userRole] || 0; const requiredLevel = ROLE_HIERARCHY[minRole] || 0; - + if (userLevel < requiredLevel) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; } + +export function rejectGuest(req, res, next) { + if (!req.user) { + return res.status(401).json({ error: 'Authentication required' }); + } + if (req.user.isGuest || req.user.role === 'guest') { + return res.status(403).json({ error: 'Guests cannot access server details' }); + } + next(); +} diff --git a/gsm-backend/routes/servers.js b/gsm-backend/routes/servers.js index b95e329..63395db 100644 --- a/gsm-backend/routes/servers.js +++ b/gsm-backend/routes/servers.js @@ -2,7 +2,7 @@ import { Router } from 'express'; import { readFileSync } from 'fs'; import { dirname, join } from 'path'; import { fileURLToPath } from 'url'; -import { authenticateToken, optionalAuth, requireRole } from '../middleware/auth.js'; +import { authenticateToken, optionalAuth, requireRole, rejectGuest } from '../middleware/auth.js'; import { getServerStatus, startServer, stopServer, restartServer, getConsoleLog, getProcessUptime, listFactorioSaves, createFactorioWorld, deleteFactorioSave, getFactorioCurrentSave, isHostFailed, listZomboidConfigs, readZomboidConfig, writeZomboidConfig, listPalworldConfigs, readPalworldConfig, writePalworldConfig, readTerrariaConfig, writeTerrariaConfig, readOpenTTDConfig, writeOpenTTDConfig } from '../services/ssh.js'; import { sendRconCommand, getPlayers, getPlayerList } from '../services/rcon.js'; import { getServerMetricsHistory, getCurrentMetrics } from '../services/prometheus.js'; @@ -501,8 +501,8 @@ router.put("/openttd/config", authenticateToken, requireRole("moderator"), async }); -// Get single server -router.get('/:id', optionalAuth, async (req, res) => { +// Get single server (guests not allowed) +router.get('/:id', authenticateToken, rejectGuest, async (req, res) => { const config = loadConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) { @@ -578,8 +578,8 @@ router.get('/:id', optionalAuth, async (req, res) => { } }); -// Get metrics history from Prometheus -router.get('/:id/metrics/history', optionalAuth, async (req, res) => { +// Get metrics history from Prometheus (guests not allowed) +router.get('/:id/metrics/history', authenticateToken, rejectGuest, async (req, res) => { const config = loadConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) return res.status(404).json({ error: 'Server not found' }); @@ -706,8 +706,8 @@ router.post('/:id/restart', authenticateToken, requireRole('moderator'), async ( } }); -// Get whitelist (with server-side caching) -router.get('/:id/whitelist', optionalAuth, async (req, res) => { +// Get whitelist (with server-side caching, guests not allowed) +router.get('/:id/whitelist', authenticateToken, rejectGuest, async (req, res) => { const config = loadConfig(); const server = config.servers.find(s => s.id === req.params.id); if (!server) return res.status(404).json({ error: 'Server not found' }); diff --git a/gsm-frontend/src/App.jsx b/gsm-frontend/src/App.jsx index cfa4f80..ae8e491 100644 --- a/gsm-frontend/src/App.jsx +++ b/gsm-frontend/src/App.jsx @@ -1,11 +1,20 @@ import { useState } from 'react' import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' -import { UserProvider } from './context/UserContext' +import { UserProvider, useUser } from './context/UserContext' import Dashboard from './pages/Dashboard' import ServerDetail from './pages/ServerDetail' import LoginPage from './pages/LoginPage' import AuthCallback from './pages/AuthCallback' +function ProtectedServerDetail({ onLogout }) { + const { isGuest, loading } = useUser() + + if (loading) return null + if (isGuest) return + + return +} + export default function App() { const [token, setToken] = useState(localStorage.getItem('gsm_token')) @@ -24,7 +33,7 @@ export default function App() { : } /> - : } /> + : } /> } /> } /> } /> diff --git a/gsm-frontend/src/components/ServerCard.jsx b/gsm-frontend/src/components/ServerCard.jsx index 3fecf6a..7914534 100644 --- a/gsm-frontend/src/components/ServerCard.jsx +++ b/gsm-frontend/src/components/ServerCard.jsx @@ -64,7 +64,7 @@ const getServerInfo = (serverName) => { return null } -export default function ServerCard({ server, onClick, isAuthenticated, displaySettings }) { +export default function ServerCard({ server, onClick, isAuthenticated, isGuest, displaySettings }) { const defaultInfo = getServerInfo(server.name) // Merge default info with database display settings (database takes priority) @@ -117,10 +117,12 @@ export default function ServerCard({ server, onClick, isAuthenticated, displaySe const statusBadge = getStatusBadge() const isUnreachable = server.status === 'unreachable' + const isClickable = !isUnreachable && !isGuest && onClick + return (
{/* Header */}
diff --git a/gsm-frontend/src/context/UserContext.jsx b/gsm-frontend/src/context/UserContext.jsx index 1bbd94f..9bf2b0c 100644 --- a/gsm-frontend/src/context/UserContext.jsx +++ b/gsm-frontend/src/context/UserContext.jsx @@ -30,6 +30,7 @@ export function UserProvider({ children, token, onLogout }) { token, loading, role: user?.role || 'user', + isGuest: user?.isGuest || user?.role === 'guest', isModerator: ['moderator', 'superadmin'].includes(user?.role), isSuperadmin: user?.role === 'superadmin' } diff --git a/gsm-frontend/src/pages/Dashboard.jsx b/gsm-frontend/src/pages/Dashboard.jsx index 9dffb31..65a1717 100644 --- a/gsm-frontend/src/pages/Dashboard.jsx +++ b/gsm-frontend/src/pages/Dashboard.jsx @@ -255,8 +255,9 @@ export default function Dashboard({ onLogout }) { > navigate('/server/' + server.id)} + onClick={isGuest ? undefined : () => navigate('/server/' + server.id)} isAuthenticated={isAuthenticated} + isGuest={isGuest} displaySettings={displaySettings[server.id]} />