Upload Seguro de Arquivos Sensíveis (Supabase Storage)
Índice do Manual
Quando se constrói um sistema SaaS de nível corporativo, o upload de arquivos confidenciais — como documentos de identidade (RG/CPF), exames médicos ou contratos assinados — é um ponto crítico de falha.
O erro arquitetural mais comum e destrutivo cometido por iniciantes é capturar o arquivo no Frontend (Next.js), enviá-lo para uma Rota de API no servidor (Node.js) e, de lá, retransmiti-lo para o banco de armazenamento (S3/Supabase).
Essa prática é perigosa. Transitar arquivos pesados pelo meio do servidor web engasga a memória RAM, derruba a performance da aplicação e gera custos astronômicos de transferência de dados (banda).
Neste manual, documentamos a arquitetura limpa e definitiva: o arquivo deve sair do navegador do usuário e pousar diretamente no cofre do Supabase Storage, com acesso bloqueado para o público e liberado apenas para o dono legítimo via tokens (RLS).
1. O Princípio do Cofre Privado
Diferente de fotos de perfil ou logomarcas que podem ser públicas, documentos sensíveis exigem que o Bucket (balde de arquivos) seja configurado como Privado.
No painel do Supabase, ao criar um novo Bucket (ex: documentos-confidenciais), a opção "Public bucket" deve estar desmarcada. Isso garante que nenhum link direto funcione sem uma autorização criptográfica.
Para amarrar a segurança, aplicamos as Políticas de Segurança em Nível de Linha (RLS) diretamente no Storage, garantindo que um usuário só possa subir arquivos na própria pasta e só possa baixar o que lhe pertence.
-- Política: O usuário só pode inserir arquivos dentro da pasta com o próprio ID
CREATE POLICY "Permitir upload para a própria pasta"
ON storage.objects FOR INSERT
TO authenticated
WITH CHECK (
bucket_id = 'documentos-confidenciais' AND
(storage.foldername(name))[1] = auth.uid()::text
);
-- Política: O usuário só pode ler arquivos da própria pasta
CREATE POLICY "Permitir leitura da própria pasta"
ON storage.objects FOR SELECT
TO authenticated
USING (
bucket_id = 'documentos-confidenciais' AND
(storage.foldername(name))[1] = auth.uid()::text
);
2. O Upload Direto (Next.js Client Component)
Com a barreira de segurança armada no banco, o Frontend ganha a permissão de falar diretamente com o Storage, contornando o servidor web por completo.
Abaixo está o código-fonte padrão para um componente React (Client Side) que faz o upload seguro. O arquivo sai da máquina do usuário, trafega criptografado e pousa no Supabase.
// src/components/UploadDocumento.tsx
'use client'
import { useState } from 'react'
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
export function UploadDocumento() {
const [loading, setLoading] = useState(false)
const supabase = createClientComponentClient()
const handleUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
try {
setLoading(true)
const file = event.target.files?.[0]
if (!file) return
// 1. Identificamos o usuário logado para colocar na pasta certa
const { data: { user } } = await supabase.auth.getUser()
if (!user) throw new Error("Usuário não autenticado")
// 2. Geramos um caminho único: [ID_DO_USUARIO]/[NOME_DO_ARQUIVO]
const fileExt = file.name.split('.').pop()
const fileName = `${Math.random()}.${fileExt}`
const filePath = `${user.id}/${fileName}`
// 3. UPLOAD DIRETO DO NAVEGADOR PARA O SUPABASE STORAGE (Sem passar pela API)
const { error: uploadError } = await supabase.storage
.from('documentos-confidenciais')
.upload(filePath, file, {
cacheControl: '3600',
upsert: false
})
if (uploadError) throw uploadError
alert('Documento seguro enviado com sucesso!')
} catch (error) {
console.error('Falha no upload:', error)
alert('Erro ao enviar o documento.')
} finally {
setLoading(false)
}
}
return (
<div className="p-4 border rounded-md">
<label className="block mb-2 font-semibold">Envie seu Contrato Assinado:</label>
<input
type="file"
accept="application/pdf, image/jpeg, image/png"
onChange={handleUpload}
disabled={loading}
className="block w-full text-sm file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
/>
{loading && <p className="mt-2 text-sm text-gray-500">Enviando arquivo pesado com segurança...</p>}
</div>
)
}
3. O Download Seguro (Signed URLs)
Como o bucket é privado, o link do arquivo é inútil para qualquer pessoa que tentar acessá-lo externamente. Para exibir esse contrato na tela do administrador ou do próprio cliente, utilizamos a técnica de Signed URLs (URLs Assinadas).
O Supabase gera um link temporário, válido por apenas alguns segundos, garantindo acesso exclusivo a quem solicitou.
// Exemplo de resgate do arquivo seguro
const { data, error } = await supabase.storage
.from('documentos-confidenciais')
.createSignedUrl(`${user.id}/${fileName}`, 60) // Token expira em 60 segundos
if (data) {
console.log("Acesse o arquivo temporariamente aqui:", data.signedUrl)
}
O Veredito Arquitetural
Ao eliminar o intermediário (servidor Node.js) no tráfego de arquivos grandes, garantimos que o sistema suporte o upload simultâneo de milhares de documentos pesados sem estourar o limite de memória ou gerar latência para os outros usuários do sistema.
A infraestrutura se torna leve, o armazenamento é direto, os custos de rede despencam e a segurança é aplicada matematicamente pelo próprio banco de dados (RLS). Esta é a fundação inegociável para a construção de sistemas modernos de alta disponibilidade.