diff --git a/dashboard/backend/requirements.txt b/dashboard/backend/requirements.txt index 758f167..707d126 100644 --- a/dashboard/backend/requirements.txt +++ b/dashboard/backend/requirements.txt @@ -11,3 +11,4 @@ asyncssh==2.17.0 slowapi==0.1.9 webauthn==2.7.1 cryptography>=44.0.2 +aiosqlite diff --git a/dashboard/backend/routers/auth.py b/dashboard/backend/routers/auth.py index 467eeb5..faf777b 100644 --- a/dashboard/backend/routers/auth.py +++ b/dashboard/backend/routers/auth.py @@ -109,6 +109,30 @@ async def login(creds: LoginRequest, request: Request): "has_2fa": bool(totp_secret) } +@router.get("/proxy") +async def proxy_auth(request: Request): + """Auto-login when Authelia has already authenticated the user via forward-auth. + Caddy sets Remote-User header after successful Authelia 2FA verification.""" + remote_user = request.headers.get("Remote-User") + if not remote_user: + raise HTTPException(status_code=401, detail="No proxy auth header") + # Map the LDAP user to the dashboard admin user + # Only allow known users (the admin) + if remote_user != config.ADMIN_USER: + raise HTTPException(status_code=403, detail="User not authorized for dashboard") + access_token = auth.create_access_token( + data={"sub": remote_user}, + expires_delta=timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES), + ) + refresh_token = auth.create_refresh_token(data={"sub": remote_user}) + return { + "access_token": access_token, + "refresh_token": refresh_token, + "token_type": "bearer", + "user": remote_user, + "has_2fa": True, + } + @router.get("/me") async def read_users_me(current_user: str = Depends(auth.get_current_user)): totp_secret = auth.load_totp_secret() diff --git a/dashboard/docker-compose.yml b/dashboard/docker-compose.yml index b0e2c85..f128876 100644 --- a/dashboard/docker-compose.yml +++ b/dashboard/docker-compose.yml @@ -35,6 +35,7 @@ services: - VPS_SSH_HOST=158.101.140.85 - VPS_SSH_USER=jimmyg - SECRET_KEY=${SECRET_KEY} + - ADMIN_USER=jimmy - ADMIN_PASSWORD_HASH=${ADMIN_PASSWORD_HASH} - TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN:-} - TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-} diff --git a/dashboard/frontend/src/App.svelte b/dashboard/frontend/src/App.svelte index 4c263f1..239fc08 100644 --- a/dashboard/frontend/src/App.svelte +++ b/dashboard/frontend/src/App.svelte @@ -28,7 +28,22 @@ document.documentElement.classList.remove('dark'); } - // Auth check + // Try proxy auth first (Authelia forward-auth sets Remote-User header) + try { + const proxyRes = await fetch("/api/auth/proxy"); + if (proxyRes.ok) { + const data = await proxyRes.json(); + setToken(data.access_token); + setRefreshToken(data.refresh_token); + authorized = true; + loading = false; + return; + } + } catch (e) { + // Proxy auth not available, fall through to regular auth + } + + // Regular token auth check const token = getToken(); if (!token) { loading = false; diff --git a/dashboard/frontend/src/components/Sidebar.svelte b/dashboard/frontend/src/components/Sidebar.svelte index 5e338b0..6608ebc 100644 --- a/dashboard/frontend/src/components/Sidebar.svelte +++ b/dashboard/frontend/src/components/Sidebar.svelte @@ -32,6 +32,7 @@ { label: "Gitea Web", port: 3300, icon: "git", external: true }, { label: "Stirling PDF", port: 8030, icon: "pdf", external: true }, { label: "Vaultwarden", port: 8222, icon: "lock", external: true }, + { label: "User Management", remoteHref: "https://ldap.jimmygan.com:8443", icon: "users", external: true }, { label: "n8n", remoteHref: "https://n8n.jimmygan.com:8443", icon: "n8n", external: true }, { label: "Speedtest", port: 8080, icon: "speedtest", external: true }, ]; @@ -144,6 +145,8 @@ {:else if item.icon === "speedtest"} + {:else if item.icon === "users"} + {:else if item.icon === "n8n"} {/if} diff --git a/dashboard/frontend/src/routes/ChatSummary.svelte b/dashboard/frontend/src/routes/ChatSummary.svelte index 6f629d9..8b56956 100644 --- a/dashboard/frontend/src/routes/ChatSummary.svelte +++ b/dashboard/frontend/src/routes/ChatSummary.svelte @@ -55,6 +55,20 @@ triggering = false; } } + + function markdownToHtml(md) { + if (!md) return ""; + return md + .replace(/&/g, "&").replace(//g, ">") + .replace(/^### (.+)$/gm, '