feat: aggiornamenti alla documentazione e miglioramenti UI/UX

This commit is contained in:
2026-01-20 00:24:16 +01:00
parent b467d4753d
commit 7895bde7ca
12 changed files with 403 additions and 81 deletions

164
NETWORK_ACCESS.md Normal file
View File

@@ -0,0 +1,164 @@
# Accesso alla Rete Locale
## Come accedere al server da altri dispositivi (tablet, smartphone, ecc.)
Il sistema Focolari Voting è progettato per funzionare su tablet Android collegati alla stessa rete locale del server.
### 1. Avvio del Server
Il server è già configurato per accettare connessioni da tutta la rete locale:
```bash
./dev.sh server
```
Il server si avvia su `0.0.0.0:8000`, il che significa che è in ascolto su **tutte le interfacce di rete**.
### 2. Trova l'indirizzo IP del server
Sul computer che esegue il server, trova il suo indirizzo IP locale:
```bash
# Linux
ip addr show | grep "inet " | grep -v 127.0.0.1
# Oppure
hostname -I
```
L'output sarà qualcosa come: `192.168.1.230` (o simile, dipende dalla tua rete).
### 3. Accedi dal tablet/dispositivo
Sul tablet o altro dispositivo connesso alla **stessa rete WiFi**:
1. Apri il browser (Chrome, Firefox, ecc.)
2. Vai all'indirizzo: `http://192.168.1.230:8000`
- Sostituisci `192.168.1.230` con l'IP che hai trovato nel passo 2
- Mantieni la porta `:8000` (o quella che hai specificato con `-p`)
### 4. Verifica della connettività
#### Test ping dal tablet
Se il tablet non si connette, verifica prima che possa "vedere" il server:
```bash
# Dal computer server
ping 192.168.1.xxx # IP del tablet
```
Se il ping funziona in entrambe le direzioni, il problema è probabilmente il firewall.
#### Firewall Linux
Se usi un firewall (ufw, firewalld, iptables), devi aprire la porta:
**ufw:**
```bash
sudo ufw allow 8000/tcp
sudo ufw reload
```
**firewalld:**
```bash
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --reload
```
#### Verifica porte in ascolto
Per verificare che il server sia effettivamente in ascolto:
```bash
sudo ss -tlnp | grep :8000
# o
sudo netstat -tlnp | grep :8000
```
Dovresti vedere qualcosa come:
```
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 12345/python
```
**Nota importante:** Se vedi `127.0.0.1:8000` invece di `0.0.0.0:8000`, significa che il server è in ascolto solo su
localhost. In questo caso, riavvia senza il parametro `--host 127.0.0.1`.
### 5. Porta personalizzata
Se la porta 8000 è già in uso o vuoi usarne un'altra:
```bash
./dev.sh server -p 9000
```
Poi accedi da: `http://192.168.1.230:9000`
### 6. Ambiente di produzione
Per un deployment permanente su un tablet/kiosk:
1. Il tablet deve avere un IP fisso (configurato nel router o nelle impostazioni WiFi)
2. Il server deve avviarsi automaticamente (systemd service o simili)
3. Il browser del tablet può essere configurato in modalità kiosk per avviare automaticamente l'URL
## Risoluzione problemi comuni
### Problema: "Impossibile raggiungere il sito"
**Cause possibili:**
1. ❌ Il server non è in esecuzione → verifica con `ps aux | grep python`
2. ❌ Firewall attivo → segui le istruzioni sopra per aprire la porta
3. ❌ IP sbagliato → ricontrolla l'IP del server con `hostname -I`
4. ❌ Tablet non sulla stessa rete → connetti alla stessa WiFi del server
5. ❌ Server in ascolto solo su localhost → NON usare `--host 127.0.0.1`
### Problema: "Server non raggiungibile" (pagina di errore del frontend)
Il frontend ha una pagina di errore che effettua un ping automatico. Se vedi questa pagina:
1. Il frontend è caricato correttamente
2. Ma l'API backend non risponde
3. Verifica che il backend sia effettivamente in esecuzione
4. Controlla la console JavaScript per errori CORS
### Problema: CORS errors nella console
Non dovrebbero verificarsi, il backend ha CORS abilitato per `*`. Se li vedi:
1. Verifica che il backend sia avviato correttamente
2. Controlla che non ci siano proxy/firewall intermedi che modificano le richieste
## Note per deployment reale
- Il sistema è stato testato su **rete locale privata**
- Non esporre direttamente il server su Internet senza autenticazione aggiuntiva
- In produzione, considera di usare HTTPS con un reverse proxy (nginx/caddy)
- Il default `0.0.0.0` è sicuro in una rete locale controllata, ma valuta restrizioni aggiuntive per deployment più
grandi
## Comandi rapidi
```bash
# Trova IP del server
hostname -I
# Avvia server (accessibile da rete)
./dev.sh server
# Avvia server su porta custom
./dev.sh server -p 9000
# Avvia server SOLO su localhost (NON accessibile da rete)
./dev.sh server --host 127.0.0.1
# Verifica porte in ascolto
sudo ss -tlnp | grep python
# Apri porta nel firewall (ufw)
sudo ufw allow 8000/tcp
```

View File

@@ -1,6 +1,7 @@
# ✅ Checklist Test Manuali - Focolari Voting System # ✅ Checklist Test Manuali - Focolari Voting System
## Pre-requisiti ## Pre-requisiti
- [ ] Backend avviato con `./dev.sh server` - [ ] Backend avviato con `./dev.sh server`
- [ ] Browser aperto su `http://localhost:8000` - [ ] Browser aperto su `http://localhost:8000`
- [ ] Lettore RFID collegato (o usa tastiera per simulare) - [ ] Lettore RFID collegato (o usa tastiera per simulare)
@@ -10,16 +11,19 @@
## 🔐 Test Login Validatore ## 🔐 Test Login Validatore
### T1: Login con password corretta ### T1: Login con password corretta
1. Passa un badge qualsiasi (es. `ò0008988288_`) 1. Passa un badge qualsiasi (es. `ò0008988288_`)
2. Inserisci password: `focolari` 2. Inserisci password: `focolari`
3. **Atteso:** Accesso al varco attivo ✅ 3. **Atteso:** Accesso al varco attivo ✅
### T2: Login con password errata ### T2: Login con password errata
1. Passa un badge qualsiasi 1. Passa un badge qualsiasi
2. Inserisci password sbagliata 2. Inserisci password sbagliata
3. **Atteso:** Messaggio errore rosso, rimane su schermata password ❌ 3. **Atteso:** Messaggio errore rosso, rimane su schermata password ❌
### T3: Annulla login ### T3: Annulla login
1. Passa un badge 1. Passa un badge
2. Clicca "Annulla" 2. Clicca "Annulla"
3. **Atteso:** Torna a "Passa badge validatore" ✅ 3. **Atteso:** Torna a "Passa badge validatore" ✅
@@ -29,24 +33,29 @@
## 👤 Test Anagrafica Utenti ## 👤 Test Anagrafica Utenti
### T4: Badge ammesso trovato ### T4: Badge ammesso trovato
1. Login come validatore 1. Login come validatore
2. Passa badge `ò0008988288_` (Marco Bianchi) 2. Passa badge `ò0008988288_` (Marco Bianchi)
3. **Atteso:** Card verde con "Utente ammesso all'ingresso" ✅ 3. **Atteso:** Card verde con "Utente ammesso all'ingresso" ✅
### T5: Badge NON ammesso trovato ### T5: Badge NON ammesso trovato
1. Passa badge `ò0000514162_` (Giuseppe Verdi) 1. Passa badge `ò0000514162_` (Giuseppe Verdi)
2. **Atteso:** Card rossa lampeggiante "ACCESSO NON CONSENTITO" ⚠️ 2. **Atteso:** Card rossa lampeggiante "ACCESSO NON CONSENTITO" ⚠️
### T6: Badge non esistente nel DB ### T6: Badge non esistente nel DB
1. Passa badge `ò0006478281_` 1. Passa badge `ò0006478281_`
2. **Atteso:** Schermata errore con badge in grassetto, countdown 30s ❌ 2. **Atteso:** Schermata errore con badge in grassetto, countdown 30s ❌
### T7: Stesso badge passato due volte ### T7: Stesso badge passato due volte
1. Visualizza un utente (es. Marco Bianchi) 1. Visualizza un utente (es. Marco Bianchi)
2. Passa lo STESSO badge di nuovo 2. Passa lo STESSO badge di nuovo
3. **Atteso:** Nessun ricaricamento, utente rimane visualizzato ✅ 3. **Atteso:** Nessun ricaricamento, utente rimane visualizzato ✅
### T8: Badge diverso sostituisce utente corrente ### T8: Badge diverso sostituisce utente corrente
1. Visualizza Marco Bianchi (`0008988288`) 1. Visualizza Marco Bianchi (`0008988288`)
2. Passa Laura Rossi (`ò0007399575_`) 2. Passa Laura Rossi (`ò0007399575_`)
3. **Atteso:** Card cambia mostrando Laura Rossi ✅ 3. **Atteso:** Card cambia mostrando Laura Rossi ✅
@@ -56,16 +65,19 @@
## ✓ Test Conferma Ingresso ## ✓ Test Conferma Ingresso
### T9: Conferma utente ammesso ### T9: Conferma utente ammesso
1. Visualizza Marco Bianchi (ammesso) 1. Visualizza Marco Bianchi (ammesso)
2. Passa badge VALIDATORE (quello usato per login) 2. Passa badge VALIDATORE (quello usato per login)
3. **Atteso:** Modal verde con carosello "Benvenuto/Welcome/..." per 8 secondi ✅ 3. **Atteso:** Modal verde con carosello "Benvenuto/Welcome/..." per 8 secondi ✅
### T10: Badge validatore su utente NON ammesso ### T10: Badge validatore su utente NON ammesso
1. Visualizza Giuseppe Verdi (non ammesso) 1. Visualizza Giuseppe Verdi (non ammesso)
2. Passa badge validatore 2. Passa badge validatore
3. **Atteso:** Banner arancione "Badge validatore rilevato, se cambiato fare logout" ⚠️ 3. **Atteso:** Banner arancione "Badge validatore rilevato, se cambiato fare logout" ⚠️
### T11: Badge validatore senza utente visualizzato ### T11: Badge validatore senza utente visualizzato
1. Torna alla schermata "In attesa partecipante" 1. Torna alla schermata "In attesa partecipante"
2. Passa badge validatore 2. Passa badge validatore
3. **Atteso:** Banner arancione come sopra ⚠️ 3. **Atteso:** Banner arancione come sopra ⚠️
@@ -75,15 +87,18 @@
## 🎠 Test Success Modal ## 🎠 Test Success Modal
### T12: Carosello scorre tutte le lingue ### T12: Carosello scorre tutte le lingue
1. Conferma ingresso di un utente ammesso 1. Conferma ingresso di un utente ammesso
2. Osserva il carosello 2. Osserva il carosello
3. **Atteso:** Scorre IT→EN→FR→DE→ES→PT→ZH→JA→AR→RU con animazione smooth ✅ 3. **Atteso:** Scorre IT→EN→FR→DE→ES→PT→ZH→JA→AR→RU con animazione smooth ✅
### T13: Badge durante carosello chiude modal ### T13: Badge durante carosello chiude modal
1. Durante il carosello, passa un NUOVO badge 1. Durante il carosello, passa un NUOVO badge
2. **Atteso:** Modal si chiude immediatamente, carica nuovo utente ✅ 2. **Atteso:** Modal si chiude immediatamente, carica nuovo utente ✅
### T14: Carosello non troppo stretto ### T14: Carosello non troppo stretto
1. Osserva le scritte durante il carosello 1. Osserva le scritte durante il carosello
2. **Atteso:** "Добро пожаловать!" (russo) deve essere visibile per intero ✅ 2. **Atteso:** "Добро пожаловать!" (russo) deve essere visibile per intero ✅
@@ -92,20 +107,36 @@
## ⏱️ Test Timeout e Sessione ## ⏱️ Test Timeout e Sessione
### T15: Timeout utente (60s) ### T15: Timeout utente (60s)
1. Visualizza un utente 1. Visualizza un utente
2. Aspetta 60 secondi 2. Aspetta 60 secondi
3. **Atteso:** Torna automaticamente a "In attesa partecipante" ✅ 3. **Atteso:** Torna automaticamente a "In attesa partecipante" ✅
### T16: Timeout badge non trovato (30s) ### T16: Timeout badge non trovato (30s)
1. Passa badge inesistente (`0006478281`) 1. Passa badge inesistente (`0006478281`)
2. Aspetta 30 secondi 2. Aspetta 30 secondi
3. **Atteso:** Torna a "In attesa partecipante", barra countdown visiva ✅ 3. **Atteso:** Torna a "In attesa partecipante", barra countdown visiva ✅
### T16b: Pulsante Annulla su badge non trovato
1. Passa badge inesistente (`0006478281`)
2. Clicca "Annulla"
3. **Atteso:** Torna immediatamente a "In attesa partecipante" ✅
### T16c: Nuovo badge durante errore "non trovato"
1. Passa badge inesistente (`0006478281`)
2. Mentre è visibile l'errore, passa badge esistente (`ò0008988288_`)
3. **Atteso:** Errore sparisce, mostra anagrafica Marco Bianchi ✅
### T17: Logout manuale ### T17: Logout manuale
1. Clicca "Esci" nell'header 1. Clicca "Esci" nell'header
2. **Atteso:** Torna a "Passa badge validatore" ✅ 2. **Atteso:** Torna a "Passa badge validatore" ✅
### T18: Sessione invalidata al riavvio server ### T18: Sessione invalidata al riavvio server
1. Login come validatore, vai al varco attivo 1. Login come validatore, vai al varco attivo
2. **Ferma il server** (Ctrl+C) 2. **Ferma il server** (Ctrl+C)
3. **Riavvia il server** (`./dev.sh server`) 3. **Riavvia il server** (`./dev.sh server`)
@@ -117,10 +148,12 @@
## 🔧 Test Debug ## 🔧 Test Debug
### T19: Pagina debug accessibile ### T19: Pagina debug accessibile
1. Vai a `http://localhost:8000/debug` 1. Vai a `http://localhost:8000/debug`
2. **Atteso:** Pagina con log tasti, stato scanner, buffer corrente ✅ 2. **Atteso:** Pagina con log tasti, stato scanner, buffer corrente ✅
### T20: Log tasti funzionante ### T20: Log tasti funzionante
1. Nella pagina debug, premi tasti sulla tastiera 1. Nella pagina debug, premi tasti sulla tastiera
2. **Atteso:** Tasti appaiono nella lista eventi ✅ 2. **Atteso:** Tasti appaiono nella lista eventi ✅
@@ -129,10 +162,12 @@
## 📱 Test UI/UX ## 📱 Test UI/UX
### T21: NumLock banner su desktop ### T21: NumLock banner su desktop
1. Su browser desktop, verifica schermata "In attesa partecipante" 1. Su browser desktop, verifica schermata "In attesa partecipante"
2. **Atteso:** Banner giallo con stato NumLock visibile ✅ 2. **Atteso:** Banner giallo con stato NumLock visibile ✅
### T22: Occhio toggle password ### T22: Occhio toggle password
1. Nella schermata password validatore 1. Nella schermata password validatore
2. Clicca l'icona occhio 2. Clicca l'icona occhio
3. **Atteso:** Password visibile/nascosta ✅ 3. **Atteso:** Password visibile/nascosta ✅
@@ -141,18 +176,21 @@
## Badge di Test ## Badge di Test
| Formato RFID | Badge | Nome | Risultato Atteso | | Formato RFID | Badge | Nome | Ruolo | Risultato Atteso |
|----------------------|------------|----------------|----------------------| |----------------|------------|----------------|-----------|----------------------|
| `ò0008988288_` | 0008988288 | Marco Bianchi | ✅ Ammesso | | `ò0008988288_` | 0008988288 | Marco Bianchi | Convocato | ✅ Ammesso |
| `ò0007399575_` | 0007399575 | Laura Rossi | ✅ Ammessa | | `ò0007399575_` | 0007399575 | Laura Rossi | Invitato | ✅ Ammessa |
| `ò0000514162_` | 0000514162 | Giuseppe Verdi | ❌ Non ammesso | | `ò0000514162_` | 0000514162 | Giuseppe Verdi | Tecnico | ❌ Non ammesso |
| `ò0006478281_` | 0006478281 | - | ⚠️ Non trovato (404) | | `ò0006478281_` | 0006478281 | - | - | ⚠️ Non trovato (404) |
**Ruoli possibili:** Convocato, Invitato, Tecnico, Staff
**Password validatore:** `focolari` **Password validatore:** `focolari`
--- ---
## Note ## Note
- I badge sono stringhe, gli zeri iniziali sono significativi - I badge sono stringhe, gli zeri iniziali sono significativi
- Il pattern RFID italiano usa `ò` come start e `_` come end (+ Enter) - Il pattern RFID italiano usa `ò` come start e `_` come end (+ Enter)
- Il pattern US usa `;` come start e `?` come end (+ Enter) - Il pattern US usa `;` come start e `?` come end (+ Enter)

View File

@@ -147,7 +147,8 @@ VotoFocolari/
5. **Badge Duplicati**: Se lo stesso badge viene passato più volte di seguito, viene ignorato (no ricaricamento). 5. **Badge Duplicati**: Se lo stesso badge viene passato più volte di seguito, viene ignorato (no ricaricamento).
6. **Success Modal Interrompibile**: Se durante il carosello di benvenuto si passa un nuovo badge, la modal si chiude e viene caricato subito il nuovo utente. 6. **Success Modal Interrompibile**: Se durante il carosello di benvenuto si passa un nuovo badge, la modal si chiude e
viene caricato subito il nuovo utente.
--- ---

View File

@@ -50,8 +50,8 @@ backend-mock/
- [x] Password validatore (solo password, badge gestito dal frontend) - [x] Password validatore (solo password, badge gestito dal frontend)
- [x] Lista utenti mock con **badge reali**: - [x] Lista utenti mock con **badge reali**:
- `0008988288` - Marco Bianchi (Votante, ammesso) - `0008988288` - Marco Bianchi (Convocato, ammesso)
- `0007399575` - Laura Rossi (Votante, ammessa) - `0007399575` - Laura Rossi (Invitato, ammessa)
- `0000514162` - Giuseppe Verdi (Tecnico, NON ammesso) - `0000514162` - Giuseppe Verdi (Tecnico, NON ammesso)
- `0006478281` - **NON nel DB** (per test "non trovato") - `0006478281` - **NON nel DB** (per test "non trovato")
- [x] Estrarre dati in file JSON separato - [x] Estrarre dati in file JSON separato
@@ -60,9 +60,20 @@ backend-mock/
**Nota:** I messaggi di benvenuto multilingua sono gestiti dal frontend con carosello. **Nota:** I messaggi di benvenuto multilingua sono gestiti dal frontend con carosello.
**TODO (da concordare con committenti):** **Ruoli Supportati:**
| Ruolo | Descrizione |
|-------|-------------|
| `Convocato` | Con diritto di voto |
| `Invitato` | Senza diritto di voto |
| `Tecnico` | Staff tecnico |
| `Staff` | Personale organizzativo |
- Valutare se `login-validate` debba ricevere e verificare anche il badge del validatore **Logica Login-Validate:**
- Il backend **DEVE** verificare la password
- Il backend **PUÒ** opzionalmente verificare anche il badge (whitelist validatori)
- Risposta 401 = password errata
- Risposta 403 = badge non in whitelist (se implementato)
### 4. Routes API (`api/routes.py`) ### 4. Routes API (`api/routes.py`)
@@ -175,10 +186,12 @@ curl -X POST http://localhost:8000/entry-request \
Tutti i task sono stati implementati e testati. Tutti i task sono stati implementati e testati.
### 📄 Documentazione API ### 📄 Documentazione API
Per le specifiche complete da implementare nel backend reale, vedere: Per le specifiche complete da implementare nel backend reale, vedere:
**`backend-mock/API_SPECIFICATION.md`** **`backend-mock/API_SPECIFICATION.md`**
Questo documento contiene: Questo documento contiene:
- Descrizione completa di tutti gli endpoint - Descrizione completa di tutti gli endpoint
- Schema request/response JSON - Schema request/response JSON
- Codici di errore e gestione - Codici di errore e gestione

