feat(Server): Abstração de rate limiting sobre Redis (FIXR-44) - #88
Open
felipemartinezmelo wants to merge 2 commits into
Open
felipemartinezmelo wants to merge 2 commits into
felipemartinezmelo wants to merge 2 commits into
Conversation
Commit 990eba6 landed conflict markers in the account repository, breaking the TypeScript build on develop. The incoming side imported accountCacheKey/jwtPayloadCacheKey from core/lib/cache.ts, which da24aeb removed when the cache decorators replaced the manual helpers. Resolved in favour of the decorator pattern and converted updateAvatarUrl to @InvalidateCache, matching how credentials already invalidates those same keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Registers @fastify/rate-limit globally with a Redis store, so counters are shared across instances and survive a restart. Routes override the default with config.rateLimit rather than editing the plugin setup; /health is exempt so orchestrator probes are never throttled. Bucket identification is a list of strategies behind registerBucketResolver, falling back to the client IP. A module that introduces its own credential plugs in a resolver instead of this file knowing about it. Resolvers only see the raw request, since the limiter runs on onRequest, before authentication — which also means a flood of invalid credentials is throttled before it reaches the database. The limiter opens its own fail-fast Redis connection. The shared client uses maxRetriesPerRequest: null, so while Redis is unreachable its commands queue instead of failing, and a queued command would hang the request without ever triggering skipOnError. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extrai o rate limiting para uma camada própria, genérica e aplicável a toda a API (FIXR-44).
A primeira versão disso nasceu dentro da #86 (chaves de API), o que prendia uma capacidade transversal a uma feature específica. Aqui ela é reescrita sem nenhum acoplamento: a #86 não depende desta PR, e esta não depende da #86.
O que entra
@fastify/rate-limitcom store Redis, registrado antes das rotas:config.rateLimit, em vez de espalhar configuraçãoGET /healthisento — probe de load balancer e orquestrador não pode ser estranguladaapiResponse, com códigorate_limit_exceededjá registrado no catálogo central de errosx-ratelimit-limit,x-ratelimit-remaining,x-ratelimit-resetA abstração:
registerBucketResolverO ponto de extensão é uma lista de estratégias que identificam o bucket. A primeira que reconhece o request ganha; o fallback é o IP do cliente.
Assim um módulo que introduz um tipo próprio de credencial (chave de integração, assinatura de webhook) se registra, em vez deste arquivo precisar conhecer cada um deles.
Restrição que molda o desenho: o limiter roda no hook
onRequest, antes de qualquer middleware de autenticação. Um resolver só enxerga o request cru — nuncarequest.userourequest.apiKey. Isso é desejável: um flood de credenciais inválidas passa a ser limitado antes de chegar ao banco, protegendo o próprio lookup.Conexão Redis dedicada
O limiter abre a própria conexão, e isso não é preciosismo.
A instância compartilhada em
config/redis.tsusamaxRetriesPerRequest: null. Com o Redis fora do ar, isso faz os comandos serem enfileirados indefinidamente em vez de falharem — entãoskipOnErrornunca dispara e a requisição trava, sem timeout. O cenário exato que o rate limiting deveria sobreviver viraria uma indisponibilidade total.A conexão daqui usa
connectTimeout: 500,maxRetriesPerRequest: 1eenableOfflineQueue: false: falha rápido,skipOnErrorassume, e a API degrada para "sem limite" em vez de travar.nameSpacepróprio (fixr:rate-limit:) mantém os contadores longe das chaves dos decorators@Cached/@InvalidateCache.Escopo desta PR
Entrega a infraestrutura e um limite global padrão. Os tiers por grupo de rota descritos na issue ficam para um passo seguinte, agora que o mecanismo de override existe:
/auth/login,/auth/registere/credentials/*, por IP e por email (credential stuffing e enumeração de contas)allowListpara IPs internosbaneexponentialBackoffpara abuso repetidotrustProxy, se a API passar a ser servida atrás de proxy — sem isso o IP registrado é o do proxy, e todo mundo cai no mesmo bucketNota de revisão
O commit
a99abe8(resolução do conflito de merge emaccount/repositories) é o mesmo da #86, incluído aqui porque adevelopnão compila sem ele. Se a #86 entrar primeiro, ele vira no-op no merge.Validação