140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
/**
|
|
* Servicio de Single Sign-On (SSO) con proveedores externos
|
|
*/
|
|
import { browser } from '$app/environment';
|
|
|
|
// Tipos de proveedores SSO soportados
|
|
export type SSOProvider = 'microsoft' | 'google' | 'github';
|
|
|
|
/**
|
|
* Inicia el flujo de autenticación con un proveedor SSO
|
|
* @param provider - El proveedor SSO a utilizar
|
|
*/
|
|
export const loginWithProvider = async (provider: SSOProvider): Promise<void> => {
|
|
if (!browser) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Construir la URL de redirección al proveedor SSO
|
|
const keycloakUrl = import.meta.env.VITE_KEYCLOAK_URL;
|
|
const realm = import.meta.env.VITE_KEYCLOAK_REALM;
|
|
const clientId = import.meta.env.VITE_KEYCLOAK_CLIENT_ID;
|
|
|
|
// Validar que las variables de entorno estén configuradas
|
|
if (!keycloakUrl || !realm || !clientId) {
|
|
const missing = [];
|
|
if (!keycloakUrl) missing.push('VITE_KEYCLOAK_URL');
|
|
if (!realm) missing.push('VITE_KEYCLOAK_REALM');
|
|
if (!clientId) missing.push('VITE_KEYCLOAK_CLIENT_ID');
|
|
|
|
const errorMsg = `Configuración de Keycloak incompleta. Faltan las siguientes variables de entorno: ${missing.join(', ')}. Por favor, verifica tu archivo .env y reinicia el servidor de desarrollo.`;
|
|
console.error(errorMsg);
|
|
alert(errorMsg);
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
const redirectUri = encodeURIComponent(window.location.origin + '/auth/callback');
|
|
|
|
// URL de login de Keycloak con el provider específico
|
|
const loginUrl = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=openid&kc_idp_hint=${provider}`;
|
|
|
|
// Redirigir al usuario al proveedor SSO
|
|
window.location.href = loginUrl;
|
|
} catch (error) {
|
|
console.error(`Error al iniciar login con ${provider}:`, error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Obtiene la lista de proveedores SSO disponibles
|
|
* Esta función podría consultar a Keycloak para obtener los providers configurados
|
|
*/
|
|
export const getAvailableProviders = async (): Promise<SSOProvider[]> => {
|
|
// Por ahora retornamos una lista estática
|
|
// En producción, esto debería consultarse desde Keycloak
|
|
return ['microsoft', 'google', 'github'];
|
|
};
|
|
|
|
/**
|
|
* Obtiene la configuración de visualización para un proveedor
|
|
*/
|
|
export const getProviderConfig = (provider: SSOProvider) => {
|
|
const configs = {
|
|
microsoft: {
|
|
name: 'Microsoft',
|
|
icon: '🪟',
|
|
color: 'bg-blue-600 hover:bg-blue-700'
|
|
},
|
|
google: {
|
|
name: 'Google',
|
|
icon: '🔍',
|
|
color: 'bg-red-600 hover:bg-red-700'
|
|
},
|
|
github: {
|
|
name: 'GitHub',
|
|
icon: '🐙',
|
|
color: 'bg-gray-800 hover:bg-gray-900'
|
|
}
|
|
};
|
|
|
|
return configs[provider];
|
|
};
|
|
|
|
/**
|
|
* Intercambia el código de autorización por tokens
|
|
*/
|
|
export const exchangeCodeForTokens = async (
|
|
code: string,
|
|
redirectUri: string
|
|
): Promise<{ access_token: string; refresh_token: string; id_token?: string }> => {
|
|
try {
|
|
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/api/';
|
|
const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL : `${API_BASE_URL}/`;
|
|
|
|
const response = await fetch(`${baseUrl}v1/auth/exchange-code`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
code,
|
|
redirect_uri: redirectUri
|
|
})
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json();
|
|
throw new Error(errorData.detail || 'Error intercambiando código por tokens');
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('Error en exchangeCodeForTokens:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Decodifica un JWT (sin verificar la firma)
|
|
* NOTA: Esta es una decodificación simple para obtener los claims.
|
|
* La verificación de la firma debe hacerse en el backend.
|
|
*/
|
|
export const decodeJWT = (token: string): any => {
|
|
try {
|
|
const parts = token.split('.');
|
|
if (parts.length !== 3) {
|
|
throw new Error('Token JWT inválido');
|
|
}
|
|
|
|
// Decodificar la parte del payload (segunda parte)
|
|
const payload = parts[1];
|
|
const decodedPayload = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
return JSON.parse(decodedPayload);
|
|
} catch (error) {
|
|
console.error('Error decodificando JWT:', error);
|
|
return null;
|
|
}
|
|
};
|