Rate-limit /authenticate per client IP and per username
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 30/100
Direzione di ricerca
Read CONTRIBUTING.md first, then inspect main() in app/app.py, the error pattern in app/metrics/auth.py, and the authentication documentation in app/docs/authenticate.py. Confirm Render's forwarded-IP behavior with a maintainer on staging before designing the limiter; done means bounded configurable limits, documented 429 responses, passing targeted and live tests, and no IP leakage in logs or metrics.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
🔄 Updated for v5.0.0 (2026-10-08). Still open and unchanged in substance. Since 5.0.0 (#241) pesu-auth signs in through PESU Academy's mobile API, so there is no CSRF token to protect; a wrong password is still a 401, which the per-username limit counts. References to the CSRF token and to #181 (now closed) are updated, along with the live-test note.
⚠️ PES University students only. This change sits in the login path and has to be tested end to end against PESU Academy with real PESU credentials (see "Tests that need credentials" in
CONTRIBUTING.md). Run the whole suite with your credentials in.envand paste the summary line into your PR. Every run makes real logins to PESU Academy, so run it when you need it, not in a loop.
Summary
/authenticate is public, anonymous and unlimited. Anyone can use pesu-auth to try password after password against any student's PESU account, hidden behind pesu-auth's IP address. That's a risk to students, and a risk to pesu-auth itself: if PESU Academy notices the abuse and blocks Render's IP, pesu-auth goes down for every app that uses it. Add rate limits per client IP and per username, returning 429 Too Many Requests with Retry-After.
Background
- There is no rate limiting anywhere in
app/. - pesu-auth can't see the real client IP yet. On Render, requests arrive through Render's proxy. uvicorn only trusts
X-Forwarded-Forfrom the addresses inforwarded_allow_ips, which defaults to theFORWARDED_ALLOW_IPSenvironment variable or127.0.0.1.main()inapp/app.pydoesn't set it, so todayrequest.client.hostis Render's proxy address for every request. A per-IP limit would be one shared limit for all users. - Setting
FORWARDED_ALLOW_IPS="*"makes uvicorn trust the header from anyone, and uvicorn may then take the leftmost address, which a client can forge by sending its ownX-Forwarded-For. The trustworthy address is the one Render adds. Check which position that is and don't assume it. - pesu-auth runs one process on Render's free tier, so in-memory state is enough. Say so in comments; a shared store such as Redis would only be needed with several instances.
Proposed change
1. Get the real client IP (do this first, verified on staging with a maintainer)
- Work out exactly what Render sends: deploy a temporary DEBUG log of the
X-Forwarded-Forchain to staging only (a maintainer has to deploy it). Send requests with and without a forgedX-Forwarded-For, and record which entry is Render's. Remove the log afterwards; IP addresses are personal data and must not be logged in production. - Implement a small
client_ip(request)helper that takes the address Render appended (the rightmost entry that isn't a trusted proxy, if that's what the experiment shows), and falls back torequest.client.hostwhen there's no header (local runs, Docker, tests). Document how to configure it for people running the Docker image behind their own proxy.
2. Rate limiter
- A small in-memory limiter (sliding window or token bucket) in its own module, for example
app/ratelimit.py, with an injectable clock for tests. It needs no new dependency.slowapiexists, but it renders its own error shape and would need to be adapted to{status, message, timestamp}; propose it only if you can justify it. - Per IP, on every
/authenticaterequest: for example 20 per minute. - Per username (
username.casefold()), counting failed logins (401) only: for example 5 per 15 minutes. This is what stops password guessing, even from many IPs. - Bounded memory: expire old entries, and cap the number of tracked keys (for example 10,000, evicting the oldest).
- Limits come from environment variables with sensible defaults (read once at startup, like
METRICS_TOKENinapp/metrics/auth.py), documented in.env.exampleand the README.
3. Response
- A new
PESUAcademyErrorsubclass,RateLimitError: status 429, message "Too many requests. Please try again later.", headerRetry-After: <seconds>(useheaders=on the exception likeMetricsAuthorizationErrordoes). - Check the limits before calling PESU Academy. A rejected request must not make any upstream call.
- Log the rejection at WARNING with the username (usernames are logged by design) and never the IP. No IP or username in metric labels;
errors_total{type="RateLimitError"}already counts rejections. - Add a 429 row to the README's
/authenticateresponses table and an example toapp/docs/authenticate.py.
Tests
- The limiter: allows up to the limit, rejects after it, recovers after the window, expires and evicts keys (fake clock).
-
client_ip: takes the right entry, ignores a forged leftmost entry, falls back without the header. -
/authenticate: 429 withRetry-Afterand the standard body; no upstream call made when limited; the per-username limit counts only 401s. - Existing tests aren't affected (configure high limits in the test fixtures).
- The live suite passes.
Acceptance criteria
- Client IPs are identified correctly on Render (evidence from staging in the PR), and a forged header can't bypass the limit
- Both limits work, are configurable and are documented
- Memory is bounded
- No IP address in logs or metrics
Related
#183 (bulkhead and circuit breaker) is a related protection; keep the two consistent. #181 (serialising logins per username) was closed: PESU Academy's mobile API allows several sessions per account, so concurrent logins no longer collide, and this issue's per-username limit is the remaining protection for a single account.
How to contribute
- Wait until you're assigned. Comment to ask for this issue, then wait for a maintainer to assign it to you before writing any code. PRs for unassigned issues are closed without review.
- Read
CONTRIBUTING.mdfirst. It covers forking, setting up withuv, and running the tests. - Branch from
devin your fork, and open the PR againstdevwith a Conventional Commit title andCloses #190in the description. - Bump the minor version in
pyproject.tomland runuv lock(CI checks this).uv run pre-commit run --all-filesmust pass, and coverage must stay at 100%. - Update the README,
app/docs/and comments in the same PR as the behaviour they describe. - Never log, print, store or echo a password or token, including in tests, PR descriptions and screenshots.
- Lingua principale
- Python
- Stelle
- 22
- Fork
- 29
- Merge medio
- 1g 3h
- PR unite (30g)
- 19
Preparare l'ambiente
- Include un Dockerfile o un file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di pesu-dev/auth
-
Check the README for dead links and fix the misleading Docker Hub badgeForse di nuovo libera @knownshah l’ha presa 18 giorni fa e non c’è nessuna pull request aperta. Apertadocumentation good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
pesu-dev/auth#220 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug documentation enhancement
Difficoltà 3/5 1-2 giorni Idoneità per principianti 54/100
I maintainer di solito rispondono entro 1 giorno
-
Commit SHA should be used instead of tag git based deployForse già presa @aditeyabaral l’ha presa 12 giorni fa. Apertabug
pesu-dev/auth#236 · 2 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Private Functions in `app.py` needs to be in separate fileForse già presa @aditeyabaral l’ha presa 12 giorni fa. Apertaapi enhancement
pesu-dev/auth#235 · 1 commento · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
-
Gather the campus mapping and other shared values into constants modulesForse già presa @edgelordlinux l’ha presa 12 giorni fa. Apertaapi enhancement good first issue pesuacademy student profile tests and ci/cd
pesu-dev/auth#230 · 3 commenti · 1 assegnatario ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di pesu-dev/auth
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
cellrishi-code/claude-skills-atlas#165 ·
I maintainer di solito rispondono entro 1 giorno
-
bug confirmed issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
open-webui/open-webui#32635 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Filters: ?field:ne=null matches NULL rows instead of non-NULL onesForse già presa @rshdhere l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
Netflix/metaflow-service#504 ·
-
needs-triage
Difficoltà 2/5 1-3 ore Idoneità per principianti 84/100
gke-labs/kube-agents#3394 ·
I maintainer di solito rispondono entro 1 giorno
-
A2A audit timestamps end in +00:00Z (invalid ISO 8601)Forse già presa @O1sumitkumar l’ha presa oggi. Apertabug good first issue help wanted python
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno