/** * 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 => { 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 => { // 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; } };