Validate admin config and registration input, and log config changes

fee_address was the dangerous one (B-05). PUT /admin/config assigned whatever it
was given, and a well-formed address from another chain (bc1...) parses fine as a
witness program — so every round's 30% commission would be signed and broadcast
to a script nobody holds the key for. A malformed one instead wedged the payout
with an unhandled EmbitError. It now has to pass is_valid_plm_address, the same
check user withdrawals already had. Numeric fields got bounds too:
fee_rate_sat_vb=0 produces transactions no node relays, which stalls bets,
payouts and withdrawals alike, and round_duration_seconds=0 expires a round the
instant it opens.

Config changes are audit-logged (B-10). /pause and /resume were logged but a
config edit wasn't, so the most sensitive setting in the system could be changed
without leaving any trace — contradicting CLAUDE.md, which says audit_log records
what changed. The entry carries a before/after diff per field, computed before
assignment, and no-op updates write nothing. `paused` was removed from
_CONFIG_FIELDS so the maintenance switch has exactly one audited path; it stays
in the response model.

Admin token comparison is constant-time (B-14), with the empty-token check kept
*ahead* of it: compare_digest("", "") returns True, so the obvious ordering would
have opened the panel on any instance without an ADMIN_TOKEN.

Registration input (B-12). It accepted an empty username and a one-character
password while /users/me/change-password demanded 8 — an odd place to be lenient
on a custodial system holding real funds. MIN_PASSWORD_LENGTH moved to
auth/security.py so both share it, and the username is constrained to 3-32 chars
of [A-Za-z0-9_.-]. The IntegrityError handler also distinguishes a username
collision (answers username_taken) from a derivation-index one (retries): a
concurrent duplicate username used to be retried five times and then reported as
derivation_index_conflict, which told the user the wrong thing.

verify_password (B-13) catches VerificationError and InvalidHashError, not just
VerifyMismatchError, so an unparseable stored hash reads as "wrong password"
instead of a 500 — logged as an error, since that one is a data problem.

guida-admin.md gains a table of the audit events worth watching, including
payout_failed, which needs manual intervention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:32:48 +02:00
co-authored by Claude Opus 5
parent daf66fd6bc
commit 85dce221c5
8 changed files with 229 additions and 30 deletions
+22
View File
@@ -108,6 +108,16 @@ piazzate, payout inviati, round chiusi, configurazione modificata, accessi
alle chiavi private, ecc. È il primo posto da controllare per ricostruire
cosa è successo dopo un problema.
Eventi a cui vale la pena prestare attenzione:
| Evento | Significato |
|---|---|
| `config_updated` | Un parametro è stato modificato; il payload contiene valore precedente e nuovo per ogni campo cambiato. |
| `bet_broadcast_failed` / `withdrawal_broadcast_failed` | La rete ha rifiutato la transazione. Non è stato speso nulla: gli UTXO sono stati liberati e il saldo dell'utente è tornato come prima. |
| `pending_tx_abandoned` | Una transazione trasmessa è scomparsa dalla catena e il sistema l'ha dichiarata persa: UTXO liberati, bet rimossa o prelievo segnato `failed`. Se capita spesso, la fee rate configurata è probabilmente troppo bassa. |
| `pending_tx_recovered` | Una transazione che si credeva incompleta è invece finita in catena (tipicamente dopo un riavvio a metà invio) e il sistema l'ha ripresa da sé. |
| `payout_failed` | Il payout di un round non è partito. Il round resta in `paying_out` e **richiede intervento manuale**: non esiste un retry automatico. Controlla `fee_address`, il saldo dell'indirizzo pool e la connessione Electrum. |
## Alternative all'interfaccia grafica
Le stesse operazioni si possono fare da terminale o da Swagger UI
@@ -125,6 +135,18 @@ curl -X PUT https://<host>/admin/config \
-d '{"fee_address": "plm1q...", "bet_amount_sats": 1000000000, "round_duration_seconds": 600}'
```
I valori vengono validati: `fee_address` deve essere un indirizzo bech32 PLM
valido (`plm1...`) e i parametri numerici hanno limiti di buon senso
(`fee_rate_sat_vb` almeno 1, durata round almeno 30s, ecc.). Un valore fuori
range viene rifiutato con un errore 422 e la configurazione resta invariata.
Il controllo su `fee_address` è deliberatamente severo: un indirizzo di
un'altra catena (per esempio `bc1...`) sarebbe formalmente valido come witness
program, e il 30% di commissione di ogni round finirebbe su uno script di cui
nessuno ha la chiave.
```bash
```
## Utenti, chiave privata e reset password
La card "Utenti" elenca id, username, indirizzo e saldo di ogni utente