- 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.
199 lines
4.7 KiB
TypeScript
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);
|
|
};
|
|
} |