/** * Login Page - Standalone login page for the admin * * This component is NOT wrapped in the admin Shell. * It's a standalone page for authentication. * * Supports: * - Passkey authentication (always available) * - Pluggable auth providers (AT Protocol, GitHub, Google, etc.) when configured * - Magic link (email) when configured * * When external auth (e.g., Cloudflare Access) is configured, this page * redirects to the admin dashboard since authentication is handled externally. */ import { Button, Input, Loader, Select } from "@cloudflare/kumo"; import { Trans, useLingui } from "@lingui/react/macro"; import { useQuery } from "@tanstack/react-query"; import { Link } from "@tanstack/react-router"; import * as React from "react"; import { apiFetch, fetchAuthMode } from "../lib/api"; import { useAuthProviderList } from "../lib/auth-provider-context"; import { sanitizeRedirectUrl } from "../lib/url"; import { SUPPORTED_LOCALES } from "../locales/index.js"; import { useLocale } from "../locales/useLocale.js"; import { PasskeyLogin } from "./auth/PasskeyLogin"; import { BrandLogo } from "./Logo.js"; // ============================================================================ // Types // ============================================================================ interface LoginPageProps { /** URL to redirect to after successful login */ redirectUrl?: string; } type LoginMethod = "passkey" | "magic-link"; // ============================================================================ // Components // ============================================================================ interface MagicLinkFormProps { onBack: () => void; } function MagicLinkForm({ onBack }: MagicLinkFormProps) { const { t } = useLingui(); const [email, setEmail] = React.useState(""); const [isLoading, setIsLoading] = React.useState(false); const [error, setError] = React.useState(null); const [sent, setSent] = React.useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setIsLoading(true); try { const response = await apiFetch("/_emdash/api/auth/magic-link/send", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email.trim().toLowerCase() }), }); if (!response.ok) { const body: { error?: { message?: string } } = await response.json().catch(() => ({})); throw new Error(body?.error?.message || t`Failed to send magic link`); } setSent(true); } catch (err) { setError(err instanceof Error ? err.message : t`Failed to send magic link`); } finally { setIsLoading(false); } }; if (sent) { return (

{t`Check your email`}

If an account exists for{" "} {email}, we've sent a sign-in link.

{t`Click the link in the email to sign in.`}

{t`The link will expire in 15 minutes.`}

); } return (
setEmail(e.target.value)} placeholder="you@example.com" className={error ? "border-kumo-danger" : ""} disabled={isLoading} autoComplete="email" autoFocus required /> {error && (
{error}
)}
); } // ============================================================================ // Main Component // ============================================================================ export function LoginPage({ redirectUrl = "/_emdash/admin" }: LoginPageProps) { // Defense-in-depth: sanitize even if the caller already validated const safeRedirectUrl = sanitizeRedirectUrl(redirectUrl); const { t } = useLingui(); const { locale, setLocale } = useLocale(); const [method, setMethod] = React.useState("passkey"); const [urlError, setUrlError] = React.useState(null); const [activeProvider, setActiveProvider] = React.useState(null); // Auth provider components from virtual module (via context) const authProviderList = useAuthProviderList(); // Fetch auth mode from public endpoint (works without authentication) const { data: authInfo, isLoading: authModeLoading } = useQuery({ queryKey: ["authMode"], queryFn: fetchAuthMode, }); // Redirect to admin when using external auth (authentication is handled externally) React.useEffect(() => { if (authInfo?.authMode && authInfo.authMode !== "passkey") { window.location.href = safeRedirectUrl; } }, [authInfo, safeRedirectUrl]); // Check for error in URL (from OAuth/provider redirect) React.useEffect(() => { const params = new URLSearchParams(window.location.search); const error = params.get("error"); const message = params.get("message"); if (error) { setUrlError(message || t`Authentication error: ${error}`); // Clean up URL window.history.replaceState({}, "", window.location.pathname); } }, []); const handleSuccess = () => { // Redirect after successful login window.location.href = safeRedirectUrl; }; // All providers with a LoginButton show in the button grid const buttonProviders = authProviderList.filter((p) => p.LoginButton); // Show loading state while checking auth mode if (authModeLoading || (authInfo?.authMode && authInfo.authMode !== "passkey")) { return (
); } return (
{/* Header */}

{method === "magic-link" ? t`Sign in with email` : activeProvider ? t`Sign in with ${authProviderList.find((p) => p.id === activeProvider)?.label ?? activeProvider}` : t`Sign in to your site`}

{/* Error from URL (provider failure) */} {urlError && (
{urlError}
)} {/* Login Card */}
{method === "passkey" && !activeProvider && (
{/* Passkey Login */} {/* Divider */}
{t`Or continue with`}
{/* Auth provider buttons */} {buttonProviders.length > 0 && (
{buttonProviders.map((provider) => { const Btn = provider.LoginButton!; const hasForm = !!provider.LoginForm; const selectProvider = () => setActiveProvider(provider.id); return (
); })}
)} {/* Magic Link Option */}
)} {/* Provider form (full card replacement, like magic link) */} {method === "passkey" && activeProvider && (() => { const provider = authProviderList.find((p) => p.id === activeProvider); if (!provider?.LoginForm) return null; const Form = provider.LoginForm; return (
); })()} {method === "magic-link" && setMethod("passkey")} />}
{/* Help text */}

{method === "magic-link" ? t`We'll send you a link to sign in without a password.` : activeProvider ? t`Enter your handle to sign in.` : t`Use your registered passkey to sign in securely.`}

{/* Signup link — only shown when self-signup is enabled */} {authInfo?.signupEnabled && (

Don't have an account?{" "} Sign up

)} {/* Language selector — only shown when multiple locales are available */} {SUPPORTED_LOCALES.length > 1 && (