/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ import { useEffect, useState } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { Loader2 } from 'lucide-react'; import { useAuthStore } from '@/stores/authStore'; import { getBasePath } from '@/lib/basePath'; import { exchangeCode, getStoredOAuthData, clearStoredOAuthData, getOAuthRedirectUri } from '@/services/auth/oauth'; export default function OAuthCallback() { const { t } = useTranslation(); const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [error, setError] = useState(null); useEffect(() => { async function handleCallback() { try { const code = searchParams.get('code'); const stateParam = searchParams.get('state'); const errorParam = searchParams.get('error'); const errorDescription = searchParams.get('error_description'); if (errorParam) { throw new Error(errorDescription || errorParam); } if (!code || !stateParam) { throw new Error(t('oauth.missingParams', 'Missing authorization code or state parameter')); } const { codeVerifier, tokenEndpoint, state, returnUrl, endSessionEndpoint } = getStoredOAuthData(); if (!state || state !== stateParam) { throw new Error(t('oauth.stateMismatch', 'State parameter mismatch. Please try logging in again.')); } if (!codeVerifier || !tokenEndpoint) { throw new Error(t('oauth.missingData', 'Missing OAuth session data. Please try logging in again.')); } const redirectUri = getOAuthRedirectUri(); const tokenResponse = await exchangeCode(code, codeVerifier, tokenEndpoint, redirectUri); useAuthStore .getState() .setTokens( tokenResponse.access_token, tokenResponse.refresh_token, tokenResponse.expires_in, tokenEndpoint, endSessionEndpoint, ); clearStoredOAuthData(); const basePath = getBasePath(); const stripBase = (p: string) => (basePath && p.startsWith(basePath) ? p.slice(basePath.length) || '/' : p); const candidate = returnUrl ? stripBase(returnUrl) : '/'; const isAuthPath = candidate === '/login' || candidate.startsWith('/login?') || candidate === '/oauth/callback' || candidate.startsWith('/oauth/callback?'); const destination = isAuthPath ? '/' : candidate; navigate(destination, { replace: true }); } catch (err) { clearStoredOAuthData(); setError(err instanceof Error ? err.message : t('oauth.error', 'Authentication failed')); } } handleCallback(); }, [navigate, searchParams, t]); if (error) { return (

{error}

{t('oauth.backToLogin', 'Back to login')}
); } return (

{t('oauth.processing', 'Completing sign in...')}

); }