Restrict server detail access for guests
All checks were successful
Deploy GSM / deploy (push) Successful in 26s

- Add isGuest flag to UserContext
- Block guests from navigating to /server/:id route
- Make ServerCards non-clickable for guests
- Add rejectGuest middleware to backend
- Protect server detail endpoints (/:id, /metrics/history, /whitelist)

Guests can now only view the dashboard overview without accessing
individual server details.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 20:57:34 +01:00
parent e88e246be6
commit 3dc7e9e7e7
6 changed files with 37 additions and 14 deletions

View File

@@ -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 <Navigate to="/" replace />
return <ServerDetail onLogout={onLogout} />
}
export default function App() {
const [token, setToken] = useState(localStorage.getItem('gsm_token'))
@@ -24,7 +33,7 @@ export default function App() {
<BrowserRouter>
<Routes>
<Route path="/" element={token ? <Dashboard onLogout={handleLogout} /> : <Navigate to="/login" replace />} />
<Route path="/server/:serverId" element={token ? <ServerDetail onLogout={handleLogout} /> : <Navigate to="/login" replace />} />
<Route path="/server/:serverId" element={token ? <ProtectedServerDetail onLogout={handleLogout} /> : <Navigate to="/login" replace />} />
<Route path="/login" element={<LoginPage onLogin={handleLogin} />} />
<Route path="/auth/callback" element={<AuthCallback onLogin={handleLogin} />} />
<Route path="*" element={<Navigate to="/" replace />} />

View File

@@ -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 (
<div
className={isUnreachable ? "card p-5 opacity-50 cursor-not-allowed" : "card card-clickable p-5"}
onClick={isUnreachable ? undefined : onClick}
className={isUnreachable ? "card p-5 opacity-50 cursor-not-allowed" : (isClickable ? "card card-clickable p-5" : "card p-5")}
onClick={isClickable ? onClick : undefined}
>
{/* Header */}
<div className="flex items-center justify-between mb-2">

View File

@@ -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'
}

View File

@@ -255,8 +255,9 @@ export default function Dashboard({ onLogout }) {
>
<ServerCard
server={server}
onClick={() => navigate('/server/' + server.id)}
onClick={isGuest ? undefined : () => navigate('/server/' + server.id)}
isAuthenticated={isAuthenticated}
isGuest={isGuest}
displaySettings={displaySettings[server.id]}
/>
</div>