import { useState, useEffect } from 'react'; import { login, setToken, getOidcConfig, oidcCallback } from '../api/dns'; const OIDC_STATE_KEY = 'sloth_oidc_state'; function getRedirectUri() { // Strip any existing query/hash — the redirect_uri must be the clean app URL return `${window.location.origin}${window.location.pathname}`; } export default function LoginPage({ onLogin }) { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); // OIDC state const [oidc, setOidc] = useState(null); // null = loading, false = disabled const [oidcLoading, setOidcLoading] = useState(false); // 1. Fetch OIDC config on mount useEffect(() => { getOidcConfig() .then(cfg => setOidc(cfg.enabled ? cfg : false)) .catch(() => setOidc(false)); }, []); // 2. Handle redirect back from Authentik (?code=...&state=...) useEffect(() => { const params = new URLSearchParams(window.location.search); const code = params.get('code'); const state = params.get('state'); if (!code) return; // Verify state to prevent CSRF const savedState = sessionStorage.getItem(OIDC_STATE_KEY); sessionStorage.removeItem(OIDC_STATE_KEY); if (state && savedState && state !== savedState) { setError('SSO state mismatch — please try again'); // Remove code from URL window.history.replaceState({}, '', window.location.pathname); return; } // Exchange code for JWT setOidcLoading(true); setError(''); const redirectUri = getRedirectUri(); // Clean URL before async work so a page refresh won't re-attempt window.history.replaceState({}, '', window.location.pathname); oidcCallback(code, redirectUri) .then(data => { setToken(data.token); onLogin(data.user); }) .catch(err => { setError(err.message); setOidcLoading(false); }); }, []); // eslint-disable-line // 3. Initiate Authentik redirect function handleSsoClick() { if (!oidc) return; const state = crypto.randomUUID(); sessionStorage.setItem(OIDC_STATE_KEY, state); const params = new URLSearchParams({ response_type: 'code', client_id: oidc.clientId, redirect_uri: getRedirectUri(), scope: oidc.scope, state, }); window.location.href = `${oidc.authorizationEndpoint}?${params}`; } // 4. Local login async function handleSubmit(e) { e.preventDefault(); setError(''); setLoading(true); try { const data = await login(username, password); setToken(data.token); onLogin(data.user); } catch (err) { setError(err.message); } finally { setLoading(false); } } // While exchanging an OIDC code, show a minimal loading screen if (oidcLoading) { return (
🦥

Sloth Manager

Signing in with Authentik…

); } return (
🦥

Sloth Manager

{/* SSO button — shown only when Authentik is configured */} {oidc && ( <>
or
)}
{error &&

{error}

}

By bobbantech {' · '} { e.preventDefault(); window.dispatchEvent(new CustomEvent('show-privacy')); }}>Privacy

); }