M-Gabrielly fb578b2a7a feat(api): add server-side /api/create-user route (user creation not functional yet)
- What was done:
  - Added a server-side Next.js route at `src/app/api/create-user/route.ts` that validates the requester token, checks roles, generates a temporary password and forwards the creation to the Supabase Edge Function using the service role key.
  - Client wired to call the route via `lib/config.ts` (`FUNCTIONS_ENDPOINTS.CREATE_USER` -> `/api/create-user`) and the `criarUsuario()` wrapper in `lib/api.ts`.
- Status / missing work:
  - Important: user creation is NOT working yet (requests to `/api/create-user` return 404 in dev).
  - Next steps: restart dev server, ensure `SUPABASE_SERVICE_ROLE_KEY` is set in the environment, check server logs and run a test POST with a valid admin JWT.
2025-10-14 17:02:26 -03:00

199 lines
4.7 KiB
TypeScript

import type {
LoginRequest,
LoginResponse,
RefreshTokenResponse,
AuthError,
UserData
} from '@/types/auth';
import { API_CONFIG, AUTH_ENDPOINTS, DEFAULT_HEADERS, 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<string, string> {
return {
"Content-Type": "application/json",
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
"Authorization": `Bearer ${token}`,
};
}
/**
* Headers APENAS para login (SEM Authorization Bearer)
*/
function getLoginHeaders(): Record<string, string> {
return {
"Content-Type": "application/json",
"Accept": "application/json",
"apikey": ENV_CONFIG.SUPABASE_ANON_KEY,
};
}
/**
* Utilitário para processar resposta da API
*/
async function processResponse<T>(response: Response): Promise<T> {
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
* POST /auth/v1/token?grant_type=password
*/
export async function loginUser(
email: string,
password: string
): Promise<LoginResponse> {
const url = `${AUTH_ENDPOINTS.LOGIN}?grant_type=password`;
const headers = getLoginHeaders();
const body = JSON.stringify({ email, password });
console.log('[AUTH] Login request:', { url, email });
const response = await fetch(url, {
method: 'POST',
headers,
body,
});
return processResponse<LoginResponse>(response);
}
/**
* Serviço para fazer logout do usuário
* POST /auth/v1/logout
*/
export async function logoutUser(token: string): Promise<void> {
const url = AUTH_ENDPOINTS.LOGOUT;
const headers = getAuthHeaders(token);
console.log('[AUTH] Logout request');
const response = await fetch(url, {
method: 'POST',
headers,
});
if (!response.ok && response.status !== 204) {
await processResponse(response);
}
}
/**
* Serviço para renovar token JWT
* POST /auth/v1/token?grant_type=refresh_token
*/
export async function refreshAuthToken(refreshToken: string): Promise<RefreshTokenResponse> {
const url = `${AUTH_ENDPOINTS.LOGIN}?grant_type=refresh_token`;
const headers = getLoginHeaders();
const body = JSON.stringify({ refresh_token: refreshToken });
console.log('[AUTH] Refresh token request');
const response = await fetch(url, {
method: 'POST',
headers,
body,
});
return processResponse<RefreshTokenResponse>(response);
}
/**
* Serviço para obter dados do usuário atual
* GET /auth/v1/user
*/
export async function getCurrentUser(token: string): Promise<UserData> {
const url = AUTH_ENDPOINTS.USER;
const headers = getAuthHeaders(token);
console.log('[AUTH] Get current user request');
const response = await fetch(url, {
method: 'GET',
headers,
});
return processResponse<UserData>(response);
}
/**
* 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<Response> => {
const token = getToken();
if (token) {
const headers = {
...options.headers,
...getAuthHeaders(token),
};
options = {
...options,
headers,
};
}
return fetch(url, options);
};
}