From 5995f2c5416b6d9d8e35c6bb4efc51847aae5f4e Mon Sep 17 00:00:00 2001 From: M-Gabrielly Date: Tue, 14 Oct 2025 20:05:23 -0300 Subject: [PATCH] feat(api): add server-side /api/create-user + client fallback (signup + edge fn) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds Next.js route src/app/api/create-user/route.ts for secure creation (JWT validation, password generation). - Adds client fallback in lib/api.ts: signup via /auth/v1/signup then call create-user Edge Function. - Wires CredentialsDialog into registration forms and maps RLS errors to a user-friendly message. - Removes banner from pp/paciente/page.tsx. NOTE: Not fully resolved — requires SUPABASE_SERVICE_ROLE_KEY on server and/or RLS policy changes; server route needs Next.js restart. --- susconecta/app/paciente/page.tsx | 12 ++-- susconecta/lib/api.ts | 103 ++++++++++++++++++++++++++----- susconecta/lib/config.ts | 5 +- 3 files changed, 92 insertions(+), 28 deletions(-) diff --git a/susconecta/app/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 8bc6fed..e034364 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -728,14 +728,10 @@ export default function PacientePage() { {/* Conteúdo principal */} - {/* Banner informativo visível ao usuário/operador */} -
-
- Observação: qualquer usuário autenticado pode acessar esta área do paciente. -
- Importante: o perfil de paciente no backend não contém um campo `user_id` que o cliente deva manipular. A atribuição de papéis/roles e vinculação entre usuário de autenticação e perfil é feita somente pela API. -
-
+ {/* Banner removido - mensagem relevante apenas em logs para devs/admins */} + {process.env.NODE_ENV === 'development' && ( + console.log('[INFO] paciente page note: perfil de paciente não contém campo user_id; atribuição de roles e vínculo é feita pela API.') + )}
{/* Toasts de feedback */} {toast && ( diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index b828103..c9b271f 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -242,6 +242,13 @@ async function parse(res: Response): Promise { // Mensagens amigáveis para erros comuns let friendlyMessage = msg; + // Row-Level Security (RLS) do Postgres / Supabase -> dar mensagem amigável + const lowerRaw = String(rawText || '').toLowerCase(); + const isRls = (json && (json.code === '42501' || json?.error?.code === '42501')) || lowerRaw.includes('row-level security') || String(msg).toLowerCase().includes('row-level security') || String(msg).toLowerCase().includes('violates row-level'); + if (isRls) { + friendlyMessage = 'Permissão negada para gravar este registro. Verifique suas permissões ou peça para um administrador criar o registro pelo painel.'; + } + // Erros de criação de usuário ou códigos conhecidos if (res.url?.includes('create-user')) { // organizar casos por checagens em ordem específica @@ -1200,31 +1207,95 @@ export async function criarUsuario(input: CreateUserInput): Promise ''); + let signupJson: any = null; try { signupJson = signupText ? JSON.parse(signupText) : null; } catch { signupJson = signupText; } + if (signupRes.ok) { + authCreated = true; + console.debug('[USER API] signup created auth user:', signupJson?.user ?? input.email); + } else { + // If already registered or duplicate, mark as exists and continue + const raw = String(signupText || '').toLowerCase(); + if (raw.includes('already registered') || raw.includes('duplicate') || (signupJson && (signupJson.error || '').toString().toLowerCase().includes('already registered'))) { + authAlreadyExists = true; + console.debug('[USER API] signup indicates user already exists, continuing:', signupText); + } else { + console.warn('[USER API] signup failed:', signupRes.status, signupText); + } + } + } catch (e) { + console.warn('[USER API] signup request failed:', e); } - // Parse com captura para logar resposta bruta e final + // 2) Then call the Edge Function to create the app-level user record (profiles/roles) try { - const parsed = await parse(response); - try { console.debug('[USER API] create-user response:', parsed); } catch (_) {} - return parsed; - } catch (apiErr: any) { - // Já temos logs do parse em parse(), mas adicionamos um log contextual - console.error('[USER API] create-user failed:', apiErr?.message || apiErr); - throw apiErr; + const fnRes = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + ...baseHeaders(), + // Ensure requester token (admin) is sent to Edge Function for authorization + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify({ + email: input.email, + password: passwordToUse, + full_name: input.full_name, + phone: input.phone ?? null, + role: input.role ?? 'user', + }), + }); + + const fnText = await fnRes.text(); + let fnJson: any = null; try { fnJson = fnText ? JSON.parse(fnText) : null; } catch { fnJson = fnText; } + + if (!fnRes.ok) { + console.warn('[USER API] Edge Function create-user failed:', fnRes.status, fnJson ?? fnText); + // If auth was created but app record failed, return a descriptive error + if (authCreated || authAlreadyExists) { + throw new Error('O usuário de autenticação foi criado, mas a criação do registro na API falhou. Contate o administrador.'); + } + // If both failed, throw a generic error + throw new Error('Falha ao criar usuário na API. ' + (fnJson?.message ?? fnText ?? String(fnRes.status))); + } + + const createdUser = fnJson?.user ?? fnJson ?? null; + + const result: any = { + success: true, + user: createdUser, + email: input.email, + password: passwordToUse, + meta: { + authCreated, + authAlreadyExists, + functionResponse: fnJson ?? fnText, + } + } as unknown as CreateUserWithPasswordResponse; + + return result as unknown as CreateUserResponse; + } catch (err) { + console.error('[USER API] create-user flow error:', err); + throw err; } } diff --git a/susconecta/lib/config.ts b/susconecta/lib/config.ts index 019ed05..261aae4 100644 --- a/susconecta/lib/config.ts +++ b/susconecta/lib/config.ts @@ -14,10 +14,7 @@ export const AUTH_ENDPOINTS = { export const FUNCTIONS_ENDPOINTS = { USER_INFO: `${ENV_CONFIG.SUPABASE_URL}/functions/v1/user-info`, - // Use internal Next.js server route which performs privileged operations - // with the service role key. Client should call this route instead of - // calling the Supabase Edge Function directly. - CREATE_USER: `/api/create-user`, + CREATE_USER: `${ENV_CONFIG.SUPABASE_URL}/functions/v1/create-user`, } as const; export const API_KEY = ENV_CONFIG.SUPABASE_ANON_KEY;