mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-24 23:41:10 +02:00
Add superuser authentication check to site-settings page
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 <noreply@anthropic.com>
This commit is contained in:
parent
b8bb74ec3e
commit
ae6455069b
2 changed files with 82 additions and 4 deletions
|
|
@ -111,4 +111,13 @@ async def authenticated_route(
|
||||||
user: User = Depends(current_active_user),
|
user: User = Depends(current_active_user),
|
||||||
session: AsyncSession = Depends(get_async_session),
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { useSiteConfig } from "@/contexts/SiteConfigContext";
|
import { useSiteConfig } from "@/contexts/SiteConfigContext";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
interface SiteConfigForm {
|
interface SiteConfigForm {
|
||||||
// Header/Navbar toggles
|
// Header/Navbar toggles
|
||||||
|
|
@ -37,6 +39,7 @@ interface SiteConfigForm {
|
||||||
|
|
||||||
export default function SiteSettingsPage() {
|
export default function SiteSettingsPage() {
|
||||||
const { config, isLoading, refetch } = useSiteConfig();
|
const { config, isLoading, refetch } = useSiteConfig();
|
||||||
|
const router = useRouter();
|
||||||
const [formData, setFormData] = useState<SiteConfigForm>({
|
const [formData, setFormData] = useState<SiteConfigForm>({
|
||||||
show_pricing_link: false,
|
show_pricing_link: false,
|
||||||
show_docs_link: false,
|
show_docs_link: false,
|
||||||
|
|
@ -56,6 +59,54 @@ export default function SiteSettingsPage() {
|
||||||
custom_copyright: "SurfSense 2025",
|
custom_copyright: "SurfSense 2025",
|
||||||
});
|
});
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
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
|
// Load config into form when available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -127,14 +178,32 @@ export default function SiteSettingsPage() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
// Show loading screen while checking authentication or loading config
|
||||||
|
if (isCheckingAuth || isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-white dark:bg-black">
|
<div className="flex flex-col items-center justify-center min-h-screen space-y-4">
|
||||||
<div className="text-neutral-600 dark:text-neutral-400">Loading configuration...</div>
|
<Card className="w-[350px] bg-background/60 backdrop-blur-sm">
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-xl font-medium">
|
||||||
|
{isCheckingAuth ? "Verifying Access" : "Loading Configuration"}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{isCheckingAuth ? "Checking superuser permissions..." : "Loading site settings..."}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex justify-center py-6">
|
||||||
|
<Loader2 className="h-12 w-12 text-primary animate-spin" />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If not checking auth anymore and user is not superuser, they've been redirected
|
||||||
|
if (!isSuperuser) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-black dark:to-gray-900 py-12 px-4 sm:px-6 lg:px-8">
|
<div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 dark:from-black dark:to-gray-900 py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl mx-auto">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue