diff --git a/NETWORK_ACCESS.md b/NETWORK_ACCESS.md new file mode 100644 index 0000000..24b37c7 --- /dev/null +++ b/NETWORK_ACCESS.md @@ -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 +``` diff --git a/TEST_CHECKLIST.md b/TEST_CHECKLIST.md index 670b4d9..2061165 100644 --- a/TEST_CHECKLIST.md +++ b/TEST_CHECKLIST.md @@ -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,18 +11,21 @@ ## 🔐 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" +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) diff --git a/ai-prompts/00-welcome-agent.md b/ai-prompts/00-welcome-agent.md index f106603..e3ce3eb 100644 --- a/ai-prompts/00-welcome-agent.md +++ b/ai-prompts/00-welcome-agent.md @@ -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. --- diff --git a/ai-prompts/01-backend-plan.md b/ai-prompts/01-backend-plan.md index 0444234..242eeea 100644 --- a/ai-prompts/01-backend-plan.md +++ b/ai-prompts/01-backend-plan.md @@ -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 diff --git a/ai-prompts/02-frontend-plan.md b/ai-prompts/02-frontend-plan.md index b096419..6ced21d 100644 --- a/ai-prompts/02-frontend-plan.md +++ b/ai-prompts/02-frontend-plan.md @@ -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 | --- diff --git a/backend-mock/API_SPECIFICATION.md b/backend-mock/API_SPECIFICATION.md index ef3b1ff..78a9737 100644 --- a/backend-mock/API_SPECIFICATION.md +++ b/backend-mock/API_SPECIFICATION.md @@ -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 +2. **Entry:** + - 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` diff --git a/backend-mock/data/users_default.json b/backend-mock/data/users_default.json index 146951e..851ad7b 100644 --- a/backend-mock/data/users_default.json +++ b/backend-mock/data/users_default.json @@ -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 }, { diff --git a/backend-mock/data/users_test.json b/backend-mock/data/users_test.json index e8ba7dc..f1c1867 100644 --- a/backend-mock/data/users_test.json +++ b/backend-mock/data/users_test.json @@ -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 }, { diff --git a/dev.sh b/dev.sh index 60199be..984f2d6 100755 --- a/dev.sh +++ b/dev.sh @@ -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 diff --git a/frontend/src/components/WelcomeCarousel.tsx b/frontend/src/components/WelcomeCarousel.tsx index ab8f6ea..596d3e3 100644 --- a/frontend/src/components/WelcomeCarousel.tsx +++ b/frontend/src/components/WelcomeCarousel.tsx @@ -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 ( -
diff --git a/frontend/src/screens/ActiveGateScreen.tsx b/frontend/src/screens/ActiveGateScreen.tsx index 0afdd86..84312a9 100644 --- a/frontend/src/screens/ActiveGateScreen.tsx +++ b/frontend/src/screens/ActiveGateScreen.tsx @@ -130,7 +130,16 @@ export function ActiveGateScreen({
Utente con badge:
{notFoundBadge}
-non trovato nel sistema
+non trovato nel sistema
+ + {/* Cancel Button */} +