Files
plantillas-proyectos/frontend/src/lib/sso.ts

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;
}
};