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

View File

@@ -81,9 +81,14 @@ Ottimizzata per tablet in orizzontale.
- [x] `ActiveGateScreen.tsx` - varco attivo:
- [x] Card utente (layout largo per tablet)
- [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] 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] `DebugScreen.tsx` - pagina diagnostica RFID
@@ -111,9 +116,10 @@ Ottimizzata per tablet in orizzontale.
- [x] Componente `WelcomeCarousel.tsx`
- [x] 10 lingue supportate
- [x] **Animazione smooth sliding** (slide up/down)
- [x] Scorrimento automatico (intervallo calcolato dinamicamente)
- [x] Scorrimento automatico lento (più rilassato)
- [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
@@ -130,12 +136,12 @@ Ottimizzata per tablet in orizzontale.
## Badge di Test
| Badge | Nome | Ruolo | Ammesso |
|--------------|----------------|---------|---------------|
| `0008988288` | Marco Bianchi | Votante | ✅ Sì |
| `0007399575` | Laura Rossi | Votante | ✅ Sì |
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
| `0006478281` | - | - | ⚠️ Non nel DB |
| Badge | Nome | Ruolo | Ammesso |
|--------------|----------------|-----------|---------------|
| `0008988288` | Marco Bianchi | Convocato | ✅ Sì |
| `0007399575` | Laura Rossi | Invitato | ✅ Sì |
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
| `0006478281` | - | - | ⚠️ Non nel DB |
---

View File

@@ -1,6 +1,7 @@
# 📄 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
**Data:** Gennaio 2026
@@ -10,12 +11,15 @@ Questo documento descrive le specifiche che il backend reale deve implementare p
## 🌐 Configurazione Server
### CORS
Il server deve abilitare CORS con le seguenti impostazioni:
- **Origins:** `*` (o lista specifica di domini autorizzati)
- **Methods:** `GET, POST, OPTIONS`
- **Headers:** `Content-Type, Authorization`
### Serving Frontend
Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`.
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
### Server Start Time
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.
**Comportamento:**
1. Il frontend salva `server_start_time` nella sessione locale
2. Al caricamento successivo, confronta il valore salvato con quello attuale
3. Se differiscono, la sessione viene invalidata (il server è stato riavviato)
Questo garantisce che:
- Un riavvio del server forza il re-login di tutti i validatori
- 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.
#### Response `200 OK`
```json
{
"room_name": "Sala Assemblea",
@@ -53,19 +61,21 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `room_name` | string | Nome della sala visualizzato nell'header |
| `meeting_id` | string | Identificativo del meeting/votazione |
| Campo | Tipo | Descrizione |
|---------------------|---------|---------------------------------------------------------------|
| `room_name` | string | Nome della sala visualizzato nell'header |
| `meeting_id` | string | Identificativo del meeting/votazione |
| `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) |
---
### 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
```json
{
"badge": "0007399575",
@@ -73,12 +83,20 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) |
| `password` | string | Password inserita dall'utente |
| Campo | Tipo | Descrizione |
|------------|--------|-------------------------------------------------------------------------------------|
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) - **opzionale per validazione** |
| `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)
```json
{
"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
{
"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:**
- La password è l'unico fattore di autenticazione
- Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive
- 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.
#### Path Parameters
- `badge_code`: Codice badge (stringa, es. "0008988288")
**IMPORTANTE - Confronto Badge:**
Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere.
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)
```json
{
"badge_code": "0008988288",
@@ -120,11 +160,18 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
"cognome": "Bianchi",
"url_foto": "https://example.com/foto.jpg",
"ruolo": "Votante",
"ammesso": true # <-- utente ammesso all'ingresso
"ammesso": true
#
<--
utente
ammesso
all
'ingresso
}
```
#### Response `200 OK` (Utente trovato ma NON ammesso)
```json
{
"badge_code": "0000514162",
@@ -132,27 +179,35 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
"cognome": "Verdi",
"url_foto": "https://example.com/foto.jpg",
"ruolo": "Tecnico",
"ammesso": false, #<-- utente NON ammesso all'ingresso
"ammesso": false,
#
<--
utente
NON
ammesso
all
'ingresso
"warning": "Utente non ammesso all'ingresso"
}
```
#### Response `404 Not Found` (Utente non trovato)
```json
{
"detail": "Badge non trovato nel sistema"
}
```
| Campo Response | Tipo | Descrizione |
|----------------|------|-------------|
| `badge_code` | string | Codice badge |
| `nome` | string | Nome dell'utente |
| `cognome` | string | Cognome dell'utente |
| `url_foto` | string | URL immagine profilo (può essere placeholder) |
| `ruolo` | string | Ruolo dell'utente (es. "Votante", "Tecnico", "Ospite") |
| `ammesso` | boolean | `true` se autorizzato all'ingresso |
| `warning` | string? | Opzionale, presente se `ammesso: false` |
| Campo Response | Tipo | Descrizione |
|----------------|---------|--------------------------------------------------------|
| `badge_code` | string | Codice badge |
| `nome` | string | Nome dell'utente |
| `cognome` | string | Cognome dell'utente |
| `url_foto` | string | URL immagine profilo (può essere placeholder) |
| `ruolo` | string | Ruolo dell'utente (es. "Votante", "Tecnico", "Ospite") |
| `ammesso` | boolean | `true` se autorizzato all'ingresso |
| `warning` | string? | Opzionale, presente se `ammesso: false` |
---
@@ -161,6 +216,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
Registra l'ingresso di un utente. Richiede conferma del validatore.
#### Request Body
```json
{
"user_badge": "0008988288",
@@ -168,12 +224,13 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
}
```
| Campo | Tipo | Descrizione |
|-------|------|-------------|
| `user_badge` | string | Badge dell'utente che sta entrando |
| Campo | Tipo | Descrizione |
|----------------------|--------|---------------------------------------|
| `user_badge` | string | Badge dell'utente che sta entrando |
| `validator_password` | string | Password del validatore (ri-verifica) |
#### Response `200 OK` (Successo)
```json
{
"success": true,
@@ -182,6 +239,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
```
#### Response `401 Unauthorized` (Password errata)
```json
{
"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)
```json
{
"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)
```json
{
"detail": "Badge utente non trovato"
@@ -203,7 +263,9 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
```
**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
2. Che l'utente esista
3. Che l'utente sia ammesso (`ammesso: true`)
@@ -215,23 +277,28 @@ Non fidarsi mai del frontend per la validazione!
## 🔒 Considerazioni di Sicurezza
### Validazione Lato Backend
Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend:
1. **Login:** Verificare che la password sia corretta
2. **Entry:**
- Verificare password validatore
- Verificare che utente esista
- Verificare che utente sia ammesso
- Loggare l'operazione per audit
- Verificare password validatore
- Verificare che utente esista
- Verificare che utente sia ammesso
- Loggare l'operazione per audit
### Logging e Audit
Si raccomanda di loggare:
- Ogni tentativo di login (successo/fallimento)
- Ogni registrazione di ingresso
- Badge non trovati (potenziale tentativo di accesso non autorizzato)
### Protezione Rate Limiting
Implementare rate limiting su:
- `/login-validate`: max 5 tentativi/minuto per IP
- `/entry-request`: max 30 richieste/minuto per IP
@@ -240,32 +307,36 @@ Implementare rate limiting su:
## 📊 Struttura Dati Suggerita
### Database Utenti
```sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
CREATE TABLE users
(
id SERIAL PRIMARY KEY,
badge_code VARCHAR(20) UNIQUE NOT NULL,
nome VARCHAR(100) NOT NULL,
cognome VARCHAR(100) NOT NULL,
url_foto VARCHAR(500),
ruolo VARCHAR(50) NOT NULL,
ammesso BOOLEAN DEFAULT true,
nome VARCHAR(100) NOT NULL,
cognome VARCHAR(100) NOT NULL,
url_foto VARCHAR(500),
ruolo VARCHAR(50) NOT NULL,
ammesso BOOLEAN DEFAULT true,
created_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)
```sql
CREATE TABLE access_log (
id SERIAL PRIMARY KEY,
user_badge VARCHAR(20) NOT NULL,
CREATE TABLE access_log
(
id SERIAL PRIMARY KEY,
user_badge VARCHAR(20) NOT NULL,
validator_badge VARCHAR(20) NOT NULL,
room_id VARCHAR(50),
action VARCHAR(20) NOT NULL, -- 'entry', 'denied', 'not_found'
timestamp TIMESTAMP DEFAULT NOW(),
ip_address VARCHAR(45)
room_id VARCHAR(50),
action VARCHAR(20) NOT NULL, -- 'entry', 'denied', 'not_found'
timestamp TIMESTAMP DEFAULT NOW(),
ip_address VARCHAR(45)
);
```
@@ -286,9 +357,10 @@ CREATE TABLE access_log (
9. **Entry badge inesistente** → 404 Not Found
### Badge di Test (Mock)
```
0008988288 - Marco Bianchi (Votante, ammesso)
0007399575 - Laura Rossi (Votante, ammessa)
0008988288 - Marco Bianchi (Convocato, ammesso)
0007399575 - Laura Rossi (Invitato, ammessa)
0000514162 - Giuseppe Verdi (Tecnico, NON ammesso)
0006478281 - NON nel database (per test 404)
```
@@ -298,9 +370,11 @@ CREATE TABLE access_log (
## 📝 Note Implementative
### Content-Type
Tutte le richieste e risposte usano `application/json`.
### Codici Errore HTTP
- `200`: Successo
- `401`: Non autorizzato (password errata)
- `403`: Vietato (utente non ammesso)
@@ -308,7 +382,9 @@ Tutte le richieste e risposte usano `application/json`.
- `500`: Errore interno server
### Formato Errori
Tutti gli errori devono restituire un JSON con campo `detail`:
```json
{
"detail": "Messaggio di errore descrittivo"
@@ -320,6 +396,7 @@ Tutti gli errori devono restituire un JSON con campo `detail`:
## 🔄 Changelog
### v1.0 (Gennaio 2026)
- Specifica iniziale
- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request`
- Meccanismo invalidazione sessioni con `server_start_time`

View File

@@ -5,12 +5,20 @@
"meeting_id": "VOT-2024"
},
"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",
"nome": "Laura",
"cognome": "Rossi",
"url_foto": "https://randomuser.me/api/portraits/women/2.jpg",
"ruolo": "Votante",
"ruolo": "Invitato",
"ammesso": true
},
{

View File

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

8
dev.sh
View File

@@ -215,10 +215,16 @@ cmd_help() {
echo "Esempi:"
echo " ./dev.sh install # Setup iniziale"
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 --host 127.0.0.1 # Solo localhost (non accessibile da rete)"
echo " ./dev.sh server -d data/users_test.json # Con dataset test"
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

View File

@@ -30,11 +30,11 @@ interface WelcomeCarouselProps {
paused?: boolean;
/** Nome dell'utente da visualizzare (opzionale) */
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;
}
export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: WelcomeCarouselProps) {
export function WelcomeCarousel({paused = false, userName, intervalMs = 1200}: WelcomeCarouselProps) {
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
@@ -50,15 +50,15 @@ export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: We
const currentMessage = WELCOME_MESSAGES[currentIndex];
return (
<div className="flex flex-col items-center justify-center text-center w-full max-w-4xl mx-auto px-4">
{/* Container con overflow hidden per animazione slide - altezza maggiore per testi lunghi */}
<div className="relative h-40 md:h-52 w-full overflow-hidden">
<div className="flex flex-col items-center justify-center text-center w-full px-4">
{/* Container fullwidth con overflow hidden per animazione slide */}
<div className="relative h-44 md:h-56 w-full overflow-hidden">
{/* Messaggio corrente */}
<div
key={currentIndex}
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}
</h1>
<p className="text-lg text-white/60 uppercase tracking-wider">

View File

@@ -130,7 +130,16 @@ export function ActiveGateScreen({
</div>
<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-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>
) : error ? (
// Error state

View File

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