Files
frontend/src/pages/SSOCallback.jsx

115 lines
4.6 KiB
JavaScript

/**
* Receptor del relay token del Hub.
* Maneja dos flujos:
* - SSO entre productos: Hub redirige a /auth/sso?relay=<uuid>
* - Login con Microsoft: Hub redirige al mismo endpoint tras OAuth
*/
import { useEffect, useState } from 'react';
const API_URL = import.meta.env.VITE_EFC_API_URL;
export default function SSOCallback() {
const [error, setError] = useState(null);
useEffect(() => {
const params = new URLSearchParams(window.location.search);
const relay = params.get('relay');
if (!relay) {
setError('No se recibió relay token. Acceso denegado.');
return;
}
(async () => {
try {
const res = await fetch(`${API_URL}/auth/sso/exchange/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ relay_token: relay }),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.detail || 'Error al iniciar sesión SSO');
}
const data = await res.json();
// Guardar tokens en localStorage para fetchWithAuth
if (data.access_token) {
localStorage.setItem('access', data.access_token);
}
if (data.refresh_token) {
localStorage.setItem('refresh', data.refresh_token);
}
// Sincronizar datos de usuario
const apiUrl = import.meta.env.VITE_EFC_API_URL || '';
try {
const [resUser, resPerms] = await Promise.all([
fetch(`${apiUrl}/user/users/me/`, { headers: { Authorization: `Bearer ${data.access_token}` }, credentials: 'include' }),
fetch(`${apiUrl}/rbac/my-permissions/`, { headers: { Authorization: `Bearer ${data.access_token}` }, credentials: 'include' }),
]);
if (resUser.ok) {
const user = await resUser.json();
if (user?.username) {
localStorage.setItem('username', user.username);
if (user.email) localStorage.setItem('user_email', user.email);
if (user.id) localStorage.setItem('user_id', String(user.id));
if (user.first_name) localStorage.setItem('user_first_name', user.first_name);
if (user.last_name) localStorage.setItem('user_last_name', user.last_name);
if (typeof user.is_importador !== 'undefined')
localStorage.setItem('user_is_importador', String(user.is_importador));
}
}
if (resPerms.ok) {
const permsData = await resPerms.json();
if (permsData?.permissions)
localStorage.setItem('user_permissions', JSON.stringify(permsData.permissions));
}
} catch (_) {}
window.dispatchEvent(new CustomEvent('authStateChanged'));
window.location.href = '/admin';
} catch (err) {
setError(err.message || 'Error al iniciar sesión vía workspace');
}
})();
}, []);
if (error) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full bg-white rounded-2xl shadow-lg p-8 text-center space-y-4">
<div className="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mx-auto">
<svg className="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 className="text-lg font-bold text-gray-900">Error de inicio de sesión</h2>
<p className="text-sm text-gray-600">{error}</p>
<a
href={`${import.meta.env.VITE_HUB_URL || 'http://localhost:3001'}/login`}
className="inline-block mt-2 text-sm font-medium text-blue-600 hover:underline"
>
Volver al login
</a>
</div>
</div>
);
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center space-y-3">
<svg className="w-8 h-8 text-blue-500 animate-spin mx-auto" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
<p className="text-sm text-gray-600 font-medium">Iniciando sesión</p>
</div>
</div>
);
}