F6.2 Lote F: RPCs anon/token resolvem tenant por token/slug + roteiam

DB (supabase_admin, manual/f6_2f_anon_token_rpcs.supabase_admin.sql):
- Documentos anon (token): validate_share_token, get_signable_document_by_token,
  sign_document_by_token resolvem tenant de document_share_links.tenant_id
  (public/F1b) -> set_config search_path; documents/document_signatures/
  document_access_logs no schema (RECORD em vez de %ROWTYPE; RETURNS
  document_signatures->jsonb; document_access_logs sem tenant_id).
  document_share_links continua public.
- sign_document_by_signature_id (paciente LOGADO, nao e tenant_member):
  +p_tenant_id via _tenant_schema_unchecked + autorizacao por LINHA (signatario_id
  =uid OU email OU documento do paciente). RETURNS->jsonb.
- agendador_dias/slots_disponiveis (anon): resolvem tenant de agendador_
  configuracoes.tenant_id (public/F1b); agenda/recurrence no schema;
  agendador_configuracoes/solicitacoes ficam public.
- match_patient_by_phone (edge service): _tenant_schema_unchecked + REVOKE de
  anon/authenticated, GRANT service_role.
- list_my_signatures: CROSS-TENANT -> fan-out por schema (RETURN QUERY EXECUTE
  por tenant_schemas, tenant_id injetado; document_share_links global).
- RPCs public-only (create_patient_intake_request, get_patient_intake_invite_info,
  issue_patient_invite, rotate_*, agendador_gerar_slug): SEM mudanca (F1b public).

Frontend: signByPortal(tenantId, signatureId, hash) + composable resolve
tenant_id da linha (list_my_signatures retorna tenant_id). Build passa.

Gotcha: paciente assinante NAO e tenant_member -> auth por linha, nao membership.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-06-13 15:51:22 -03:00
parent f079192698
commit 1243a12ced
3 changed files with 258 additions and 4 deletions
@@ -66,12 +66,17 @@ export function useDocumentSignatures() {
}
}
async function sign(signatureId, { hashDocumento = null } = {}) {
async function sign(signatureId, { hashDocumento = null, tenantId = null } = {}) {
loading.value = true;
error.value = '';
try {
const updated = await signByPortal(signatureId, hashDocumento);
const idx = signatures.value.findIndex(s => s.id === signatureId);
// schema-per-tenant: a assinatura vive no schema do tenant. Resolve o
// tenant_id da própria linha (list_my_signatures retorna tenant_id) ou
// do parâmetro explícito.
const row = signatures.value.find(s => (s.signature_id ?? s.id) === signatureId);
const tid = tenantId || row?.tenant_id;
const updated = await signByPortal(tid, signatureId, hashDocumento);
const idx = signatures.value.findIndex(s => (s.signature_id ?? s.id) === signatureId);
if (idx >= 0) signatures.value.splice(idx, 1, updated);
return updated;
} catch (e) {