257 lines
8.5 KiB
TypeScript
257 lines
8.5 KiB
TypeScript
"use client";
|
|
|
|
import { ArrowLeft, CheckCircle, Loader2, XCircle } from "lucide-react";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { Suspense, useEffect, useState } from "react";
|
|
import AnimatedProcessingIcon from "@/components/icons/animated-processing-icon";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { useAuth } from "@/contexts/auth-context";
|
|
|
|
function AuthCallbackContent() {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const { refreshAuth } = useAuth();
|
|
const [status, setStatus] = useState<"processing" | "success" | "error">(
|
|
"processing",
|
|
);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [purpose, setPurpose] = useState<string>("app_auth");
|
|
|
|
useEffect(() => {
|
|
const code = searchParams.get("code");
|
|
const callbackKey = `callback_processed_${code}`;
|
|
|
|
// Prevent double execution across component remounts
|
|
if (sessionStorage.getItem(callbackKey)) {
|
|
return;
|
|
}
|
|
sessionStorage.setItem(callbackKey, "true");
|
|
|
|
const handleCallback = async () => {
|
|
try {
|
|
// Get parameters from URL
|
|
const state = searchParams.get("state");
|
|
const errorParam = searchParams.get("error");
|
|
|
|
// Get stored auth info
|
|
const connectorId = localStorage.getItem("connecting_connector_id");
|
|
const storedConnectorType = localStorage.getItem(
|
|
"connecting_connector_type",
|
|
);
|
|
const authPurpose = localStorage.getItem("auth_purpose");
|
|
|
|
// Determine purpose - default to app_auth for login, data_source for connectors
|
|
const detectedPurpose =
|
|
authPurpose ||
|
|
(storedConnectorType?.includes("drive") ? "data_source" : "app_auth");
|
|
setPurpose(detectedPurpose);
|
|
|
|
// Debug logging
|
|
console.log("OAuth Callback Debug:", {
|
|
urlParams: { code: !!code, state: !!state, error: errorParam },
|
|
localStorage: { connectorId, storedConnectorType, authPurpose },
|
|
detectedPurpose,
|
|
fullUrl: window.location.href,
|
|
});
|
|
|
|
// Use state parameter as connection_id if localStorage is missing
|
|
const finalConnectorId = connectorId || state;
|
|
|
|
if (errorParam) {
|
|
throw new Error(`OAuth error: ${errorParam}`);
|
|
}
|
|
|
|
if (!code || !state || !finalConnectorId) {
|
|
console.error("Missing OAuth callback parameters:", {
|
|
code: !!code,
|
|
state: !!state,
|
|
finalConnectorId: !!finalConnectorId,
|
|
});
|
|
throw new Error("Missing required parameters for OAuth callback");
|
|
}
|
|
|
|
// Send callback data to backend
|
|
const response = await fetch("/api/auth/callback", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
connection_id: finalConnectorId,
|
|
authorization_code: code,
|
|
state: state,
|
|
}),
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok) {
|
|
setStatus("success");
|
|
|
|
if (result.purpose === "app_auth" || detectedPurpose === "app_auth") {
|
|
// App authentication - refresh auth context and redirect to home/original page
|
|
await refreshAuth();
|
|
|
|
// Get redirect URL from login page
|
|
const redirectTo = searchParams.get("redirect") || "/chat";
|
|
|
|
// Clean up localStorage
|
|
localStorage.removeItem("connecting_connector_id");
|
|
localStorage.removeItem("connecting_connector_type");
|
|
localStorage.removeItem("auth_purpose");
|
|
|
|
// Redirect to the original page or home
|
|
setTimeout(() => {
|
|
router.push(redirectTo);
|
|
}, 2000);
|
|
} else {
|
|
// Connector authentication - redirect to connectors page
|
|
|
|
// Clean up localStorage
|
|
localStorage.removeItem("connecting_connector_id");
|
|
localStorage.removeItem("connecting_connector_type");
|
|
localStorage.removeItem("auth_purpose");
|
|
|
|
// Redirect to settings page with success indicator
|
|
setTimeout(() => {
|
|
router.push("/settings?oauth_success=true");
|
|
}, 2000);
|
|
}
|
|
} else {
|
|
throw new Error(result.error || "Authentication failed");
|
|
}
|
|
} catch (err) {
|
|
console.error("OAuth callback error:", err);
|
|
setError(err instanceof Error ? err.message : "Unknown error occurred");
|
|
setStatus("error");
|
|
|
|
// Clean up localStorage on error too
|
|
localStorage.removeItem("connecting_connector_id");
|
|
localStorage.removeItem("connecting_connector_type");
|
|
localStorage.removeItem("auth_purpose");
|
|
}
|
|
};
|
|
|
|
handleCallback();
|
|
}, [searchParams, router, refreshAuth]);
|
|
|
|
// Dynamic UI content based on purpose
|
|
const isAppAuth = purpose === "app_auth";
|
|
|
|
const getTitle = () => {
|
|
if (status === "processing") {
|
|
return isAppAuth ? "Signing you in..." : "Connecting...";
|
|
}
|
|
if (status === "success") {
|
|
return isAppAuth ? "Welcome to OpenRAG!" : "Connection Successful!";
|
|
}
|
|
if (status === "error") {
|
|
return isAppAuth ? "Sign In Failed" : "Connection Failed";
|
|
}
|
|
};
|
|
|
|
const getDescription = () => {
|
|
if (status === "processing") {
|
|
return isAppAuth
|
|
? "Please wait while we complete your sign in..."
|
|
: "Please wait while we complete the connection...";
|
|
}
|
|
if (status === "success") {
|
|
return "You will be redirected shortly.";
|
|
}
|
|
if (status === "error") {
|
|
return isAppAuth
|
|
? "There was an issue signing you in."
|
|
: "There was an issue with the connection.";
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-card rounded-lg m-4">
|
|
<Card className="w-full max-w-md bg-card rounded-lg m-4">
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="flex items-center justify-center gap-2">
|
|
{status === "processing" && (
|
|
<>
|
|
<AnimatedProcessingIcon className="h-5 w-5 text-current" />
|
|
{getTitle()}
|
|
</>
|
|
)}
|
|
{status === "success" && (
|
|
<>
|
|
<CheckCircle className="h-5 w-5 text-green-500" />
|
|
{getTitle()}
|
|
</>
|
|
)}
|
|
{status === "error" && (
|
|
<>
|
|
<XCircle className="h-5 w-5 text-red-500" />
|
|
{getTitle()}
|
|
</>
|
|
)}
|
|
</CardTitle>
|
|
<CardDescription>{getDescription()}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
{status === "error" && (
|
|
<div className="space-y-4">
|
|
<div className="p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
|
|
<p className="text-sm text-red-600">{error}</p>
|
|
</div>
|
|
<Button
|
|
onClick={() => router.push(isAppAuth ? "/login" : "/settings")}
|
|
variant="outline"
|
|
className="w-full"
|
|
>
|
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
{isAppAuth ? "Back to Login" : "Back to Settings"}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{status === "success" && (
|
|
<div className="text-center">
|
|
<div className="p-3 bg-green-500/10 border border-green-500/20 rounded-lg">
|
|
<p className="text-sm text-green-600">
|
|
{isAppAuth
|
|
? "Redirecting you to the app..."
|
|
: "Redirecting to settings..."}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function AuthCallbackPage() {
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
<Card className="w-full max-w-md">
|
|
<CardHeader className="text-center">
|
|
<CardTitle className="flex items-center justify-center gap-2">
|
|
<Loader2 className="h-5 w-5 animate-spin" />
|
|
Loading...
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Please wait while we process your request...
|
|
</CardDescription>
|
|
</CardHeader>
|
|
</Card>
|
|
</div>
|
|
}
|
|
>
|
|
<AuthCallbackContent />
|
|
</Suspense>
|
|
);
|
|
}
|