Capítulo 19 de 35

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.