Andrade Systems
Todos os sistemas
Pré-lançamento

Lumen+

Busca por documento sem armazenamento em texto legível.

Aplicativo mobile · iOS, Android e Web

Pré-lançamento
Status
iOS · Android · Web
Plataformas
270+
Testes automatizados

Visão geral

O que este sistema resolve

Lumen+ é a plataforma de gestão de uma comunidade católica organizada em hierarquia de cinco níveis. Substitui planilha, formulário e grupo de mensagem por um aplicativo único em iOS, Android e Web: cadastro com perfil completo, estrutura organizacional, convites, mensageria interna, módulo de retiros com inscrição e comprovante de pagamento, e um módulo de acompanhamento espiritual com ciclos e revisões mensais.

Projeto voluntário, desenvolvido para a Obra Lumen de Evangelização.

O desafio

O que tornava o problema difícil

Estes são os pontos que mais influenciaram a arquitetura.

  • Dado pessoal sensível em volume, num app aberto a qualquer membro

    O cadastro guarda documento de identificação sob LGPD, num aplicativo que qualquer membro instala. O perfil de ameaça não é o de um sistema interno com dez operadores.

  • Busca por documento sem armazenamento legível

    O sistema precisa saber que dois cadastros são a mesma pessoa, e precisa localizar alguém pelo documento. Guardar o documento em texto plano transforma qualquer vazamento de banco num vazamento de base de documentos — e um hash comum não resolve, porque o espaço de documentos válidos é pequeno o bastante para ser enumerado em tempo trivial.

  • Autorização que segue uma árvore

    A hierarquia tem cinco níveis, e quem coordena um nível enxerga e opera o que está abaixo dele. Autorização por papel global não descreve isso; a permissão depende de onde a pessoa está na árvore.

  • Rede ruim e aparelho modesto

    O público usa o aplicativo em condição real de rede brasileira. Uma consulta que dispara uma chamada por item da lista funciona no teste com três registros e trava a tela com trezentos.

  • Exigências de loja de aplicativo

    Apple e Google exigem exclusão de conta funcionando de verdade, política de privacidade versionada com aceite registrado e moderação de conteúdo gerado por usuário. Não são detalhes de conformidade — sem eles o aplicativo não é publicado.

Arquitetura

Como o sistema é montado

Mobile

React Native · Expo · TypeScript

iOS, Android e Web a partir do mesmo código, com roteamento por arquivo.

Estado

Zustand · TanStack Query

Estado global separado de estado de servidor — cache, revalidação e erro num lugar só.

API

FastAPI · Python 3.12

Rotas tipadas com validação de entrada e saída pelo mesmo modelo.

Dados

PostgreSQL · SQLAlchemy · Alembic

Migrações versionadas; documento sensível nunca em claro.

Cache

Redis

Rate limiting distribuído, correto com múltiplas instâncias.

Identidade

Firebase Authentication

O backend valida o token e resolve o usuário local. Senha nunca passa pela API.

Observabilidade

Structlog · Prometheus · Sentry

Log estruturado com identificador de requisição; métricas emitidas no processo; rastreamento de erro configurado sem dado pessoal.

Como o documento é guardadoO documento informado no aplicativo segue por dois caminhos independentes: um HMAC-SHA256 calculado com um segredo de busca, que vira índice para comparar sem revelar; e uma cifra AES-256-GCM, que permite recuperar mas não comparar. Ambos são gravados no PostgreSQL, e os dois segredos vivem fora do banco.Documento informadoHMAC-SHA256segredo de buscaAES-256-GCMchave de cifraÍndice de buscacompara, não revelaTexto cifradorecupera, não comparaPostgreSQLos dois segredos vivem fora do bancosem eles, nenhuma das colunas diz nada
O documento entra uma vez e sai por dois caminhos que nunca se cruzam: um derivado determinístico que serve para comparar e indexar, e um texto cifrado que serve para recuperar. Sem os segredos, que vivem fora do banco, nenhum dos dois diz nada.

Decisões de engenharia

Decisões e trade-offs

