From 378f04d183958c5b1af5ecceaf42f2af1e4c3456 Mon Sep 17 00:00:00 2001 From: Louis King Date: Thu, 30 Apr 2026 00:56:55 +0100 Subject: [PATCH] Hide admin UI when OIDC is disabled --- src/meshcore_hub/web/app.py | 25 +++++++++++-------- src/meshcore_hub/web/static/js/spa/app.js | 2 +- .../web/static/js/spa/components.js | 2 +- tests/test_web/test_oidc.py | 12 ++++----- 4 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/meshcore_hub/web/app.py b/src/meshcore_hub/web/app.py index 3ee6177..3ebce7b 100644 --- a/src/meshcore_hub/web/app.py +++ b/src/meshcore_hub/web/app.py @@ -1019,21 +1019,24 @@ def create_app( @app.api_route("/{path:path}", methods=["GET"], tags=["SPA"], response_model=None) async def spa_catchall(request: Request, path: str = "") -> Response: """Serve the SPA shell for all non-API routes.""" - # Admin route protection when OIDC is enabled + # Admin route protection if path.startswith("admin") and ( path == "admin" or path == "admin/" or path.startswith("admin/") ): - if request.app.state.oidc_enabled: - user = get_session_user(request) - if not user: - from starlette.responses import RedirectResponse + if not request.app.state.oidc_enabled: + from starlette.responses import RedirectResponse - return RedirectResponse(url=f"/auth/login?next=/{path}") - logger.debug( - "Admin route access: path=%s, user=%s", - path, - user.get("name"), - ) + return RedirectResponse(url="/") + user = get_session_user(request) + if not user: + from starlette.responses import RedirectResponse + + return RedirectResponse(url=f"/auth/login?next=/{path}") + logger.debug( + "Admin route access: path=%s, user=%s", + path, + user.get("name"), + ) templates_inst: Jinja2Templates = request.app.state.templates features = request.app.state.features diff --git a/src/meshcore_hub/web/static/js/spa/app.js b/src/meshcore_hub/web/static/js/spa/app.js index 4aa0f28..1d93414 100644 --- a/src/meshcore_hub/web/static/js/spa/app.js +++ b/src/meshcore_hub/web/static/js/spa/app.js @@ -88,7 +88,7 @@ if (features.pages !== false) { router.addRoute('/pages/:slug', pageHandler(pages.customPage)); } -// Admin routes (only register when OIDC disabled or user is admin) +// Admin routes (only register when OIDC enabled and user has admin role) if (hasRole('admin')) { router.addRoute('/admin', pageHandler(pages.adminIndex)); router.addRoute('/admin/', pageHandler(pages.adminIndex)); diff --git a/src/meshcore_hub/web/static/js/spa/components.js b/src/meshcore_hub/web/static/js/spa/components.js index e7f593f..2309bf8 100644 --- a/src/meshcore_hub/web/static/js/spa/components.js +++ b/src/meshcore_hub/web/static/js/spa/components.js @@ -33,7 +33,7 @@ export function getConfig() { */ export function hasRole(roleName) { const config = getConfig(); - if (!config.oidc_enabled) return true; + if (!config.oidc_enabled) return false; const actualRole = (config.role_names || {})[roleName] || roleName; return (config.roles || []).includes(actualRole); } diff --git a/tests/test_web/test_oidc.py b/tests/test_web/test_oidc.py index a4f23ed..baae4db 100644 --- a/tests/test_web/test_oidc.py +++ b/tests/test_web/test_oidc.py @@ -234,13 +234,11 @@ class TestBackwardCompatibility: assert config["oidc_enabled"] is False assert config["roles"] == [] - def test_admin_routes_serve_spa_shell_when_oidc_disabled( - self, client: TestClient - ) -> None: - """Test admin routes serve SPA shell when OIDC disabled (no redirect).""" - response = client.get("/admin/") - assert response.status_code == 200 - assert "window.__APP_CONFIG__" in response.text + def test_admin_routes_blocked_when_oidc_disabled(self, client: TestClient) -> None: + """Test admin routes redirect to home when OIDC disabled.""" + response = client.get("/admin/", follow_redirects=False) + assert response.status_code == 307 + assert response.headers["location"] == "/" class TestConfigInjection: