import React, { useState, useRef, useEffect, type FormEvent } from "react"; import Button from "@mui/joy/Button"; import { toast } from "react-toastify"; import { API_URL } from "@/config"; function getCookie(name: string): string | null { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return decodeURIComponent(parts.pop()!.split(';').shift()!); return null; } function clearCookie(name: string): void { document.cookie = `${name}=; Max-Age=0; path=/;`; } interface LoginPageProps { loggedIn?: boolean; accessDenied?: boolean; requiredRoles?: string[] | string; } export default function LoginPage({ loggedIn = false, accessDenied = false, requiredRoles = [] }: LoginPageProps): React.ReactElement { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const passwordRef = useRef(null); useEffect(() => { if (passwordRef.current && password === "") { passwordRef.current.value = ""; } }, []); async function handleSubmit(e: FormEvent): Promise { e.preventDefault(); setLoading(true); try { if (!username || !password) { setLoading(false); if (!toast.isActive("login-missing-data-toast")) { toast.error("Username and password are required", { toastId: "login-missing-data-toast", }); } return; } const formData = new URLSearchParams({ username, password, grant_type: "password", scope: "", client_id: "", client_secret: "", }); const resp = await fetch(`${API_URL}/auth/login`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, credentials: "include", body: formData.toString(), }); if (resp.status === 401) { toast.error("Invalid username or password", { toastId: "login-error-invalid-toast", }); setLoading(false); return; } if (!resp.ok) { const data = await resp.json().catch(() => ({})); toast.error(data.detail ? `Login failed: ${data.detail}` : "Login failed", { toastId: "login-error-failed-toast", }); setLoading(false); return; } const data = await resp.json(); if (data.access_token) { toast.success("Login successful!", { toastId: "login-success-toast", }); // Check for returnUrl in query params const urlParams = new URLSearchParams(window.location.search); const returnUrl = urlParams.get('returnUrl'); // Validate returnUrl is a relative path (security: prevent open redirect) const returnTo = (returnUrl && returnUrl.startsWith('/')) ? returnUrl : '/'; window.location.href = returnTo; } else { toast.error("Login failed: no access token received", { toastId: "login-error-no-token-toast", }); setLoading(false); } } catch (error) { toast.error("Network error during login", { toastId: "login-error-network-toast", }); console.error("Login error:", error); setLoading(false); } } if (loggedIn) { const rolesList = Array.isArray(requiredRoles) ? requiredRoles : (requiredRoles ? requiredRoles.split(',') : []); return (
Logo

Access Denied

You don't have permission to access this resource.

{rolesList.length > 0 && (

Required role{rolesList.length > 1 ? 's' : ''}:

{rolesList.map((role, i) => ( {role} ))}
)}

If you believe this is an error, scream at codey.

); } return (
Logo

Log In

Sign in to continue

setUsername(e.target.value)} required disabled={loading} className="w-full border border-neutral-200 dark:border-neutral-700 rounded-xl px-4 py-3 bg-white dark:bg-neutral-900/50 text-neutral-900 dark:text-white focus:border-blue-500 dark:focus:border-blue-400 focus:ring-2 focus:ring-blue-500/20 transition-all outline-none" placeholder="Enter your username" />
setPassword(e.target.value)} required disabled={loading} className="w-full border border-neutral-200 dark:border-neutral-700 rounded-xl px-4 py-3 bg-white dark:bg-neutral-900/50 text-neutral-900 dark:text-white focus:border-blue-500 dark:focus:border-blue-400 focus:ring-2 focus:ring-blue-500/20 transition-all outline-none" placeholder="Enter your password" />
); }