import React, { useMemo, useState } from "react"; import { Box, TextField, Button, Typography } from "@mui/material"; export default function Login({ onLogin }) { // ----------------- Original state & logic ----------------- const [username, setUsername] = useState("admin"); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const [step, setStep] = useState("credentials"); // 'credentials' | 'mfa' const [pendingToken, setPendingToken] = useState(""); const [mfaStage, setMfaStage] = useState(null); const [mfaCode, setMfaCode] = useState(""); const [setupSecret, setSetupSecret] = useState(""); const [setupQr, setSetupQr] = useState(""); const [setupUri, setSetupUri] = useState(""); const formattedSecret = useMemo(() => { if (!setupSecret) return ""; return setupSecret.replace(/(.{4})/g, "$1 ").trim(); }, [setupSecret]); const sha512 = async (text) => { try { if (window.crypto && window.crypto.subtle && window.isSecureContext) { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await window.crypto.subtle.digest("SHA-512", data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); } } catch (_) { // fall through to return null } // Not a secure context or subtle crypto unavailable return null; }; const resetMfaState = () => { setStep("credentials"); setPendingToken(""); setMfaStage(null); setMfaCode(""); setSetupSecret(""); setSetupQr(""); setSetupUri(""); }; const handleCredentialsSubmit = async (e) => { e.preventDefault(); setIsSubmitting(true); setError(""); try { const hash = await sha512(password); const body = hash ? { username, password_sha512: hash } : { username, password }; const resp = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(body) }); const data = await resp.json(); if (!resp.ok) { throw new Error(data?.error || "Invalid username or password"); } if (data?.status === "mfa_required") { setPendingToken(data.pending_token || ""); setMfaStage(data.stage || "verify"); setStep("mfa"); setMfaCode(""); setSetupSecret(data.secret || ""); setSetupQr(data.qr_image || ""); setSetupUri(data.otpauth_url || ""); setError(""); setPassword(""); return; } if (data?.token) { try { document.cookie = `borealis_auth=${data.token}; Path=/; SameSite=Lax`; } catch (_) {} } onLogin({ username: data.username, role: data.role }); } catch (err) { const msg = err?.message || "Unable to log in"; setError(msg); resetMfaState(); } finally { setIsSubmitting(false); } }; const handleMfaSubmit = async (e) => { e.preventDefault(); if (!pendingToken) { setError("Your MFA session expired. Please log in again."); resetMfaState(); return; } if (!mfaCode || mfaCode.trim().length < 6) { setError("Enter the 6-digit code from your authenticator app."); return; } setIsSubmitting(true); setError(""); try { const resp = await fetch("/api/auth/mfa/verify", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ pending_token: pendingToken, code: mfaCode }) }); const data = await resp.json(); if (!resp.ok) { const errKey = data?.error; if (errKey === "expired" || errKey === "invalid_session" || errKey === "mfa_pending") { setError("Your MFA session expired. Please log in again."); resetMfaState(); return; } const msgMap = { invalid_code: "Incorrect code. Please try again.", mfa_not_configured: "MFA is not configured for this account." }; setError(msgMap[errKey] || data?.error || "Failed to verify code."); return; } if (data?.token) { try { document.cookie = `borealis_auth=${data.token}; Path=/; SameSite=Lax`; } catch (_) {} } setError(""); onLogin({ username: data.username, role: data.role }); } catch (err) { setError("Failed to verify code."); } finally { setIsSubmitting(false); } }; const handleBackToLogin = () => { resetMfaState(); setPassword(""); setError(""); }; const onCodeChange = (event) => { const raw = event.target.value || ""; const digits = raw.replace(/\D/g, "").slice(0, 6); setMfaCode(digits); }; const formTitle = step === "mfa" ? "Multi-Factor Authentication" : "Borealis - Automation Platform"; // ----------------- UI helpers ----------------- const FieldLabel = ({ children }) => ( {children} ); return ( <> {/* Component-scoped styles for MagicUI effects */}
{/* Background layers */}
{/* Card with ShineBorder */}
Borealis Automation
{step === "mfa" ? "Multi‑Factor Authentication" : ""} {step === "credentials" ? ( <> Username setUsername(e.target.value)} InputProps={{ sx: { borderRadius: 2 } }} /> Password setPassword(e.target.value)} InputProps={{ sx: { borderRadius: 2 } }} /> {error &&
{error}
} ) : ( <> {mfaStage === "setup" ? ( <> Scan the QR code with your authenticator app, then enter the 6‑digit code to complete setup for {username}. {setupQr ? ( MFA enrollment QR code ) : null} {formattedSecret ? (
Manual code
{formattedSecret}
) : null} {setupUri ? ( {setupUri} ) : null} ) : ( Enter the 6‑digit code from your authenticator app for {username}. )} One‑time code { const raw = e.target.value || ""; const digits = raw.replace(/\D/g, "").slice(0, 6); setMfaCode(digits); }} disabled={isSubmitting} inputProps={{ inputMode: "numeric", pattern: "[0-9]*", maxLength: 6, style: { letterSpacing: "0.4rem", textAlign: "center", fontSize: "1.15rem" } }} autoComplete="one-time-code" placeholder="• • • • • •" /> {error &&
{error}
} )}
); }