"use client"; import { useEffect, useState, type ReactNode } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useAuth } from "@/contexts/AuthContext"; import { useUserProfile } from "@/contexts/UserProfileContext"; import { needsMfaVerification } from "./MfaVerificationPopup"; type GateState = "idle" | "checking" | "required" | "verified"; const MFA_VERIFIED_AT_KEY = "mike:mfa-verified-at"; const MFA_VERIFIED_GRACE_MS = 60_000; export function MfaLoginGate({ children }: { children: ReactNode }) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const { user } = useAuth(); const { profile, loading } = useUserProfile(); const [gateState, setGateState] = useState("idle"); const isVerifyPage = pathname === "/verify-mfa"; useEffect(() => { if (!user || loading || !profile?.mfaOnLogin) { setGateState("idle"); return; } let cancelled = false; setGateState("checking"); async function checkLoginMfa() { try { if (hasRecentMfaVerification()) { if (!cancelled) setGateState("verified"); return; } const required = await needsMfaVerification(); if (cancelled) return; setGateState(required ? "required" : "verified"); } catch { if (!cancelled) setGateState("required"); } } void checkLoginMfa(); return () => { cancelled = true; }; }, [loading, profile?.mfaOnLogin, user]); useEffect(() => { if (!user || loading || !profile?.mfaOnLogin) return; if (gateState === "required" && !isVerifyPage) { if (hasRecentMfaVerification()) { setGateState("verified"); return; } const search = searchParams.toString(); const next = `${pathname}${search ? `?${search}` : ""}`; router.replace(`/verify-mfa?next=${encodeURIComponent(next)}`); } else if (gateState === "verified" && isVerifyPage) { const next = safeNextPath(searchParams.get("next")); router.replace(next); } }, [ gateState, isVerifyPage, loading, pathname, profile?.mfaOnLogin, router, searchParams, user, ]); if (user && loading) return ; if (user && profile?.mfaOnLogin) { if (gateState === "required" && isVerifyPage) { return <>{children}; } if (gateState === "verified" && isVerifyPage) { return ; } if (gateState === "verified") { return <>{children}; } if (gateState === "required" && !isVerifyPage) { return ; } return ; } return <>{children}; } function safeNextPath(value: string | null) { if (!value || !value.startsWith("/") || value.startsWith("//")) { return "/assistant"; } if (value.startsWith("/verify-mfa")) return "/assistant"; return value; } function FullScreenGateLoader() { return (
); } export function markMfaVerifiedForGate() { window.sessionStorage.setItem(MFA_VERIFIED_AT_KEY, String(Date.now())); } function hasRecentMfaVerification() { const raw = window.sessionStorage.getItem(MFA_VERIFIED_AT_KEY); const verifiedAt = raw ? Number.parseInt(raw, 10) : 0; return ( Number.isFinite(verifiedAt) && Date.now() - verifiedAt < MFA_VERIFIED_GRACE_MS ); }