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.
Decisões de engenharia
Decisões e trade-offs
Cada decisão registra a justificativa e o trade-off aceito.
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.
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.
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
Backend
Dados
Identidade
Segurança
Observabilidade
Infraestrutura