Cada decisão registra a justificativa e o trade-off aceito.

  1. Identidade delegada a um provedor externo

    Autenticação de aplicativo de loja tem uma lista longa de requisitos — verificação de e-mail, recuperação de senha, políticas de sessão — e nada nela é diferencial deste produto. Delegar libera o esforço para o que é específico: a hierarquia, os retiros e a proteção do dado sensível.

    Custo aceito · Dependência de um provedor externo no caminho crítico de login. O backend mantém o usuário local desacoplado, de modo que trocar o provedor não significa reescrever o domínio.

  2. Métricas emitidas à mão, sem instalar plataforma

    O formato de exposição do Prometheus é texto simples; emiti-lo direto cobre o essencial — contagem e duração de requisições, requisições em voo, contagem de consultas por requisição e estado do pool — sem trazer uma stack inteira para um projeto voluntário.

    Custo aceito · Sem agregação histórica embutida. Em compensação, a regra de cardinalidade fica explícita no código, e não escondida na configuração de um agente.

  3. Acesso a documento sensível por fluxo de aprovação

    A maior parte da operação nunca precisa ver o documento de alguém. Permissão permanente para dado sensível vira acesso rotineiro, e acesso rotineiro não deixa rastro útil — todo mundo tem, então ninguém explica por quê.

    Custo aceito · Mais atrito para o administrador legítimo quando o acesso é realmente necessário. É o ponto: o atrito é o que faz o registro significar alguma coisa.

Por dentro da engenharia

Os problemas em detalhe

Cada bloco abre com o problema, mostra a solução e explica por que ela foi escolhida. Expanda o que interessar.

Identificador pesquisável sem ser legível

Problema

O sistema precisa responder duas perguntas: "este documento já está cadastrado?" e "qual usuário tem este documento?". Mas não pode guardá-lo de forma que um dump do banco entregue a base inteira. Hash comum não resolve o problema: o conjunto de documentos válidos é pequeno e conhecido, então basta gerar todos e comparar.

Solução

Dois campos, com propósitos que não se misturam. O campo de busca é um HMAC-SHA256 do documento normalizado, calculado com um segredo que vive apenas na configuração do processo — determinístico, portanto serve para índice único e para consulta por igualdade. O campo recuperável é o documento cifrado com AES-256-GCM, com vetor de inicialização novo a cada operação. Em produção, a ausência de qualquer um dos segredos impede o serviço de subir.

Por que foi feito assim

Separar "comparar" de "recuperar" é o que permite ter as duas propriedades ao mesmo tempo, e é por isso que são dois campos e dois segredos, e não um esquema só. Sem o segredo, o derivado não pode ser recomputado — a enumeração que quebra um hash puro deixa de funcionar, porque o atacante não consegue gerar os candidatos. É também a razão de as chaves viverem fora do banco: quem leva o dump não leva o que faz o dump valer alguma coisa. O custo é real e assumido: busca por igualdade exata funciona, busca parcial não; e rotacionar o segredo de busca exige reprocessar o índice inteiro.

IP de cliente resistente a falsificação

Problema

Limitar requisições por endereço de origem é inútil se o cliente pode escolher qual endereço o servidor enxerga. O cabeçalho de encaminhamento é uma lista, e o valor mais à esquerda — o que a leitura ingênua pega — é escrito por quem faz a chamada.

Solução

Em vez do primeiro valor, o sistema lê a posição contada a partir da direita, conforme o número conhecido de proxies confiáveis à frente da aplicação. O valor lido é validado como endereço; se não for, a leitura cai para o endereço da conexão real.

Por que foi feito assim

Cada proxy confiável acrescenta à direita o endereço de quem se conectou a ele. Contar da direita é a única leitura em que a posição consultada não é controlada pelo cliente — e fixar o número de saltos pela topologia real, em vez de confiar no cabeçalho inteiro, é o que impede que acrescentar entradas falsas mova o alvo.

Métricas com cardinalidade sob controle

Problema

Rótulo de métrica é um campo que multiplica séries temporais. Colocar identificador de usuário, e-mail ou caminho com identificador dentro de um rótulo faz duas coisas ruins ao mesmo tempo: explode o custo de armazenamento e vaza dado pessoal para um sistema que não foi feito para guardá-lo.

Solução

Regra de cardinalidade escrita como restrição do módulo de métricas: a rota entra sempre como template, nunca com o identificador real, e a lista do que é proibido em rótulo está no próprio código.

Por que foi feito assim

