feat(api): add server-side /api/create-user + client fallback (signup + edge fn)

- 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.
This commit is contained in:
M-Gabrielly 2025-10-14 20:05:23 -03:00
parent fb578b2a7a
commit 5995f2c541
3 changed files with 92 additions and 28 deletions

View File

@ -728,14 +728,10 @@ export default function PacientePage() {
<Button variant={tab==='perfil'?'secondary':'ghost'} aria-current={tab==='perfil'} onClick={()=>setTab('perfil')} className="justify-start"><UserCog className="mr-2 h-5 w-5" />{strings.perfil}</Button>
</nav>
{/* Conteúdo principal */}
{/* Banner informativo visível ao usuário/operador */}
<div className="w-full mb-4">
<div className="rounded border border-yellow-300 bg-yellow-50 p-3 text-sm text-yellow-900">
<strong>Observação:</strong> qualquer usuário autenticado pode acessar esta área do paciente.
<br />
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.
</div>
</div>
{/* 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.')
)}
<div className="flex-1 min-w-0 p-4 max-w-4xl mx-auto w-full">
{/* Toasts de feedback */}
{toast && (

View File

@ -242,6 +242,13 @@ async function parse<T>(res: Response): Promise<T> {
// 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<CreateUserRe
// ignore logging errors
}
let response: Response;
// Generate a password to use if caller didn't provide one
const passwordToUse = input.password ?? gerarSenhaAleatoria();
// 1) First, try to create auth user via public signup (anon key).
// This ensures the user can authenticate even if the Edge Function
// doesn't create the auth account.
let authCreated = false;
let authAlreadyExists = false;
try {
response = await fetch(url, {
const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`;
const signupRes = await fetch(signupUrl, {
method: 'POST',
headers: {
...baseHeaders(),
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
Accept: 'application/json',
apikey: ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(input),
body: JSON.stringify({ email: input.email, password: passwordToUse }),
});
} catch (networkErr) {
console.error('[USER API] Network error calling create-user', networkErr);
throw new Error('Erro de rede ao tentar criar usuário. Verifique sua conexão e tente novamente.');
const signupText = await signupRes.text().catch(() => '');
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<CreateUserResponse>(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;
}
}

View File

@ -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;