import type { LoginRequest, LoginResponse, RefreshTokenResponse, AuthError, UserData } from '@/types/auth'; import { API_CONFIG, AUTH_ENDPOINTS, DEFAULT_HEADERS, API_KEY, buildApiUrl } from '@/lib/config'; import { debugRequest } from '@/lib/debug-utils'; import { ENV_CONFIG } from '@/lib/env-config'; /** * Classe de erro customizada para autenticação */ export class AuthenticationError extends Error { constructor( message: string, public code: string, public details?: any ) { super(message); this.name = 'AuthenticationError'; } } /** * Headers para requisições autenticadas (COM Bearer token) */ function getAuthHeaders(token: string): Record { return { "Content-Type": "application/json", "Accept": "application/json", "apikey": API_KEY, "Authorization": `Bearer ${token}`, }; } /** * Headers APENAS para login (SEM Authorization Bearer) */ function getLoginHeaders(): Record { return { "Content-Type": "application/json", "Accept": "application/json", "apikey": API_KEY, }; } /** * Utilitário para processar resposta da API */ async function processResponse(response: Response): Promise { console.log(`[AUTH] Response status: ${response.status} ${response.statusText}`); let data: any = null; try { const text = await response.text(); if (text) { data = JSON.parse(text); } } catch (error) { console.log('[AUTH] Response sem JSON ou vazia (normal para alguns endpoints)'); } if (!response.ok) { const errorMessage = data?.message || data?.error || response.statusText || 'Erro na autenticação'; const errorCode = data?.code || String(response.status); console.error('[AUTH ERROR]', { url: response.url, status: response.status, data, }); throw new AuthenticationError(errorMessage, errorCode, data); } console.log('[AUTH] Response data:', data); return data as T; } /** * Serviço para fazer login e obter token JWT */ export async function loginUser( email: string, password: string, userType: 'profissional' | 'paciente' | 'administrador' ): Promise { let url = AUTH_ENDPOINTS.LOGIN; const payload = { email, password, }; console.log('[AUTH-API] Iniciando login...', { email, userType, url, payload, timestamp: new Date().toLocaleTimeString() }); console.log('🔑 [AUTH-API] Credenciais sendo usadas no login:'); console.log('📧 Email:', email); console.log('🔐 Senha:', password); console.log('👤 UserType:', userType); // Delay para visualizar na aba Network await new Promise(resolve => setTimeout(resolve, 50)); try { console.log('[AUTH-API] Enviando requisição de login...'); // Debug: Log request sem credenciais sensíveis debugRequest('POST', url, getLoginHeaders(), payload); const response = await fetch(url, { method: 'POST', headers: getLoginHeaders(), body: JSON.stringify(payload), }); console.log(`[AUTH-API] Login response: ${response.status} ${response.statusText}`, { url: response.url, status: response.status, timestamp: new Date().toLocaleTimeString() }); // Se falhar, mostrar detalhes do erro if (!response.ok) { try { const errorText = await response.text(); console.error('[AUTH-API] Erro detalhado:', { status: response.status, statusText: response.statusText, body: errorText, headers: Object.fromEntries(response.headers.entries()) }); } catch (e) { console.error('[AUTH-API] Não foi possível ler erro da resposta'); } } // Delay adicional para ver status code await new Promise(resolve => setTimeout(resolve, 50)); const data = await processResponse(response); console.log('[AUTH] Dados recebidos da API:', data); // Verificar se recebemos os dados necessários if (!data || (!data.access_token && !data.token)) { console.error('[AUTH] API não retornou token válido:', data); throw new AuthenticationError( 'API não retornou token de acesso', 'NO_TOKEN_RECEIVED', data ); } // Adaptar resposta da sua API para o formato esperado const adaptedResponse: LoginResponse = { access_token: data.access_token || data.token, token_type: data.token_type || "Bearer", expires_in: data.expires_in || 3600, user: { id: data.user?.id || data.id || "1", email: email, name: data.user?.name || data.name || email.split('@')[0], userType: userType, profile: data.user?.profile || data.profile || {} } }; console.log('[AUTH-API] LOGIN REALIZADO COM SUCESSO!', { token: adaptedResponse.access_token?.substring(0, 20) + '...', user: { email: adaptedResponse.user.email, userType: adaptedResponse.user.userType }, timestamp: new Date().toLocaleTimeString() }); // Delay final para visualizar sucesso await new Promise(resolve => setTimeout(resolve, 50)); return adaptedResponse; } catch (error) { console.error('[AUTH] Erro no login:', error); if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( 'Email ou senha incorretos', 'INVALID_CREDENTIALS', error ); } } /** * Serviço para fazer logout do usuário */ export async function logoutUser(token: string): Promise { const url = AUTH_ENDPOINTS.LOGOUT; console.log('[AUTH-API] Fazendo logout na API...', { url, hasToken: !!token, timestamp: new Date().toLocaleTimeString() }); // Delay para visualizar na aba Network await new Promise(resolve => setTimeout(resolve, 400)); try { console.log('[AUTH-API] Enviando requisição de logout...'); const response = await fetch(url, { method: 'POST', headers: getAuthHeaders(token), }); console.log(`[AUTH-API] Logout response: ${response.status} ${response.statusText}`, { timestamp: new Date().toLocaleTimeString() }); // Delay para ver status code await new Promise(resolve => setTimeout(resolve, 600)); // Logout pode retornar 200, 204 ou até 401 (se token já expirou) // Todos são considerados "sucesso" para logout if (response.ok || response.status === 401) { console.log('[AUTH] Logout realizado com sucesso na API'); return; } // Se chegou aqui, algo deu errado mas não é crítico para logout console.warn('[AUTH] API retornou status inesperado:', response.status); } catch (error) { console.error('[AUTH] Erro ao chamar API de logout:', error); } // Para logout, sempre continuamos mesmo com erro na API // Isso evita que o usuário fique "preso" se a API estiver indisponível console.log('[AUTH] Logout concluído (local sempre executado)'); } /** * Serviço para renovar token JWT */ export async function refreshAuthToken(refreshToken: string): Promise { const url = AUTH_ENDPOINTS.REFRESH; console.log('[AUTH] Renovando token'); try { const response = await fetch(url, { method: 'POST', headers: { "Content-Type": "application/json", "Accept": "application/json", "apikey": API_KEY, }, body: JSON.stringify({ refresh_token: refreshToken }), }); const data = await processResponse(response); console.log('[AUTH] Token renovado com sucesso'); return data; } catch (error) { console.error('[AUTH] Erro ao renovar token:', error); if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( 'Não foi possível renovar a sessão', 'REFRESH_ERROR', error ); } } /** * Serviço para obter dados do usuário atual */ export async function getCurrentUser(token: string): Promise { const url = AUTH_ENDPOINTS.USER; console.log('[AUTH] Obtendo dados do usuário atual'); try { const response = await fetch(url, { method: 'GET', headers: getAuthHeaders(token), }); const data = await processResponse(response); console.log('[AUTH] Dados do usuário obtidos:', { id: data.id, email: data.email }); return data; } catch (error) { console.error('[AUTH] Erro ao obter usuário atual:', error); if (error instanceof AuthenticationError) { throw error; } throw new AuthenticationError( 'Não foi possível obter dados do usuário', 'USER_DATA_ERROR', error ); } } /** * Utilitário para validar se um token está expirado */ export function isTokenExpired(expiryTimestamp: number): boolean { const now = Date.now(); const expiry = expiryTimestamp * 1000; // Converter para milliseconds const buffer = 5 * 60 * 1000; // Buffer de 5 minutos return now >= (expiry - buffer); } /** * Utilitário para interceptar requests e adicionar token automaticamente */ export function createAuthenticatedFetch(getToken: () => string | null) { return async (url: string, options: RequestInit = {}): Promise => { const token = getToken(); if (token) { const headers = { ...options.headers, ...getAuthHeaders(token), }; options = { ...options, headers, }; } return fetch(url, options); }; }