Vazamento por telemetria não aparece em revisão de segurança de API — a métrica não é uma rota, e quem revisa autorização não olha para ela. Deixar a regra escrita ao lado do código que a aplica é o que evita que a próxima métrica adicionada abra o buraco.

Testes de regressão de desempenho

Problema

Em ORM, carregar uma relação dentro de um laço vira uma consulta por item. O teste funcional passa — o resultado está certo. A tela é que fica inviável quando a lista cresce.

Solução

Testes que contam as consultas emitidas durante uma requisição e falham se o número crescer com o tamanho do resultado. A contagem atravessa o pool de threads do framework por um portador mutável, para que o incremento feito na thread de trabalho seja visto pela requisição que a originou.

Por que foi feito assim

Regressão de N+1 não quebra teste funcional: ela passa verde e só aparece em produção, com volume real e no aparelho do usuário. Transformar o desempenho numa asserção é o que faz o problema ser encontrado no pull request, e não no aplicativo publicado.

Exclusão de conta que exclui de verdade

Problema

As lojas exigem que o usuário consiga apagar a própria conta. Implementar isso como uma marcação de inativo satisfaz a tela e não satisfaz a lei — e quebra na primeira vez que alguém tenta se cadastrar de novo com o mesmo documento.

Solução

Fluxo de exclusão coberto por teste ponta a ponta, que percorre o dado do usuário nas tabelas relacionadas em vez de apenas marcar um campo.

Por que foi feito assim

Exclusão parcial é pior do que exclusão nenhuma: cria a expectativa de que o dado foi removido enquanto ele continua alcançável por outro caminho. O teste ponta a ponta existe porque essa é a garantia que degrada em silêncio a cada tabela nova.

Segurança

Por que estas defesas, neste domínio

O dado mais sensível do sistema é o documento de identificação dos membros. Toda decisão de segurança aqui parte de uma pergunta: o que este dado permite fazer se o banco vazar amanhã?

Documento cifrado e indexado por derivação com segredo

AES-256-GCM para o valor recuperável, HMAC-SHA256 com segredo próprio para o valor pesquisável. As duas chaves vivem apenas na configuração do processo; em produção, a ausência de qualquer uma impede o serviço de iniciar.

Acesso a documento por solicitação e aprovação

Nem o perfil administrativo vê documento por padrão. O acesso passa por um fluxo de pedido e concessão, e cada concessão gera registro de auditoria com quem, o quê e quando.

Rate limiting distribuído

Contadores em Redis, corretos com múltiplas instâncias, sobre o endereço resolvido de forma resistente a falsificação.

Rastreamento de erro sem dado pessoal

O monitoramento de exceções está configurado para não enviar informação identificável, e a regra de cardinalidade das métricas proíbe identificador em rótulo.

Aceite versionado de termos e política

A versão aceita fica registrada por usuário. Mudança de política gera novo aceite em vez de assumir consentimento retroativo.

Qualidade

O que os testes protegem

A suíte cobre três frentes que este produto não pode errar: autorização na hierarquia, proteção do dado sensível e desempenho de consulta. Há suítes de regressão dedicadas a autorização, porque cada nível novo na árvore é uma oportunidade de abrir acesso lateral sem perceber.

  • Que um coordenador não alcance dado fora do próprio ramo da hierarquia
  • Que uma conta não possa ser assumida por outra identidade
  • Que a exclusão de conta remova o dado, e não apenas o marque
  • Que listagens não degradem para uma consulta por item
  • Que upload e tamanho de corpo tenham limite verificado

Stack

Stack do projeto

O sistema em números

56
Tabelas de dados
174
Rotas de API
271 em 36 arquivos
Testes automatizados
47
Migrations
53
Telas no app

conferido no repositório em 27/08/2026

Status

Pré-lançamento

Mobile

React NativeExpoTypeScriptExpo RouterZustand

Backend

FastAPIPython 3.12PydanticSQLAlchemy

Dados

PostgreSQLAlembicRedis

Identidade

Firebase Authentication

Segurança

AES-256-GCMHMAC-SHA256Rate limiting

Observabilidade

StructlogPrometheusSentry

Infraestrutura

RailwayDockerEAS

Próximo sistema

RST Transparente

Nove domínios diferentes convivendo no mesmo sistema.

Produção