From ae6455069b2cd5225513ef23b4eb3af6d1bcf7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oj=C4=81rs=20Kapteinis?= Date: Tue, 18 Nov 2025 15:07:39 +0200 Subject: [PATCH] Add superuser authentication check to site-settings page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhanced security for site configuration admin panel to ensure only superusers can access and modify site settings. Backend changes: - Enhanced /verify-token endpoint to return user information including is_superuser flag - Now returns user.id, email, is_active, is_superuser, and is_verified status - Allows frontend to verify user permissions before loading sensitive pages Frontend changes: - Added superuser verification check to site-settings page - Verifies user is authenticated AND is_superuser before loading page - Shows loading screen with "Verifying Access" message during check - Redirects non-superusers to dashboard with clear error toast - Prevents unauthorized access to admin-only configuration Security improvements: - Layered protection: Dashboard auth + Page-level superuser check + Backend API validation - Clear error messages for non-superusers - Graceful UX with loading states and redirects - Backend API endpoints already protected with superuser checks 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- surfsense_backend/app/app.py | 11 ++- .../app/dashboard/site-settings/page.tsx | 75 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/surfsense_backend/app/app.py b/surfsense_backend/app/app.py index 7925a8945..ed0269bed 100644 --- a/surfsense_backend/app/app.py +++ b/surfsense_backend/app/app.py @@ -111,4 +111,13 @@ async def authenticated_route( user: User = Depends(current_active_user), session: AsyncSession = Depends(get_async_session), ): - return {"message": "Token is valid"} + return { + "message": "Token is valid", + "user": { + "id": str(user.id), + "email": user.email, + "is_active": user.is_active, + "is_superuser": user.is_superuser, + "is_verified": user.is_verified, + } + } diff --git a/surfsense_web/app/dashboard/site-settings/page.tsx b/surfsense_web/app/dashboard/site-settings/page.tsx index 787acf863..338708124 100644 --- a/surfsense_web/app/dashboard/site-settings/page.tsx +++ b/surfsense_web/app/dashboard/site-settings/page.tsx @@ -4,6 +4,8 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { useSiteConfig } from "@/contexts/SiteConfigContext"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Loader2 } from "lucide-react"; interface SiteConfigForm { // Header/Navbar toggles @@ -37,6 +39,7 @@ interface SiteConfigForm { export default function SiteSettingsPage() { const { config, isLoading, refetch } = useSiteConfig(); + const router = useRouter(); const [formData, setFormData] = useState({ show_pricing_link: false, show_docs_link: false, @@ -56,6 +59,54 @@ export default function SiteSettingsPage() { custom_copyright: "SurfSense 2025", }); const [isSaving, setIsSaving] = useState(false); + const [isCheckingAuth, setIsCheckingAuth] = useState(true); + const [isSuperuser, setIsSuperuser] = useState(false); + + // Check if user is a superuser + useEffect(() => { + const checkSuperuser = async () => { + try { + const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000"; + const token = localStorage.getItem("access_token"); + + if (!token) { + router.push("/login"); + return; + } + + const response = await fetch(`${backendUrl}/verify-token`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (!response.ok) { + router.push("/login"); + return; + } + + const data = await response.json(); + + if (!data.user?.is_superuser) { + toast.error("Access Denied", { + description: "You must be a superuser to access site settings.", + duration: 5000, + }); + router.push("/dashboard"); + return; + } + + setIsSuperuser(true); + } catch (error) { + console.error("Error verifying superuser status:", error); + router.push("/login"); + } finally { + setIsCheckingAuth(false); + } + }; + + checkSuperuser(); + }, [router]); // Load config into form when available useEffect(() => { @@ -127,14 +178,32 @@ export default function SiteSettingsPage() { } }; - if (isLoading) { + // Show loading screen while checking authentication or loading config + if (isCheckingAuth || isLoading) { return ( -
-
Loading configuration...
+
+ + + + {isCheckingAuth ? "Verifying Access" : "Loading Configuration"} + + + {isCheckingAuth ? "Checking superuser permissions..." : "Loading site settings..."} + + + + + +
); } + // If not checking auth anymore and user is not superuser, they've been redirected + if (!isSuperuser) { + return null; + } + return (