Show Admin nav only when GET /admin succeeds

Probe Authelia-protected /admin with credentials; keep the public
site free of admin links for anonymous visitors.
This commit is contained in:
2026-07-24 08:48:20 -04:00
parent 43928f7a49
commit af5bfc1b75
4 changed files with 48 additions and 2 deletions
+5
View File
@@ -55,6 +55,11 @@
opacity: 0.85;
}
&__admin {
color: var(--accent) !important;
font-weight: 500;
}
&__burger {
display: none;
width: 40px;
+18
View File
@@ -1,17 +1,30 @@
import { useEffect, useState } from "react";
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
import { api, type SiteSettings } from "../lib/api";
import { canAccessAdmin } from "../lib/adminAccess";
import "./Layout.scss";
export function Layout() {
const [site, setSite] = useState<SiteSettings | null>(null);
const [menuOpen, setMenuOpen] = useState(false);
const [showAdmin, setShowAdmin] = useState(false);
const location = useLocation();
useEffect(() => {
api.site().then(setSite).catch(() => setSite({ name: "jmartgraphix" }));
}, []);
// Only surface Admin when Traefik/Authelia already allows GET /admin
useEffect(() => {
let cancelled = false;
canAccessAdmin().then((ok) => {
if (!cancelled) setShowAdmin(ok);
});
return () => {
cancelled = true;
};
}, [location.pathname]);
useEffect(() => {
setMenuOpen(false);
}, [location.pathname]);
@@ -47,6 +60,11 @@ export function Layout() {
>
ArtStation
</a>
{showAdmin && (
<NavLink to="/admin" className="site-header__admin">
Admin
</NavLink>
)}
</nav>
</div>
</header>
+24
View File
@@ -0,0 +1,24 @@
/**
* Probe Traefik/Authelia protection on /admin.
* Returns true only when a same-origin GET succeeds (HTTP 200),
* meaning the visitor is already authenticated for admin.
* Failures (401/403/302/network) keep the public site admin-free.
*/
export async function canAccessAdmin(): Promise<boolean> {
try {
const res = await fetch("/admin", {
method: "GET",
credentials: "include",
redirect: "manual",
cache: "no-store",
headers: { Accept: "text/html" },
});
// opaqueredirect (0) = browser blocked reading a cross-origin redirect
// 3xx with redirect:manual also means not authorized for the resource
if (res.type === "opaqueredirect") return false;
if (res.status >= 300 && res.status < 400) return false;
return res.status === 200;
} catch {
return false;
}
}
+1 -2
View File
@@ -90,8 +90,7 @@ export function HomePage() {
</div>
{recent.length === 0 ? (
<p className="home-empty">
Projects will appear here once published. Use the{" "}
<Link to="/admin">admin panel</Link> to add work, or import from ArtStation.
Projects will appear here once published.
</p>
) : (
<div className="project-grid">