6f31d6043c
- New branch for mobile2.falah-os.com with Casdoor OIDC - Auth page with 'Sign in with Casdoor' button - OIDC callback: exchanges code via server-side API route - env vars configured on Netlify - DNS auto-configured via Netlify (falah-os.com zone)
152 lines
4.8 KiB
TypeScript
152 lines
4.8 KiB
TypeScript
"use client";
|
|
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
|
|
function AuthCallbackContent() {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const [status, setStatus] = useState<"processing" | "success" | "error">(
|
|
"processing"
|
|
);
|
|
const [errorMsg, setErrorMsg] = useState("");
|
|
|
|
useEffect(() => {
|
|
const token = searchParams.get("token");
|
|
const code = searchParams.get("code");
|
|
const error = searchParams.get("error");
|
|
|
|
if (error) {
|
|
setStatus("error");
|
|
setErrorMsg(decodeURIComponent(error));
|
|
return;
|
|
}
|
|
|
|
// --- Casdoor OIDC flow: exchange authorization code for tokens ---
|
|
if (code) {
|
|
fetch(`/api/auth/casdoor-callback?code=${encodeURIComponent(code)}`)
|
|
.then((res) => res.json())
|
|
.then((data) => {
|
|
if (data.error) {
|
|
setStatus("error");
|
|
setErrorMsg(data.error);
|
|
return;
|
|
}
|
|
|
|
// Store the access token
|
|
localStorage.setItem("flh_token", data.access_token);
|
|
if (data.id_token) {
|
|
localStorage.setItem("flh_id_token", data.id_token);
|
|
}
|
|
if (data.refresh_token) {
|
|
localStorage.setItem("flh_refresh_token", data.refresh_token);
|
|
}
|
|
if (data.user) {
|
|
localStorage.setItem("flh_user", JSON.stringify(data.user));
|
|
}
|
|
|
|
// Dispatch storage event so AuthContext picks it up
|
|
window.dispatchEvent(
|
|
new StorageEvent("storage", {
|
|
key: "flh_token",
|
|
newValue: data.access_token,
|
|
oldValue: null,
|
|
storageArea: localStorage,
|
|
})
|
|
);
|
|
|
|
setStatus("success");
|
|
|
|
// Redirect to home
|
|
setTimeout(() => router.push("/"), 500);
|
|
})
|
|
.catch((err) => {
|
|
console.error("[oidc-callback] Token exchange error:", err);
|
|
setStatus("error");
|
|
setErrorMsg("Failed to complete authentication. Please try again.");
|
|
});
|
|
return;
|
|
}
|
|
|
|
// --- Legacy UmmahID flow: token passed directly in URL ---
|
|
if (!token) {
|
|
setStatus("error");
|
|
setErrorMsg("No authentication code received.");
|
|
return;
|
|
}
|
|
|
|
// Store token in localStorage
|
|
localStorage.setItem("flh_token", token);
|
|
|
|
window.dispatchEvent(
|
|
new StorageEvent("storage", {
|
|
key: "flh_token",
|
|
newValue: token,
|
|
oldValue: null,
|
|
storageArea: localStorage,
|
|
})
|
|
);
|
|
|
|
setStatus("success");
|
|
|
|
// If this is a popup window, close it after a brief delay
|
|
if (window.opener) {
|
|
setTimeout(() => window.close(), 500);
|
|
} else {
|
|
setTimeout(() => router.push("/"), 500);
|
|
}
|
|
}, [searchParams, router]);
|
|
|
|
return (
|
|
<div className="min-h-dvh bg-[#0a0a0f] flex items-center justify-center">
|
|
<div className="text-center">
|
|
{status === "processing" && (
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="w-10 h-10 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
|
<p className="text-sm text-gray-400">Signing you in...</p>
|
|
</div>
|
|
)}
|
|
{status === "success" && (
|
|
<div className="flex flex-col items-center gap-3">
|
|
<div className="w-12 h-12 rounded-full bg-green-900/30 border border-green-700/40 flex items-center justify-center">
|
|
<span className="text-green-400 text-xl">✓</span>
|
|
</div>
|
|
<p className="text-sm text-green-400">Signed in successfully!</p>
|
|
</div>
|
|
)}
|
|
{status === "error" && (
|
|
<div className="flex flex-col items-center gap-3">
|
|
<div className="w-12 h-12 rounded-full bg-red-900/30 border border-red-700/40 flex items-center justify-center">
|
|
<span className="text-red-400 text-xl">✕</span>
|
|
</div>
|
|
<p className="text-sm text-red-400">{errorMsg}</p>
|
|
<button
|
|
onClick={() => router.push("/auth")}
|
|
className="mt-4 px-6 py-2 rounded-xl bg-[#D4AF37] text-[#0a0a0f] text-sm font-medium"
|
|
>
|
|
Back to Login
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function AuthCallbackPage() {
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<div className="min-h-dvh bg-[#0a0a0f] flex items-center justify-center">
|
|
<div className="flex flex-col items-center gap-4">
|
|
<div className="w-10 h-10 rounded-full border-2 border-[#D4AF37]/30 border-t-[#D4AF37] animate-spin" />
|
|
<p className="text-sm text-gray-400">Signing you in...</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
<AuthCallbackContent />
|
|
</Suspense>
|
|
);
|
|
}
|