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
@@ -1,6 +1,6 @@
|
||||
# ADR-41 — Limitazione dei tentativi solo su login e registrazione
|
||||
|
||||
- **Stato:** Accettata — implementazione da completare (B-94)
|
||||
- **Stato:** Accettata
|
||||
- **Area:** Account, amministrazione e sicurezza
|
||||
- **Fonte di verità per:** la scelta di progetto descritta qui sotto; i requisiti (`PREFISSO-NN`) che la attuano stanno nei documenti di dominio, indicati nelle *Conseguenze*.
|
||||
|
||||
@@ -17,12 +17,10 @@ account creati davvero. I contatori sono limitati in numero.
|
||||
limita la produzione.
|
||||
- *Conseguenze:* nessun limite su scommesse, prelievi, admin ([LIM-07]).
|
||||
Il cambio password (limitato e registrato come `password_changed`) e le
|
||||
segnalazioni di bug (limitate per IP) si aggiungono con B-94; ogni campo
|
||||
segnalazioni di bug (limitate per IP) sono limitati (B-94); ogni campo
|
||||
delle credenziali ha una lunghezza massima di 256 caratteri
|
||||
([sicurezza.md](../sicurezza.md#limiti-di-frequenza), B-94).
|
||||
|
||||
*Codice:* `app/auth/rate_limit.py:AuthRateLimiters` · `app/auth/rate_limit.py:RateLimiter` · `app/auth/rate_limit.py:RollingQuota` · `app/auth/routes.py:login` · `app/api/routes/users.py:change_password` · `app/api/routes/bug_reports.py:create_bug_report` ·
|
||||
*Test:* `tests/unit/test_auth.py`, `tests/unit/test_rate_limit.py`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* cambio password e segnalazioni di
|
||||
bug non hanno limiti né lunghezze massime, e il cambio password non scrive
|
||||
audit.
|
||||
*Test:* `tests/unit/test_auth.py`, `tests/unit/test_rate_limit.py`, `tests/unit/test_abuse_limits.py`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# ADR-50 — Frontend statici, senza framework né build
|
||||
|
||||
- **Stato:** Accettata — implementazione da completare (B-94)
|
||||
- **Stato:** Accettata
|
||||
- **Area:** Interfacce, API e lingue
|
||||
- **Fonte di verità per:** la scelta di progetto descritta qui sotto; i requisiti (`PREFISSO-NN`) che la attuano stanno nei documenti di dominio, indicati nelle *Conseguenze*.
|
||||
|
||||
@@ -15,6 +15,3 @@ aggiornamento non lascia una pagina nuova con uno script vecchio;
|
||||
[architettura.md](../architettura.md#allimport), B-94).
|
||||
|
||||
*Codice:* `app/main.py:index_page` · `app/main.py:admin_panel` · `app/static/app.js` · `app/static/admin.js`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* solo le quattro pagine HTML hanno
|
||||
`Cache-Control` (`no-store`); JS e CSS, serviti da `StaticFiles`, non ne
|
||||
hanno.
|
||||
+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
|
||||
|
||||
@@ -397,7 +397,6 @@ implementare» non esistono ancora nel codice (vedi [ARC-60] e [ARC-22]).
|
||||
`/app`.
|
||||
*Codice:* `app/main.py:index_page` · `app/main.py:admin_panel` ·
|
||||
`app/main.py:user_guide` · `app/main.py:report_bug_page`
|
||||
*Stato:* da implementare (B-94) · *Oggi:* `Cache-Control` c'è **solo** sulle
|
||||
quattro pagine HTML; i file serviti da `StaticFiles` (e `/index.html`
|
||||
chiesto per nome) non ne hanno, e il browser può usare uno script vecchio
|
||||
con una pagina nuova.
|
||||
|
||||
+1
-2
@@ -275,7 +275,6 @@ flowchart LR
|
||||
sbagliata, nuova troppo corta) non scrive nulla. Nessuna logica rilegge la
|
||||
riga.
|
||||
*Codice:* `app/api/routes/users.py:change_password` · *Test:* —
|
||||
*Stato:* da implementare (B-94) · *Oggi:* il cambio password non scrive
|
||||
alcun evento.
|
||||
|
||||
- **[AUD-43]** `restore_gap_detected` — il controllo all'avvio ha trovato
|
||||
@@ -627,7 +626,7 @@ flowchart LR
|
||||
`app/rounds/scheduler.py:RoundScheduler._tick`,
|
||||
`app/rounds/confirmation.py:_on_payout_confirmed`,
|
||||
`app/tx/confirmation.py:poll_once`, `app/tx/broadcast.py:bump_fee`
|
||||
*Stato:* da implementare (B-92, B-94), per i due fatti che escono
|
||||
*Stato:* da implementare (B-92), per user_registered
|
||||
dall'elenco · *Oggi:* la registrazione e il cambio password riuscito non
|
||||
producono alcuna riga.
|
||||
|
||||
|
||||
+8
-22
@@ -151,10 +151,10 @@ implementato il [piano delle correzioni](piano-correzioni.md).
|
||||
blocco per-utente li serializza, non li limita); il cambio password
|
||||
permette di provare password attuali senza ritardi a chi possiede già un
|
||||
token valido; le segnalazioni anonime possono riempire il database.
|
||||
- *Stato:* da chiudere in parte con B-94 (cambio password limitato e
|
||||
registrato, segnalazioni di bug limitate per IP, lunghezza massima di 256
|
||||
caratteri sui campi delle credenziali). Scommesse, prelievi ed endpoint
|
||||
admin restano senza limite di frequenza anche dopo il piano.
|
||||
- *Stato:* chiuso in parte da B-94 (cambio password limitato e registrato,
|
||||
segnalazioni di bug limitate per IP, lunghezza massima di 256 caratteri sui
|
||||
campi delle credenziali). Scommesse, prelievi ed endpoint admin restano
|
||||
senza limite di frequenza.
|
||||
- *Per chiuderlo:* un limitatore per utente e per IP sugli endpoint
|
||||
interessati, sullo stesso modello di `RateLimiter`/`RollingQuota`.
|
||||
|
||||
@@ -166,8 +166,8 @@ implementato il [piano delle correzioni](piano-correzioni.md).
|
||||
- *Impatto:* dopo una compromissione non c'è traccia di quando e da dove sia
|
||||
avvenuto l'accesso.
|
||||
- *Stato:* da chiudere in parte con B-92 (`user_registered`, con indice di
|
||||
derivazione e indirizzo) e B-94 (`password_changed`). Login riusciti e
|
||||
falliti restano senza voce di audit dopo il piano.
|
||||
derivazione e indirizzo); chiusa da B-94 la parte `password_changed`. Login
|
||||
riusciti e falliti restano senza voce di audit dopo il piano.
|
||||
- *Per chiuderlo:* eventi di audit per questi casi (vedi
|
||||
[audit-log.md](audit-log.md)).
|
||||
|
||||
@@ -414,21 +414,7 @@ implementato il [piano delle correzioni](piano-correzioni.md).
|
||||
|
||||
*Codice:* `app/static/i18n.js:t`
|
||||
|
||||
- **[LIM-22]** **L'API di amministrazione e alcuni errori di contorno non usano
|
||||
il formato d'errore strutturato.** Gli endpoint `/admin/*` rispondono con un
|
||||
`detail` stringa (per esempio `"invalid admin token"`, `"user not found"`), e
|
||||
così il 503 del canale SSE oltre il tetto; è coerente con un pannello non
|
||||
tradotto, ma non è dichiarato altrove.
|
||||
- *Stato:* da chiudere con B-94.
|
||||
- *Per chiuderlo:* errori strutturati ovunque: `/admin/*`, il 503 del canale
|
||||
SSE e gli errori generati dal framework usano lo stesso formato
|
||||
`{code, message, params}` ([ADR-47](adr/ADR-47-l-api-parla-sola-lingua-risponde.md)).
|
||||
- ~~[LIM-22]~~ Rimosso: chiuso da B-94. Gli endpoint `/admin/*`, il `503` del canale SSE e gli errori del framework (`401`, `404`, `405`, `422`) rispondono con il formato strutturato `{code, message, params}` ([API-16](api.md#contratto-degli-errori), [ADR-47](adr/ADR-47-l-api-parla-sola-lingua-risponde.md)).
|
||||
|
||||
*Codice:* `app/api/routes/admin.py:require_admin` · `app/api/routes/rounds.py:round_stream`
|
||||
- ~~[LIM-23]~~ Rimosso: chiuso da B-94. `GET /admin/users` è paginato ([API-33](api.md)).
|
||||
|
||||
- **[LIM-23]** **`GET /admin/users` non ha limite né paginazione**, a
|
||||
differenza degli altri elenchi admin.
|
||||
- *Impatto:* con molti utenti la risposta cresce senza limite.
|
||||
- *Stato:* da chiudere con B-94 (paginazione dell'elenco utenti).
|
||||
|
||||
*Codice:* `app/api/routes/admin.py:list_users`
|
||||
@@ -123,7 +123,6 @@ il freno sui tentativi di login falliti è
|
||||
prima di qualunque verifica.
|
||||
*Codice:* `app/auth/routes.py:login`, `app/auth/routes.py:LoginRequest` ·
|
||||
*Test:* `tests/unit/test_auth.py`
|
||||
*Stato:* da implementare (B-94), per la lunghezza massima · *Oggi:* i campi
|
||||
del login non hanno limiti di lunghezza.
|
||||
- ~~[REG-10]~~ Assorbito da [SEC-30](sicurezza.md#limiti-di-frequenza):
|
||||
il freno sui login falliti (chiavi, soglie, azzeramento) non ha nulla di
|
||||
@@ -146,7 +145,6 @@ il freno sui tentativi di login falliti è
|
||||
e [api.md](api.md) vi rimandano.
|
||||
*Codice:* `app/api/routes/users.py:change_password` ·
|
||||
*Test:* `tests/unit/test_users.py`
|
||||
*Stato:* da implementare (B-94), per lunghezza massima, freno ed evento
|
||||
`password_changed` · *Oggi:* nessun limite di lunghezza, nessun freno sui
|
||||
tentativi, nessun evento di audit.
|
||||
- **[REG-13]** Non esiste recupero password: non viene raccolta alcuna email.
|
||||
|
||||
+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.
|
||||
|
||||
|
||||
+4
-4
@@ -242,7 +242,7 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
|
||||
| B-91 | Container eseguito come root, permessi larghi, dipendenze e immagini non fissate | Alta | *da registrare* | `test_container_hardening.py`, `test_config.py` | SEC, OPS |
|
||||
| B-92 | Nessuna verifica di salute o prontezza, stato Electrum invisibile, audit non filtrabile, log incompleti | Media | **aperto** | — | OPS, AUD, API |
|
||||
| B-93 | Deploy senza arresto ordinato, rollback impossibile, `SITE_ADDRESS` che torna a `localhost` | Media | **aperto** | — | OPS |
|
||||
| B-94 | Cambio password, login e bug report senza limiti adeguati; errori non strutturati in alcune risposte | Media | **aperto** | — | SEC, API |
|
||||
| B-94 | Cambio password, login e bug report senza limiti adeguati; errori non strutturati in alcune risposte | Media | *da registrare* | `test_abuse_limits.py` | SEC, API |
|
||||
| B-95 | Costo della scommessa e `fee_address` letti dalla configurazione corrente invece che fotografati all'apertura | Media | *da registrare* | `test_round_snapshot.py`, `test_admin.py`, `test_rounds_route.py`, `test_scheduler.py` | RND, BET |
|
||||
| B-96 | Incoerenze minori di stato (prelievo ridotto sotto il minimo, importi di un pagamento abbandonato, audit mancanti, attesa in `closing` senza segnali, …) | Media | **aperto** | — | TX, BET, WDR, DEP |
|
||||
| B-97 | Le migrazioni possono eliminare in silenzio gli indici su espressione | Media | *da registrare* | `test_migrations.py` | DAT |
|
||||
@@ -254,8 +254,8 @@ cifrava di nuovo il file sbagliato (vedi [operativita.md](operativita.md)).
|
||||
|
||||
- **Corretti:** B-01 … B-72, tranne B-16, che è chiuso nella sostanza (il
|
||||
crash è eliminato e `/guida` serve un segnaposto) ma lascia la guida vera
|
||||
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77, B-78, B-83, B-74, B-85, B-86, B-73, B-81, B-79, B-82, B-87, B-89, B-90, B-91, B-95, B-98, B-99, B-100.
|
||||
- **Aperti:** dalla revisione del 2026-09-28, B-92, B-93, B-94 e B-96 (0 critici,
|
||||
0 alti, 4 medi), da implementare nell'ordine di
|
||||
come limite accettato; dalla revisione del 2026-09-28: B-88, B-75, B-97, B-80, B-84, B-76, B-77, B-78, B-83, B-74, B-85, B-86, B-73, B-81, B-79, B-82, B-87, B-89, B-90, B-91, B-94, B-95, B-98, B-99, B-100.
|
||||
- **Aperti:** dalla revisione del 2026-09-28, B-92, B-93 e B-96 (0 critici,
|
||||
0 alti, 3 medi), da implementare nell'ordine di
|
||||
[piano-correzioni.md](piano-correzioni.md).
|
||||
- **Prossimo numero libero:** `B-101`.
|
||||
Reference in new issue
Block a user