Readur/frontend/src/components/Auth/OidcCallback.tsx

149 lines
5.4 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Box, CircularProgress, Typography, Alert, Container } from '@mui/material';
import { useAuth } from '../../contexts/AuthContext';
import { api, ErrorHelper, ErrorCodes } from '../../services/api';
const OidcCallback: React.FC = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { login } = useAuth();
const [error, setError] = useState<string>('');
const [processing, setProcessing] = useState<boolean>(true);
useEffect(() => {
const handleCallback = async () => {
try {
const code = searchParams.get('code');
const error = searchParams.get('error');
const state = searchParams.get('state');
if (error) {
setError(`Authentication failed: ${error}`);
setProcessing(false);
return;
}
if (!code) {
setError('No authorization code received');
setProcessing(false);
return;
}
// Call the backend OIDC callback endpoint
const response = await api.get(`/auth/oidc/callback?code=${code}&state=${state || ''}`);
if (response.data && response.data.token) {
// Store the token and user data
localStorage.setItem('token', response.data.token);
api.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`;
// Redirect to dashboard - the auth context will pick up the token on next page load
window.location.href = '/dashboard';
} else {
setError('Invalid response from authentication server');
setProcessing(false);
}
} catch (err: any) {
console.error('OIDC callback error:', err);
const errorInfo = ErrorHelper.formatErrorForDisplay(err, true);
// Handle specific OIDC callback errors
if (ErrorHelper.isErrorCode(err, ErrorCodes.USER_OIDC_AUTH_FAILED)) {
setError('OIDC authentication failed. Please try logging in again or contact your administrator.');
} else if (ErrorHelper.isErrorCode(err, ErrorCodes.USER_AUTH_PROVIDER_NOT_CONFIGURED)) {
setError('OIDC is not configured on this server. Please use username/password login.');
} else if (ErrorHelper.isErrorCode(err, ErrorCodes.USER_INVALID_CREDENTIALS)) {
setError('Authentication failed. Your OIDC credentials may be invalid or expired.');
} else if (ErrorHelper.isErrorCode(err, ErrorCodes.USER_ACCOUNT_DISABLED)) {
setError('Your account has been disabled. Please contact an administrator for assistance.');
} else if (ErrorHelper.isErrorCode(err, ErrorCodes.USER_SESSION_EXPIRED) ||
ErrorHelper.isErrorCode(err, ErrorCodes.USER_TOKEN_EXPIRED)) {
setError('Authentication session expired. Please try logging in again.');
} else if (errorInfo.category === 'network') {
setError('Network error during authentication. Please check your connection and try again.');
} else if (errorInfo.category === 'server') {
setError('Server error during authentication. Please try again later or contact support.');
} else {
setError(errorInfo.message || 'Failed to complete authentication. Please try again.');
}
setProcessing(false);
}
};
handleCallback();
}, [searchParams, navigate, login]);
const handleReturnToLogin = () => {
navigate('/login');
};
return (
<Box
sx={{
minHeight: '100vh',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
p: 2,
}}
>
<Container maxWidth="sm">
<Box
sx={{
backgroundColor: 'rgba(255, 255, 255, 0.95)',
borderRadius: 4,
p: 4,
textAlign: 'center',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
}}
>
{processing ? (
<>
<CircularProgress size={60} sx={{ mb: 3, color: 'primary.main' }} />
<Typography variant="h5" sx={{ mb: 2, fontWeight: 600 }}>
Completing Authentication
</Typography>
<Typography variant="body1" color="text.secondary">
Please wait while we process your authentication...
</Typography>
</>
) : (
<>
<Alert
severity="error"
sx={{ mb: 3, textAlign: 'left' }}
action={
<Box
component="button"
onClick={handleReturnToLogin}
sx={{
background: 'none',
border: 'none',
color: 'primary.main',
cursor: 'pointer',
textDecoration: 'underline',
fontSize: '0.875rem',
}}
>
Return to Login
</Box>
}
>
<Typography variant="h6" sx={{ mb: 1 }}>
Authentication Error
</Typography>
{error}
</Alert>
</>
)}
</Box>
</Container>
</Box>
);
};
export default OidcCallback;