import { timestampDate } from "@bufbuild/protobuf/wkt"; import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { setAccessToken } from "@/auth-state"; import Spinner from "@/components/Spinner"; import { authServiceClient } from "@/connect"; import { useAuth } from "@/contexts/AuthContext"; import { absolutifyLink } from "@/helpers/utils"; import useNavigateTo from "@/hooks/useNavigateTo"; import { handleError } from "@/lib/error"; import { validateOAuthState } from "@/utils/oauth"; interface State { loading: boolean; errorMessage: string; } const AuthCallback = () => { const navigateTo = useNavigateTo(); const { initialize } = useAuth(); const [searchParams] = useSearchParams(); const [state, setState] = useState({ loading: true, errorMessage: "", }); useEffect(() => { // Check for OAuth error response first (e.g., user denied access) const error = searchParams.get("error"); const errorDescription = searchParams.get("error_description"); const errorUri = searchParams.get("error_uri"); if (error) { // OAuth provider returned an error let errorMessage = `OAuth error: ${error}`; if (errorDescription) { errorMessage += `\n${decodeURIComponent(errorDescription)}`; } if (errorUri) { errorMessage += `\nMore info: ${errorUri}`; } setState({ loading: false, errorMessage, }); return; } const code = searchParams.get("code"); const state = searchParams.get("state"); if (!code || !state) { setState({ loading: false, errorMessage: "Failed to authorize. Missing authorization code or state parameter.", }); return; } // Validate OAuth state (CSRF protection) and retrieve PKCE code_verifier const validatedState = validateOAuthState(state); if (!validatedState) { setState({ loading: false, errorMessage: "Failed to authorize. Invalid or expired state parameter. This may indicate a CSRF attack attempt.", }); return; } const { identityProviderId, returnUrl, codeVerifier } = validatedState; const redirectUri = absolutifyLink("/auth/callback"); (async () => { try { const response = await authServiceClient.signIn({ credentials: { case: "ssoCredentials", value: { idpId: identityProviderId, code, redirectUri, codeVerifier: codeVerifier || "", // Pass PKCE code_verifier for token exchange }, }, }); // Store access token from login response if (response.accessToken) { setAccessToken(response.accessToken, response.accessTokenExpiresAt ? timestampDate(response.accessTokenExpiresAt) : undefined); } setState({ loading: false, errorMessage: "", }); await initialize(); // Redirect to return URL if specified, otherwise home navigateTo(returnUrl || "/"); } catch (error: unknown) { handleError(error, () => {}, { fallbackMessage: "Failed to authenticate.", onError: (err) => { const message = err instanceof Error ? err.message : "Failed to authenticate."; setState({ loading: false, errorMessage: message, }); }, }); } })(); }, [searchParams, navigateTo]); return (
{state.loading ? ( ) : (
{state.errorMessage}
)}
); }; export default AuthCallback;