Bound credentials, brake password changes, cap bug reports, page the user list, structure every error (B-94, SEC-54, SEC-55, API-09, API-15, API-16, API-19, API-21, API-28, API-33, REG-12, AUD-42)
- login and password-change fields: at most 256 characters
- wrong current password: per-user brake (5 free, 2s doubling to 300s), 429 even for the
right password while locked; password_changed audited in the same transaction
- POST /bug-reports: 10 per IP per hour, consumed only by a stored report
- GET /admin/users: limit/after_id paging, the panel walks the pages
- 401/403/404/405/422/503 all answer {code, message, params}; the 422 omits the input
- static files revalidated on every use (Cache-Control: no-cache)
- error.* keys for the new codes in all seven languages
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c1b192f6d1
commit
24e5fc3d8b
23 files changed
+374
-111
No files matched your search
+4
-14
@@ -324,7 +324,6 @@ che ne è il proprietario; qui ci sono le regole di sicurezza.
|
||||
*Codice:* `app/auth/security.py:MIN_PASSWORD_LENGTH`, `app/auth/routes.py:RegisterRequest`,
|
||||
`app/auth/routes.py:LoginRequest`, `app/api/routes/users.py:ChangePasswordRequest` ·
|
||||
*Test:* `tests/unit/test_users.py`
|
||||
*Stato:* da implementare (B-94), per il massimo su login e cambio password ·
|
||||
*Oggi:* il massimo di 256 vale solo per la password della registrazione;
|
||||
i campi del login e del cambio password non hanno limite di lunghezza.
|
||||
|
||||
@@ -374,7 +373,6 @@ che ne è il proprietario; qui ci sono le regole di sicurezza.
|
||||
*Codice:* `app/auth/dependencies.py:get_current_user`, `app/api/routes/users.py:change_password`,
|
||||
`app/api/routes/admin.py:reset_user_password` ·
|
||||
*Test:* `tests/unit/test_users.py`, `tests/unit/test_security.py`, `tests/unit/test_admin.py`
|
||||
*Stato:* da implementare (B-94), per `not_authenticated` · *Oggi:*
|
||||
l'header assente risponde 401 con il corpo non strutturato di FastAPI
|
||||
`{"detail": "Not authenticated"}`.
|
||||
|
||||
@@ -398,7 +396,6 @@ endpoint `/admin/*`, che richiedono l'header `X-Admin-Token`.
|
||||
`invalid_admin_token` ([API-08](api.md#convenzioni-generali)).
|
||||
*Codice:* `app/api/routes/admin.py:require_admin` ·
|
||||
*Test:* `tests/unit/test_admin.py`
|
||||
*Stato:* da implementare (B-94), per il codice · *Oggi:* 403 con il corpo
|
||||
non strutturato `{"detail": "invalid admin token"}`.
|
||||
|
||||
- **[SEC-25]** Il token è **unico e condiviso**: non esiste un'identità per
|
||||
@@ -457,8 +454,8 @@ ogni riavvio e non funzionerebbero con più worker.
|
||||
| Login per username | `user:<username minuscolo>` | Dal 5° fallimento: blocco di 2s, raddoppiato a ogni fallimento successivo, massimo 300s. Il contatore si azzera dopo 900s senza fallimenti, o con un login riuscito | `POST /auth/login` |
|
||||
| Login per IP | `ip:<ip>` | Dal 20° fallimento: stessa progressione (2s → 300s), stesso decadimento di 900s. **Non** si azzera con un login riuscito | `POST /auth/login` |
|
||||
| Registrazione per IP | `ip:<ip>` | Al massimo 5 account creati per IP in una finestra mobile di 3600s. Contano solo gli account creati davvero | `POST /auth/register` |
|
||||
| Cambio password per utente *(B-94, da implementare)* | `user:<id utente>`, in un limitatore separato da quello del login | Dal 5° fallimento della password attuale: stessa progressione del login (2s → 300s), stesso decadimento di 900s; un cambio riuscito azzera il contatore | `POST /users/me/change-password` |
|
||||
| Segnalazioni di bug per IP *(B-94, da implementare)* | `ip:<ip>` | Al massimo 10 segnalazioni per IP in una finestra mobile di 3600s, anche da autenticati. Contano solo le segnalazioni salvate davvero | `POST /bug-reports` |
|
||||
| Cambio password per utente *(B-94)* | `user:<id utente>`, in un limitatore separato da quello del login | Dal 5° fallimento della password attuale: stessa progressione del login (2s → 300s), stesso decadimento di 900s; un cambio riuscito azzera il contatore | `POST /users/me/change-password` |
|
||||
| Segnalazioni di bug per IP *(B-94)* | `ip:<ip>` | Al massimo 10 segnalazioni per IP in una finestra mobile di 3600s, anche da autenticati. Contano solo le segnalazioni salvate davvero | `POST /bug-reports` |
|
||||
| Stream SSE per IP | IP | Al massimo 5 stream contemporanei per IP: il 6° chiude il più vecchio dello stesso IP | `GET /rounds/stream` |
|
||||
| Stream SSE globale | — | Al massimo 500 stream contemporanei: oltre, 503 | `GET /rounds/stream` |
|
||||
|
||||
@@ -493,8 +490,7 @@ ogni riavvio e non funzionerebbero con più worker.
|
||||
[API-21](api.md).
|
||||
*Codice:* `app/api/routes/users.py:change_password`,
|
||||
`app/auth/rate_limit.py:RateLimiter`, `app/auth/rate_limit.py:AuthRateLimiters` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-94) · *Oggi:* nessun freno: con un token valido
|
||||
*Test:* `tests/unit/test_abuse_limits.py`
|
||||
la password attuale si può provare senza limiti.
|
||||
|
||||
- **[SEC-55]** L'invio di segnalazioni di bug è una **quota** per IP
|
||||
@@ -506,8 +502,7 @@ ogni riavvio e non funzionerebbero con più worker.
|
||||
vecchia ([API-28](api.md)).
|
||||
*Codice:* `app/api/routes/bug_reports.py:create_bug_report`,
|
||||
`app/auth/rate_limit.py:RollingQuota` ·
|
||||
*Test:* —
|
||||
*Stato:* da implementare (B-94) · *Oggi:* nessun limite: un anonimo può
|
||||
*Test:* `tests/unit/test_abuse_limits.py`
|
||||
inserire segnalazioni senza limite, fino a riempire il database.
|
||||
|
||||
- **[SEC-32]** La memoria dei limitatori è limitata: le voci esaurite vengono
|
||||
@@ -524,7 +519,6 @@ ogni riavvio e non funzionerebbero con più worker.
|
||||
*Codice:* `app/rounds/events.py:RoundEventBroadcaster.subscribe`,
|
||||
`app/api/routes/rounds.py:round_stream` ·
|
||||
*Test:* `tests/unit/test_round_events.py`
|
||||
*Stato:* da implementare (B-94), per il codice `too_many_streams` ·
|
||||
*Oggi:* il 503 ha il corpo non strutturato
|
||||
`{"detail": "too many concurrent update streams"}`.
|
||||
|
||||
@@ -536,7 +530,6 @@ ogni riavvio e non funzionerebbero con più worker.
|
||||
che non è un limite di frequenza ([LIM-07](limiti-noti.md)).
|
||||
*Codice:* `app/api/routes/bets.py`, `app/api/routes/withdrawals.py`,
|
||||
`app/api/routes/admin.py:require_admin`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* anche il cambio password e
|
||||
l'invio di segnalazioni di bug sono senza limite ([SEC-54], [SEC-55]).
|
||||
|
||||
## Indirizzo IP del client
|
||||
@@ -667,7 +660,6 @@ a sé. L'unica risorsa esterna ammessa è Google Fonts (l'`@import` in
|
||||
chiamate all'API solo dalla stessa origine.
|
||||
*Codice:* `app/main.py:index_page`, `app/main.py:admin_panel`,
|
||||
`app/main.py:user_guide`, `app/main.py:report_bug_page`, `app/main.py` (`app.mount`)
|
||||
*Stato:* da implementare (B-94), per i file statici · *Oggi:* i file
|
||||
serviti dal mount di `app/static/` non hanno `Cache-Control`, solo
|
||||
`ETag`/`Last-Modified`, e il browser può riusare uno script vecchio con
|
||||
una pagina nuova.
|
||||
@@ -687,7 +679,6 @@ a sé. L'unica risorsa esterna ammessa è Google Fonts (l'`@import` in
|
||||
([API-16](api.md#contratto-degli-errori)) e non ripete il valore ricevuto,
|
||||
così una password troppo lunga non torna indietro in chiaro.
|
||||
*Codice:* `app/main.py:log_unhandled_exception`, `app/main.py`
|
||||
*Stato:* da implementare (B-94), per gli errori diversi dal 500 ·
|
||||
*Oggi:* 401 senza header, 403 admin, 404/405 del framework e 503 dell'SSE
|
||||
hanno `detail` come stringa, e il 422 di validazione ripete nel campo
|
||||
`input` il valore ricevuto.
|
||||
@@ -734,7 +725,6 @@ di discrezione, non una protezione: la pagina è pubblica, gli endpoint no.
|
||||
`app/api/routes/admin.py:list_pending_transactions`, `app/api/routes/admin.py:list_bug_reports`,
|
||||
`app/api/routes/admin.py:list_users` ·
|
||||
*Test:* `tests/unit/test_admin.py`
|
||||
*Stato:* da implementare (B-94), per l'elenco utenti · *Oggi:*
|
||||
`GET /admin/users` restituisce tutti gli utenti in una sola risposta, senza
|
||||
limite né paginazione.
|
||||
|
||||
|
||||
Reference in new issue
Block a user