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:
@@ -55,6 +55,11 @@
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__admin {
|
||||||
|
color: var(--accent) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
&__burger {
|
&__burger {
|
||||||
display: none;
|
display: none;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
|
|||||||
@@ -1,17 +1,30 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
|
import { Link, NavLink, Outlet, useLocation } from "react-router-dom";
|
||||||
import { api, type SiteSettings } from "../lib/api";
|
import { api, type SiteSettings } from "../lib/api";
|
||||||
|
import { canAccessAdmin } from "../lib/adminAccess";
|
||||||
import "./Layout.scss";
|
import "./Layout.scss";
|
||||||
|
|
||||||
export function Layout() {
|
export function Layout() {
|
||||||
const [site, setSite] = useState<SiteSettings | null>(null);
|
const [site, setSite] = useState<SiteSettings | null>(null);
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
const [showAdmin, setShowAdmin] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.site().then(setSite).catch(() => setSite({ name: "jmartgraphix" }));
|
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(() => {
|
useEffect(() => {
|
||||||
setMenuOpen(false);
|
setMenuOpen(false);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
@@ -47,6 +60,11 @@ export function Layout() {
|
|||||||
>
|
>
|
||||||
ArtStation
|
ArtStation
|
||||||
</a>
|
</a>
|
||||||
|
{showAdmin && (
|
||||||
|
<NavLink to="/admin" className="site-header__admin">
|
||||||
|
Admin
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,8 +90,7 @@ export function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
{recent.length === 0 ? (
|
{recent.length === 0 ? (
|
||||||
<p className="home-empty">
|
<p className="home-empty">
|
||||||
Projects will appear here once published. Use the{" "}
|
Projects will appear here once published.
|
||||||
<Link to="/admin">admin panel</Link> to add work, or import from ArtStation.
|
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="project-grid">
|
<div className="project-grid">
|
||||||
|
|||||||
Reference in New Issue
Block a user