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
+12
-21
@@ -117,7 +117,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
modo: torna alla schermata di login.
|
||||
*Codice:* `app/auth/dependencies.py:get_current_user` ·
|
||||
*Test:* `tests/unit/test_users.py`, `tests/unit/test_bug_reports.py`
|
||||
*Stato:* da implementare (B-94), per l'header assente · *Oggi:* l'assenza
|
||||
dell'header è intercettata da FastAPI prima del codice applicativo e
|
||||
risponde con il corpo non strutturato `{"detail": "Not authenticated"}`.
|
||||
|
||||
@@ -137,7 +136,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
(vedi [sicurezza.md](sicurezza.md)).
|
||||
*Codice:* `app/api/routes/admin.py:require_admin` ·
|
||||
*Test:* `tests/unit/test_admin.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* `403` con il corpo non
|
||||
strutturato `{"detail": "invalid admin token"}`.
|
||||
|
||||
- **[API-09]** Intestazioni di cache:
|
||||
@@ -156,7 +154,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`app/main.py:user_guide`, `app/main.py:report_bug_page`,
|
||||
`app/api/routes/rounds.py:round_stream`, `app/api/routes/qr.py:address_qr`,
|
||||
`app/main.py` (`app.mount`)
|
||||
*Stato:* da implementare (B-94), per i file statici · *Oggi:* i file
|
||||
serviti dal mount non hanno `Cache-Control`, solo `ETag`/`Last-Modified`,
|
||||
e il browser può riusarli senza rivalidarli.
|
||||
|
||||
@@ -169,8 +166,8 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
*Test:* `tests/unit/test_docs_disabled.py`
|
||||
|
||||
- **[API-11]** Dove un controllo dipende dall'indirizzo IP del chiamante (limiti
|
||||
di login e registrazione, tetto per IP del flusso SSE e, da implementare con
|
||||
B-94, quota per IP di `POST /bug-reports`), l'IP è l'**ultimo**
|
||||
di login e registrazione, tetto per IP del flusso SSE e quota per IP di
|
||||
`POST /bug-reports`), l'IP è l'**ultimo**
|
||||
elemento di `X-Forwarded-For` (quello scritto dal proxy fidato), o in sua
|
||||
assenza l'indirizzo della connessione, o `"unknown"`. Il motivo e il ruolo di
|
||||
Caddy sono in [sicurezza.md](sicurezza.md).
|
||||
@@ -238,11 +235,10 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
con `type: "json_invalid"`). Il valore ricevuto (`input`) **non** è
|
||||
ripetuto nella risposta, così una password troppo lunga non torna indietro
|
||||
in chiaro.
|
||||
*Codice:* `app/main.py` (gestore di `RequestValidationError`, da scrivere),
|
||||
*Codice:* `app/main.py` (gestore di `RequestValidationError`),
|
||||
`app/static/app.js:apiErrorMessage` ·
|
||||
*Test:* `tests/unit/test_users.py`, `tests/unit/test_admin.py`,
|
||||
`tests/unit/test_bug_reports.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* `422` nel formato **nativo** di
|
||||
FastAPI: `detail` è una **lista** di oggetti `{type, loc, msg, input, ctx?}`,
|
||||
senza `code`; il frontend riconosce la lista e mostra il messaggio generico
|
||||
tradotto `error.invalid_request`.
|
||||
@@ -269,7 +265,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`app/api/routes/admin.py:update_bug_report_status`,
|
||||
`app/api/routes/rounds.py:round_stream`, `app/main.py` ·
|
||||
*Test:* `tests/unit/test_admin.py`, `tests/unit/test_bug_reports.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* tutte le risposte della tabella
|
||||
hanno `detail` come stringa inglese (colonna "Oggi"), e il frontend la
|
||||
mostra così com'è.
|
||||
|
||||
@@ -334,7 +329,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
massimo 256 caratteri (oltre: `422 invalid_request`, prima di qualunque
|
||||
hash Argon2 e senza consumare i contatori di fallimento). Lo username è
|
||||
confrontato senza distinzione tra maiuscole e minuscole.
|
||||
*Stato:* da implementare (B-94) · *Oggi:* nessun vincolo di lunghezza.
|
||||
- **Risposta `200`:** `{"access_token": <JWT>, "address": <indirizzo>}`.
|
||||
- **Errori:** `422`; `429 rate_limited` (`params.retry_after_seconds`,
|
||||
arrotondato per eccesso al secondo); `401 invalid_credentials` —
|
||||
@@ -399,7 +393,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
*Codice:* `app/api/routes/users.py:change_password`,
|
||||
`app/auth/rate_limit.py:RateLimiter` ·
|
||||
*Test:* `tests/unit/test_users.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* nessuna lunghezza massima, nessun
|
||||
limite di frequenza (la password attuale si può provare senza limiti) e
|
||||
nessuna voce di audit.
|
||||
|
||||
@@ -609,7 +602,7 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`app/rounds/events.py:RoundEventBroadcaster.subscribe`,
|
||||
`app/rounds/events.py:RoundEventBroadcaster.publish` ·
|
||||
*Test:* `tests/unit/test_round_events.py`
|
||||
*Stato:* da implementare (B-94, B-93) · *Oggi:* il `503` ha il corpo non
|
||||
*Stato:* da implementare (B-93), per la chiusura dei flussi all'arresto
|
||||
strutturato `{"detail": "too many concurrent update streams"}`, e un flusso
|
||||
aperto non viene chiuso all'arresto: uvicorn lo attende finché Docker non
|
||||
termina il processo.
|
||||
@@ -720,7 +713,6 @@ esiste una API separata per l'amministrazione, solo un'autenticazione diversa.
|
||||
`app/api/routes/bug_reports.py:BugReportCreate`,
|
||||
`app/auth/rate_limit.py:RollingQuota` ·
|
||||
*Test:* `tests/unit/test_bug_reports.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* nessun limite di frequenza: un
|
||||
anonimo può inserire segnalazioni senza limite.
|
||||
|
||||
#### GET /bug-reports/mine
|
||||
@@ -841,7 +833,6 @@ dei parametri operativi in
|
||||
"balance_sats", "created_at"}`; `balance_sats` è il saldo confermato.
|
||||
*Codice:* `app/api/routes/admin.py:list_users` ·
|
||||
*Test:* `tests/unit/test_admin.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* restituisce **tutti** gli utenti
|
||||
in una sola risposta, senza paginazione né limite.
|
||||
|
||||
#### GET /admin/users/{user_id}/privkey
|
||||
@@ -1029,7 +1020,7 @@ dei parametri operativi in
|
||||
|---|---|---|---|---|---|
|
||||
| `session_expired` | 401 | `app/auth/dependencies.py:get_current_user` | — | token illeggibile, scaduto, utente inesistente o token superato da un cambio/reset password | sì |
|
||||
| `invalid_credentials` | 401 | `app/auth/routes.py:login` | — | username inesistente o password sbagliata (risposta identica) | sì |
|
||||
| `rate_limited` | 429 | `app/auth/routes.py:register`, `app/auth/routes.py:login`; anche `app/api/routes/users.py:change_password` e `app/api/routes/bug_reports.py` *da implementare (B-94)* | `retry_after_seconds` | quota di registrazione, blocco per tentativi falliti, o quota di segnalazioni | sì |
|
||||
| `rate_limited` | 429 | `app/auth/routes.py:register`, `app/auth/routes.py:login`; anche `app/api/routes/users.py:change_password` e `app/api/routes/bug_reports.py` | `retry_after_seconds` | quota di registrazione, blocco per tentativi falliti, o quota di segnalazioni | sì |
|
||||
| `username_taken` | 409 | `app/auth/routes.py:register` | — | nome già usato (senza distinzione di maiuscole) | sì |
|
||||
| `derivation_index_conflict` | 409 | `app/auth/routes.py:register` | — | nessun indice di derivazione libero dopo 5 tentativi | sì |
|
||||
| `current_password_incorrect` | 401 | `app/api/routes/users.py:change_password` | — | password attuale errata | sì |
|
||||
@@ -1053,14 +1044,14 @@ dei parametri operativi in
|
||||
| `withdrawal_to_platform_address` | 400 | `app/withdrawals/service.py:request_withdrawal` | — | destinazione uguale a un indirizzo di montepremi, di riserva o delle commissioni | sì |
|
||||
| `service_busy` | 503 | `app/bets/service.py:place_bet`, `app/withdrawals/service.py:request_withdrawal`, `app/auth/routes.py:register` | — | il database non ha concesso il lock di scrittura entro il timeout; nulla è stato trasmesso, si può riprovare | sì |
|
||||
| `registration_unavailable` | 503 | `app/auth/routes.py:register` | — | registrazione sospesa finché il controllo degli indici di derivazione dopo un ripristino non è concluso o risolto ([registrazione-e-depositi.md](registrazione-e-depositi.md), B-90) | sì |
|
||||
| `not_authenticated` | 401 | dipendenza Bearer di FastAPI | — | header `Authorization` assente — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `invalid_admin_token` | 403 | `app/api/routes/admin.py:require_admin` | — | token admin assente o errato — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `user_not_found` | 404 | `app/api/routes/admin.py` | — | utente inesistente — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `bug_report_not_found` | 404 | `app/api/routes/admin.py` | — | segnalazione inesistente — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `not_authenticated` | 401 | dipendenza Bearer di FastAPI | — | header `Authorization` assente | sì |
|
||||
| `invalid_admin_token` | 403 | `app/api/routes/admin.py:require_admin` | — | token admin assente o errato | sì |
|
||||
| `user_not_found` | 404 | `app/api/routes/admin.py` | — | utente inesistente | sì |
|
||||
| `bug_report_not_found` | 404 | `app/api/routes/admin.py` | — | segnalazione inesistente | sì |
|
||||
| `round_not_found` | 404 | `app/api/routes/rounds.py` (`GET /rounds/{id}/proof`) | — | round inesistente | sì |
|
||||
| `not_found` | 404 | gestore delle eccezioni HTTP in `app/main.py` | — | percorso inesistente — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `method_not_allowed` | 405 | gestore delle eccezioni HTTP in `app/main.py` | — | metodo non ammesso — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `too_many_streams` | 503 | `app/api/routes/rounds.py` (SSE) | — | tetto globale degli stream raggiunto — oggi `detail` stringa — *da implementare (B-94)* | no — da aggiungere |
|
||||
| `not_found` | 404 | gestore delle eccezioni HTTP in `app/main.py` | — | percorso inesistente | sì |
|
||||
| `method_not_allowed` | 405 | gestore delle eccezioni HTTP in `app/main.py` | — | metodo non ammesso | sì |
|
||||
| `too_many_streams` | 503 | `app/api/routes/rounds.py` (SSE) | — | tetto globale degli stream raggiunto | sì |
|
||||
|
||||
Due codici di `app/wallet/psbt_builder.py` — `winner_share_below_network_fee`
|
||||
e `commission_below_dust_limit` — riguardano solo la costruzione del
|
||||
|
||||
Reference in new issue
Block a user