View File

@@ -81,9 +81,14 @@ Ottimizzata per tablet in orizzontale.
- [x] `ActiveGateScreen.tsx` - varco attivo: - [x] `ActiveGateScreen.tsx` - varco attivo:
- [x] Card utente (layout largo per tablet) - [x] Card utente (layout largo per tablet)
- [x] **Schermata "badge non trovato"** con countdown barra visiva (30s) - [x] **Schermata "badge non trovato"** con countdown barra visiva (30s)
- [x] **Pulsante "Annulla" nella schermata badge non trovato**
- [x] **Badge diverso durante errore "non trovato" → ricarica nuovo utente/errore**
- [x] **Notifica badge validatore ignorato** - [x] **Notifica badge validatore ignorato**
- [x] NumLockBanner - [x] NumLockBanner
- [x] `SuccessModal.tsx` - conferma ingresso con carosello (**durata aumentata 8s**) - [x] `SuccessModal.tsx` - conferma ingresso con carosello:
- [x] **Carosello fullwidth** (nessun troncamento testo)
- [x] **Scorrimento lento** (più rilassato, meno ansioso)
- [x] **Durata aumentata 8s**
- [x] `ErrorModal.tsx` - errore fullscreen - [x] `ErrorModal.tsx` - errore fullscreen
- [x] `DebugScreen.tsx` - pagina diagnostica RFID - [x] `DebugScreen.tsx` - pagina diagnostica RFID
@@ -111,9 +116,10 @@ Ottimizzata per tablet in orizzontale.
- [x] Componente `WelcomeCarousel.tsx` - [x] Componente `WelcomeCarousel.tsx`
- [x] 10 lingue supportate - [x] 10 lingue supportate
- [x] **Animazione smooth sliding** (slide up/down) - [x] **Animazione smooth sliding** (slide up/down)
- [x] Scorrimento automatico (intervallo calcolato dinamicamente) - [x] Scorrimento automatico lento (più rilassato)
- [x] Modale fullscreen verde - [x] Modale fullscreen verde
- [x] **Durata totale: 8 secondi** (più rilassato) - [x] **Contenitore fullwidth** (nessun troncamento testo lungo)
- [x] **Durata totale: 8 secondi**
### 10. Debug & Diagnostica ### 10. Debug & Diagnostica
@@ -131,9 +137,9 @@ Ottimizzata per tablet in orizzontale.
## Badge di Test ## Badge di Test
| Badge | Nome | Ruolo | Ammesso | | Badge | Nome | Ruolo | Ammesso |
|--------------|----------------|---------|---------------| |--------------|----------------|-----------|---------------|
| `0008988288` | Marco Bianchi | Votante | ✅ Sì | | `0008988288` | Marco Bianchi | Convocato | ✅ Sì |
| `0007399575` | Laura Rossi | Votante | ✅ Sì | | `0007399575` | Laura Rossi | Invitato | ✅ Sì |
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No | | `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
| `0006478281` | - | - | ⚠️ Non nel DB | | `0006478281` | - | - | ⚠️ Non nel DB |

View File

@@ -1,6 +1,7 @@
# 📄 Specifiche API Backend - Focolari Voting System # 📄 Specifiche API Backend - Focolari Voting System
Questo documento descrive le specifiche che il backend reale deve implementare per funzionare correttamente con il frontend del sistema di controllo accessi. Questo documento descrive le specifiche che il backend reale deve implementare per funzionare correttamente con il
frontend del sistema di controllo accessi.
**Versione:** 1.0 **Versione:** 1.0
**Data:** Gennaio 2026 **Data:** Gennaio 2026
@@ -10,12 +11,15 @@ Questo documento descrive le specifiche che il backend reale deve implementare p
## 🌐 Configurazione Server ## 🌐 Configurazione Server
### CORS ### CORS
Il server deve abilitare CORS con le seguenti impostazioni: Il server deve abilitare CORS con le seguenti impostazioni:
- **Origins:** `*` (o lista specifica di domini autorizzati) - **Origins:** `*` (o lista specifica di domini autorizzati)
- **Methods:** `GET, POST, OPTIONS` - **Methods:** `GET, POST, OPTIONS`
- **Headers:** `Content-Type, Authorization` - **Headers:** `Content-Type, Authorization`
### Serving Frontend ### Serving Frontend
Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`. Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`.
Questo permette di avere un unico endpoint per frontend e API. Questo permette di avere un unico endpoint per frontend e API.
@@ -24,15 +28,18 @@ Questo permette di avere un unico endpoint per frontend e API.
## 🔑 Meccanismo di Invalidazione Sessioni ## 🔑 Meccanismo di Invalidazione Sessioni
### Server Start Time ### Server Start Time
All'avvio, il server deve generare un **timestamp univoco** (es. Unix timestamp in secondi). All'avvio, il server deve generare un **timestamp univoco** (es. Unix timestamp in secondi).
Questo valore viene restituito nell'endpoint `/info-room` e serve al frontend per invalidare sessioni obsolete. Questo valore viene restituito nell'endpoint `/info-room` e serve al frontend per invalidare sessioni obsolete.
**Comportamento:** **Comportamento:**
1. Il frontend salva `server_start_time` nella sessione locale 1. Il frontend salva `server_start_time` nella sessione locale
2. Al caricamento successivo, confronta il valore salvato con quello attuale 2. Al caricamento successivo, confronta il valore salvato con quello attuale
3. Se differiscono, la sessione viene invalidata (il server è stato riavviato) 3. Se differiscono, la sessione viene invalidata (il server è stato riavviato)
Questo garantisce che: Questo garantisce che:
- Un riavvio del server forza il re-login di tutti i validatori - Un riavvio del server forza il re-login di tutti i validatori
- Sessioni zombie non rimangono attive dopo manutenzione - Sessioni zombie non rimangono attive dopo manutenzione
@@ -45,6 +52,7 @@ Questo garantisce che:
Restituisce informazioni sulla sala/meeting corrente e lo stato del server. Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
#### Response `200 OK` #### Response `200 OK`
```json ```json
{ {
"room_name": "Sala Assemblea", "room_name": "Sala Assemblea",
@@ -54,7 +62,7 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
``` ```
| Campo | Tipo | Descrizione | | Campo | Tipo | Descrizione |
|-------|------|-------------| |---------------------|---------|---------------------------------------------------------------|
| `room_name` | string | Nome della sala visualizzato nell'header | | `room_name` | string | Nome della sala visualizzato nell'header |
| `meeting_id` | string | Identificativo del meeting/votazione | | `meeting_id` | string | Identificativo del meeting/votazione |
| `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) | | `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) |
@@ -63,9 +71,11 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
### 2. `POST /login-validate` ### 2. `POST /login-validate`
Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password è corretta. Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password
è corretta.
#### Request Body #### Request Body
```json ```json
{ {
"badge": "0007399575", "badge": "0007399575",
@@ -74,11 +84,19 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
``` ```
| Campo | Tipo | Descrizione | | Campo | Tipo | Descrizione |
|-------|------|-------------| |------------|--------|-------------------------------------------------------------------------------------|
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) | | `badge` | string | Codice badge scansionato (numerico, senza sentinel) - **opzionale per validazione** |
| `password` | string | Password inserita dall'utente | | `password` | string | Password inserita dall'utente |
**Logica di Validazione:**
- Il backend **DEVE** verificare la password
- Il backend **PUÒ** opzionalmente verificare anche il badge (lista whitelist di badge abilitati come validatori)
- Se verifica solo password: qualsiasi badge può diventare validatore con la password corretta
- Se verifica anche badge: solo badge nella whitelist possono diventare validatori
#### Response `200 OK` (Successo) #### Response `200 OK` (Successo)
```json ```json
{ {
"success": true, "success": true,
@@ -87,14 +105,26 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
} }
``` ```
#### Response `401 Unauthorized` (Errore) #### Response `401 Unauthorized` (Password errata)
```json ```json
{ {
"detail": "Password non valida" "detail": "Password non valida"
} }
``` ```
#### Response `403 Forbidden` (Badge non autorizzato - se implementato controllo badge)
```json
{
"detail": "Badge non autorizzato come validatore"
}
```
**Note:** Il frontend gestisce entrambi i codici di errore (401 e 403) mostrando il messaggio ricevuto nel campo
`detail`.
**Note:** **Note:**
- La password è l'unico fattore di autenticazione - La password è l'unico fattore di autenticazione
- Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive - Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive
- Il token è opzionale (per future implementazioni di autenticazione JWT) - Il token è opzionale (per future implementazioni di autenticazione JWT)
@@ -106,13 +136,23 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
Recupera i dati anagrafici di un utente dato il suo codice badge. Recupera i dati anagrafici di un utente dato il suo codice badge.
#### Path Parameters #### Path Parameters
- `badge_code`: Codice badge (stringa, es. "0008988288") - `badge_code`: Codice badge (stringa, es. "0008988288")
**IMPORTANTE - Confronto Badge:** **IMPORTANTE - Confronto Badge:**
Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere. Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere.
Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **diversi**. Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **diversi**.
**Ruoli Ammessi:**
| Ruolo | Descrizione |
|-------|-------------|
| `Convocato` | Con diritto di voto |
| `Invitato` | Senza diritto di voto |
| `Tecnico` | Staff tecnico |
| `Staff` | Personale organizzativo |
#### Response `200 OK` (Utente trovato e ammesso) #### Response `200 OK` (Utente trovato e ammesso)
```json ```json
{ {
"badge_code": "0008988288", "badge_code": "0008988288",
@@ -120,11 +160,18 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
"cognome": "Bianchi", "cognome": "Bianchi",
"url_foto": "https://example.com/foto.jpg", "url_foto": "https://example.com/foto.jpg",
"ruolo": "Votante", "ruolo": "Votante",
"ammesso": true # <-- utente ammesso all'ingresso "ammesso": true
#
<--
utente
ammesso
all
'ingresso
} }
``` ```
#### Response `200 OK` (Utente trovato ma NON ammesso) #### Response `200 OK` (Utente trovato ma NON ammesso)
```json ```json
{ {
"badge_code": "0000514162", "badge_code": "0000514162",
@@ -132,12 +179,20 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
"cognome": "Verdi", "cognome": "Verdi",
"url_foto": "https://example.com/foto.jpg", "url_foto": "https://example.com/foto.jpg",
"ruolo": "Tecnico", "ruolo": "Tecnico",
"ammesso": false, #<-- utente NON ammesso all'ingresso "ammesso": false,
#
<--
utente
NON
ammesso
all
'ingresso
"warning": "Utente non ammesso all'ingresso" "warning": "Utente non ammesso all'ingresso"
} }
``` ```
#### Response `404 Not Found` (Utente non trovato) #### Response `404 Not Found` (Utente non trovato)
```json ```json
{ {
"detail": "Badge non trovato nel sistema" "detail": "Badge non trovato nel sistema"
@@ -145,7 +200,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
``` ```
| Campo Response | Tipo | Descrizione | | Campo Response | Tipo | Descrizione |
|----------------|------|-------------| |----------------|---------|--------------------------------------------------------|
| `badge_code` | string | Codice badge | | `badge_code` | string | Codice badge |
| `nome` | string | Nome dell'utente | | `nome` | string | Nome dell'utente |
| `cognome` | string | Cognome dell'utente | | `cognome` | string | Cognome dell'utente |
@@ -161,6 +216,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
Registra l'ingresso di un utente. Richiede conferma del validatore. Registra l'ingresso di un utente. Richiede conferma del validatore.
#### Request Body #### Request Body
```json ```json
{ {
"user_badge": "0008988288", "user_badge": "0008988288",
@@ -169,11 +225,12 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
``` ```
| Campo | Tipo | Descrizione | | Campo | Tipo | Descrizione |
|-------|------|-------------| |----------------------|--------|---------------------------------------|
| `user_badge` | string | Badge dell'utente che sta entrando | | `user_badge` | string | Badge dell'utente che sta entrando |
| `validator_password` | string | Password del validatore (ri-verifica) | | `validator_password` | string | Password del validatore (ri-verifica) |
#### Response `200 OK` (Successo) #### Response `200 OK` (Successo)
```json ```json
{ {
"success": true, "success": true,
@@ -182,6 +239,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
``` ```
#### Response `401 Unauthorized` (Password errata) #### Response `401 Unauthorized` (Password errata)
```json ```json
{ {
"detail": "Password validatore non valida" "detail": "Password validatore non valida"
@@ -189,6 +247,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
``` ```
#### Response `403 Forbidden` (Utente non ammesso) #### Response `403 Forbidden` (Utente non ammesso)
```json ```json
{ {
"detail": "Utente non autorizzato all'ingresso" "detail": "Utente non autorizzato all'ingresso"
@@ -196,6 +255,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
``` ```
#### Response `404 Not Found` (Badge non trovato) #### Response `404 Not Found` (Badge non trovato)
```json ```json
{ {
"detail": "Badge utente non trovato" "detail": "Badge utente non trovato"
@@ -203,7 +263,9 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
``` ```
**IMPORTANTE - Sicurezza:** **IMPORTANTE - Sicurezza:**
Anche se il frontend non dovrebbe permettere di inviare entry-request per utenti non ammessi, il backend **DEVE** sempre verificare: Anche se il frontend non dovrebbe permettere di inviare entry-request per utenti non ammessi, il backend **DEVE** sempre
verificare:
1. Che la password validatore sia corretta 1. Che la password validatore sia corretta
2. Che l'utente esista 2. Che l'utente esista
3. Che l'utente sia ammesso (`ammesso: true`) 3. Che l'utente sia ammesso (`ammesso: true`)
@@ -215,6 +277,7 @@ Non fidarsi mai del frontend per la validazione!
## 🔒 Considerazioni di Sicurezza ## 🔒 Considerazioni di Sicurezza
### Validazione Lato Backend ### Validazione Lato Backend
Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend: Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend:
1. **Login:** Verificare che la password sia corretta 1. **Login:** Verificare che la password sia corretta
@@ -225,13 +288,17 @@ Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da c
- Loggare l'operazione per audit - Loggare l'operazione per audit
### Logging e Audit ### Logging e Audit
Si raccomanda di loggare: Si raccomanda di loggare:
- Ogni tentativo di login (successo/fallimento) - Ogni tentativo di login (successo/fallimento)
- Ogni registrazione di ingresso - Ogni registrazione di ingresso
- Badge non trovati (potenziale tentativo di accesso non autorizzato) - Badge non trovati (potenziale tentativo di accesso non autorizzato)
### Protezione Rate Limiting ### Protezione Rate Limiting
Implementare rate limiting su: Implementare rate limiting su:
- `/login-validate`: max 5 tentativi/minuto per IP - `/login-validate`: max 5 tentativi/minuto per IP
- `/entry-request`: max 30 richieste/minuto per IP - `/entry-request`: max 30 richieste/minuto per IP
@@ -240,8 +307,10 @@ Implementare rate limiting su:
## 📊 Struttura Dati Suggerita ## 📊 Struttura Dati Suggerita
### Database Utenti ### Database Utenti
```sql ```sql
CREATE TABLE users ( CREATE TABLE users
(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
badge_code VARCHAR(20) UNIQUE NOT NULL, badge_code VARCHAR(20) UNIQUE NOT NULL,
nome VARCHAR(100) NOT NULL, nome VARCHAR(100) NOT NULL,
@@ -253,12 +322,14 @@ CREATE TABLE users (
updated_at TIMESTAMP DEFAULT NOW() updated_at TIMESTAMP DEFAULT NOW()
); );
CREATE INDEX idx_badge_code ON users(badge_code); CREATE INDEX idx_badge_code ON users (badge_code);
``` ```
### Tabella Accessi (Audit Log) ### Tabella Accessi (Audit Log)
```sql ```sql
CREATE TABLE access_log ( CREATE TABLE access_log
(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
user_badge VARCHAR(20) NOT NULL, user_badge VARCHAR(20) NOT NULL,
validator_badge VARCHAR(20) NOT NULL, validator_badge VARCHAR(20) NOT NULL,
@@ -286,9 +357,10 @@ CREATE TABLE access_log (
9. **Entry badge inesistente** → 404 Not Found 9. **Entry badge inesistente** → 404 Not Found
### Badge di Test (Mock) ### Badge di Test (Mock)
``` ```
0008988288 - Marco Bianchi (Votante, ammesso) 0008988288 - Marco Bianchi (Convocato, ammesso)
0007399575 - Laura Rossi (Votante, ammessa) 0007399575 - Laura Rossi (Invitato, ammessa)
0000514162 - Giuseppe Verdi (Tecnico, NON ammesso) 0000514162 - Giuseppe Verdi (Tecnico, NON ammesso)
0006478281 - NON nel database (per test 404) 0006478281 - NON nel database (per test 404)
``` ```
@@ -298,9 +370,11 @@ CREATE TABLE access_log (
## 📝 Note Implementative ## 📝 Note Implementative
### Content-Type ### Content-Type
Tutte le richieste e risposte usano `application/json`. Tutte le richieste e risposte usano `application/json`.
### Codici Errore HTTP ### Codici Errore HTTP
- `200`: Successo - `200`: Successo
- `401`: Non autorizzato (password errata) - `401`: Non autorizzato (password errata)
- `403`: Vietato (utente non ammesso) - `403`: Vietato (utente non ammesso)
@@ -308,7 +382,9 @@ Tutte le richieste e risposte usano `application/json`.
- `500`: Errore interno server - `500`: Errore interno server
### Formato Errori ### Formato Errori
Tutti gli errori devono restituire un JSON con campo `detail`: Tutti gli errori devono restituire un JSON con campo `detail`:
```json ```json
{ {
"detail": "Messaggio di errore descrittivo" "detail": "Messaggio di errore descrittivo"
@@ -320,6 +396,7 @@ Tutti gli errori devono restituire un JSON con campo `detail`:
## 🔄 Changelog ## 🔄 Changelog
### v1.0 (Gennaio 2026) ### v1.0 (Gennaio 2026)
- Specifica iniziale - Specifica iniziale
- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request` - Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request`
- Meccanismo invalidazione sessioni con `server_start_time` - Meccanismo invalidazione sessioni con `server_start_time`

View File

@@ -5,12 +5,20 @@
"meeting_id": "VOT-2024" "meeting_id": "VOT-2024"
}, },
"users": [ "users": [
{
"badge_code": "0008988288",
"nome": "Marco",
"cognome": "Bianchi",
"url_foto": "https://randomuser.me/api/portraits/men/1.jpg",
"ruolo": "Convocato",
"ammesso": true
},
{ {
"badge_code": "0007399575", "badge_code": "0007399575",
"nome": "Laura", "nome": "Laura",
"cognome": "Rossi", "cognome": "Rossi",
"url_foto": "https://randomuser.me/api/portraits/women/2.jpg", "url_foto": "https://randomuser.me/api/portraits/women/2.jpg",
"ruolo": "Votante", "ruolo": "Invitato",
"ammesso": true "ammesso": true
}, },
{ {

View File

@@ -10,7 +10,7 @@
"nome": "Test", "nome": "Test",
"cognome": "Ammesso", "cognome": "Ammesso",
"url_foto": "https://randomuser.me/api/portraits/lego/1.jpg", "url_foto": "https://randomuser.me/api/portraits/lego/1.jpg",
"ruolo": "Votante", "ruolo": "Convocato",
"ammesso": true "ammesso": true
}, },
{ {
@@ -18,7 +18,7 @@
"nome": "Test", "nome": "Test",
"cognome": "NonAmmesso", "cognome": "NonAmmesso",
"url_foto": "https://randomuser.me/api/portraits/lego/2.jpg", "url_foto": "https://randomuser.me/api/portraits/lego/2.jpg",
"ruolo": "Ospite", "ruolo": "Invitato",
"ammesso": false "ammesso": false
}, },
{ {

8
dev.sh
View File

@@ -215,10 +215,16 @@ cmd_help() {
echo "Esempi:" echo "Esempi:"
echo " ./dev.sh install # Setup iniziale" echo " ./dev.sh install # Setup iniziale"
echo " ./dev.sh dev # Sviluppo (hot reload)" echo " ./dev.sh dev # Sviluppo (hot reload)"
echo " ./dev.sh server # Produzione locale" echo " ./dev.sh server # Produzione locale (accessibile da rete)"
echo " ./dev.sh server -p 9000 # Server su porta 9000" echo " ./dev.sh server -p 9000 # Server su porta 9000"
echo " ./dev.sh server --host 127.0.0.1 # Solo localhost (non accessibile da rete)"
echo " ./dev.sh server -d data/users_test.json # Con dataset test" echo " ./dev.sh server -d data/users_test.json # Con dataset test"
echo "" echo ""
echo "Note:"
echo " - Il server usa --host 0.0.0.0 di default (accessibile da tutta la rete locale)"
echo " - Per accedere da altri dispositivi: usa l'IP della macchina (es. 192.168.1.230:8000)"
echo " - Verifica che il firewall non blocchi la porta"
echo ""
} }
# Main # Main

View File

@@ -30,11 +30,11 @@ interface WelcomeCarouselProps {
paused?: boolean; paused?: boolean;
/** Nome dell'utente da visualizzare (opzionale) */ /** Nome dell'utente da visualizzare (opzionale) */
userName?: string; userName?: string;
/** Intervallo tra i messaggi in ms (default 800) */ /** Intervallo tra i messaggi in ms (default 1200 - più lento per effetto rilassante) */
intervalMs?: number; intervalMs?: number;
} }
export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: WelcomeCarouselProps) { export function WelcomeCarousel({paused = false, userName, intervalMs = 1200}: WelcomeCarouselProps) {
const [currentIndex, setCurrentIndex] = useState(0); const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => { useEffect(() => {
@@ -50,15 +50,15 @@ export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: We
const currentMessage = WELCOME_MESSAGES[currentIndex]; const currentMessage = WELCOME_MESSAGES[currentIndex];
return ( return (
<div className="flex flex-col items-center justify-center text-center w-full max-w-4xl mx-auto px-4"> <div className="flex flex-col items-center justify-center text-center w-full px-4">
{/* Container con overflow hidden per animazione slide - altezza maggiore per testi lunghi */} {/* Container fullwidth con overflow hidden per animazione slide */}
<div className="relative h-40 md:h-52 w-full overflow-hidden"> <div className="relative h-44 md:h-56 w-full overflow-hidden">
{/* Messaggio corrente */} {/* Messaggio corrente */}
<div <div
key={currentIndex} key={currentIndex}
className="absolute inset-0 flex flex-col items-center justify-center animate-slide-in-up" className="absolute inset-0 flex flex-col items-center justify-center animate-slide-in-up"
> >
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold text-white mb-2 drop-shadow-lg whitespace-nowrap"> <h1 className="text-5xl md:text-7xl lg:text-8xl font-bold text-white mb-2 drop-shadow-lg">
{currentMessage.text} {currentMessage.text}
</h1> </h1>
<p className="text-lg text-white/60 uppercase tracking-wider"> <p className="text-lg text-white/60 uppercase tracking-wider">

View File

@@ -130,7 +130,16 @@ export function ActiveGateScreen({
</div> </div>
<p className="text-2xl text-gray-700 mb-2">Utente con badge:</p> <p className="text-2xl text-gray-700 mb-2">Utente con badge:</p>
<p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p> <p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p>
<p className="text-2xl text-gray-700">non trovato nel sistema</p> <p className="text-2xl text-gray-700 mb-8">non trovato nel sistema</p>
{/* Cancel Button */}
<Button
variant="secondary"
size="lg"
onClick={onCancelUser}
>
Annulla
</Button>
</div> </div>
) : error ? ( ) : error ? (
// Error state // Error state

View File

@@ -54,7 +54,7 @@ export function SuccessModal({
</div> </div>
{/* Carosello Messaggi Benvenuto */} {/* Carosello Messaggi Benvenuto */}
<WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs} /> <WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs}/>
{/* Sub text */} {/* Sub text */}
<p className="text-2xl md:text-3xl text-white/80 mt-8 animate-fade-in"> <p className="text-2xl md:text-3xl text-white/80 mt-8 animate-fade-in">