| @@ -18,8 +18,9 @@ CMS per portali captive: gestione di card informative, gallerie, flip-book e con | |||||
| 10. [Factory Preset (developer)](#factory-preset-developer) | 10. [Factory Preset (developer)](#factory-preset-developer) | ||||
| 11. [Font](#font) | 11. [Font](#font) | ||||
| 12. [Deploy sotto sotto-percorso (basePath) dietro Apache](#deploy-sotto-sotto-percorso-basepath-dietro-apache) | 12. [Deploy sotto sotto-percorso (basePath) dietro Apache](#deploy-sotto-sotto-percorso-basepath-dietro-apache) | ||||
| 13. [Prerequisiti di sistema](#prerequisiti-di-sistema) | |||||
| 14. [Risoluzione problemi](#risoluzione-problemi) | |||||
| 13. [Protezione dell'amministrazione (Keycloak) e routing](#protezione-dellamministrazione-keycloak-e-routing) | |||||
| 14. [Prerequisiti di sistema](#prerequisiti-di-sistema) | |||||
| 15. [Risoluzione problemi](#risoluzione-problemi) | |||||
| --- | --- | ||||
| @@ -87,7 +88,7 @@ Per cambiare un qualunque limite: modifica il numero in `lib/config.ts` e ricost | |||||
| | `IMAGE_GALLERY` | Image Gallery | Galleria di immagini/video/PDF sfogliabile a schermo intero. | | | `IMAGE_GALLERY` | Image Gallery | Galleria di immagini/video/PDF sfogliabile a schermo intero. | | ||||
| | `BOOK` | Flip-Book | Sfoglialibro in formato A4 (due pagine affiancate). | | | `BOOK` | Flip-Book | Sfoglialibro in formato A4 (due pagine affiancate). | | ||||
| | `FULLSCREEN_LOCK` | Fullscreen Lock (kiosk) | **Takeover totale**: se presente, il portale pubblico mostra SOLO il suo contenuto (immagine o video) a tutto schermo, senza hero, griglia o pulsanti di chiusura. Le altre card vengono nascoste. Utile per redirect/segnaletica kiosk. | | | `FULLSCREEN_LOCK` | Fullscreen Lock (kiosk) | **Takeover totale**: se presente, il portale pubblico mostra SOLO il suo contenuto (immagine o video) a tutto schermo, senza hero, griglia o pulsanti di chiusura. Le altre card vengono nascoste. Utile per redirect/segnaletica kiosk. | | ||||
| | `EXTERNAL_LINK` | External Link | Apre un URL esterno. Visibile nel menu solo se `EXTERNAL_LINK_ENABLED = true`. | | |||||
| | `EXTERNAL_LINK` | External Link | Apre un URL esterno. L'**URL è obbligatorio**: il salvataggio è bloccato (lato UI e lato server) se manca. Visibile nel menu solo se `EXTERNAL_LINK_ENABLED = true`. | | |||||
| **Note sulla Fullscreen Lock:** | **Note sulla Fullscreen Lock:** | ||||
| - Se ci sono più card lock, viene usata la prima per ordine di visualizzazione. | - Se ci sono più card lock, viene usata la prima per ordine di visualizzazione. | ||||
| @@ -132,7 +133,7 @@ La transcodifica richiede `ffmpeg`/`ffprobe` sul server — vedi [Prerequisiti]( | |||||
| Ogni campo compilabile dall'admin ha un limite (vedi [`TEXT_LIMITS`](#limiti-testo-text_limits)). L'interfaccia mostra un contatore quando ci si avvicina al limite (rosso se superato) e blocca l'inserimento oltre il massimo. Lato server, una richiesta che sfora viene rifiutata con `400` e l'elenco dei campi fuori limite — nessun troncamento silenzioso. | Ogni campo compilabile dall'admin ha un limite (vedi [`TEXT_LIMITS`](#limiti-testo-text_limits)). L'interfaccia mostra un contatore quando ci si avvicina al limite (rosso se superato) e blocca l'inserimento oltre il massimo. Lato server, una richiesta che sfora viene rifiutata con `400` e l'elenco dei campi fuori limite — nessun troncamento silenzioso. | ||||
| Gli URL (`actionUrl`) accettano solo gli schemi `http`, `https`, `mailto`, `tel`. Schemi come `javascript:` vengono rifiutati. | |||||
| Gli URL (`actionUrl`) accettano solo gli schemi `http`, `https`, `mailto`, `tel`. Schemi come `javascript:` vengono rifiutati. Per le card **External Link** l'URL è **obbligatorio**: il salvataggio viene rifiutato (UI + server `400`) se il campo è vuoto. | |||||
| ### Caratteri non-ASCII (emoji, cirillico, CJK…) | ### Caratteri non-ASCII (emoji, cirillico, CJK…) | ||||
| @@ -315,6 +316,67 @@ Punti chiave: | |||||
| - Con `BASE_PATH = '/cards'` l'app vive **sempre** sotto `/cards`, anche **senza** proxy: in locale la raggiungi su `http://localhost:3000/cards` (la radice `/` dà 404). Il proxy serve solo a esporla pubblicamente. | - Con `BASE_PATH = '/cards'` l'app vive **sempre** sotto `/cards`, anche **senza** proxy: in locale la raggiungi su `http://localhost:3000/cards` (la radice `/` dà 404). Il proxy serve solo a esporla pubblicamente. | ||||
| - Un singolo build **non** può rispondere contemporaneamente su `/` e su `/cards`: per cambiare percorso modifica `BASE_PATH` e ricompila. | - Un singolo build **non** può rispondere contemporaneamente su `/` e su `/cards`: per cambiare percorso modifica `BASE_PATH` e ricompila. | ||||
| ## Protezione dell'amministrazione (Keycloak) e routing | |||||
| L'autorizzazione **non** è gestita dall'app Next ma da **Apache `mod_auth_openidc`** (Keycloak), già usato dal captive portal. L'app non contiene codice di auth: si fida del gate del reverse proxy. | |||||
| ### Cosa è protetto | |||||
| - **Pagina `/cards/admin`** e **API di scrittura**: accessibili solo all'utente Keycloak con `preferred_username == admin`. | |||||
| - Chi non è autenticato o non è admin → **redirect alla home** del portale (nessun errore). | |||||
| - **Restano pubbliche** (servono al portale per tutti gli utenti WiFi): le **GET** di `/cards/api/cards` e `/cards/api/portals`, e `/cards/api/files`, `/cards/api/fonts`. | |||||
| - Protette su **tutti i metodi**: `/cards/api/upload`, `/cards/api/transcode`, `/cards/api/admin/*` (incluse le GET come backup e stato factory-preset). | |||||
| ### Configurazione Apache | |||||
| 1. Il cookie di sessione OIDC deve coprire `/cards`: nel vhost imposta `OIDCCookiePath /` (non un sotto-percorso come `/general`), altrimenti `mod_auth_openidc` non vede la sessione su `/cards`. | |||||
| 2. Aggiungi nel/i vhost (dove l'OIDC è configurato) i blocchi di protezione: | |||||
| ```apache | |||||
| # Pagina admin: solo utente 'admin'; non autenticato/non admin → home | |||||
| <Location "/cards/admin"> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| ErrorDocument 401 https://<host>/cards/ | |||||
| ErrorDocument 403 https://<host>/cards/ | |||||
| </Location> | |||||
| # API: GET pubblica, scrittura solo admin | |||||
| <Location "/cards/api/cards"> | |||||
| <LimitExcept GET HEAD> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| </LimitExcept> | |||||
| </Location> | |||||
| <Location "/cards/api/portals"> | |||||
| <LimitExcept GET HEAD> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| </LimitExcept> | |||||
| </Location> | |||||
| # upload / transcode / admin: tutti i metodi solo admin | |||||
| <Location "/cards/api/upload"> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| </Location> | |||||
| <Location "/cards/api/transcode"> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| </Location> | |||||
| <Location "/cards/api/admin"> | |||||
| AuthType openid-connect | |||||
| OIDCUnAuthAction 401 | |||||
| Require claim preferred_username:admin | |||||
| </Location> | |||||
| ``` | |||||
| Note: `OIDCUnAuthAction 401` fa sì che il non autenticato riceva `401` (poi `ErrorDocument` → home) invece di essere mandato al login. L'`ErrorDocument` di redirect è solo sulla pagina admin: sulle API un accesso non autorizzato riceve `401/403` (corretto per una chiamata API). L'admin, una volta autenticato via Keycloak come utente `admin` (tramite il normale flusso del portale), invia il cookie di sessione con le richieste e può salvare/caricare/fare backup. | |||||
| ### Pagine inesistenti → redirect alla home | |||||
| Qualsiasi percorso non corrispondente a una pagina o API reale del progetto **non mostra una schermata d'errore**: viene reindirizzato alla home. È gestito lato Next da una rotta catch-all ([app/[...not_found]/page.tsx](app/%5B...not_found%5D/page.tsx)) che esegue `redirect('/')`. Le rotte reali (`/`, `/admin`, `/api/*`) hanno priorità; solo i path inesistenti finiscono lì. Le API inesistenti restano `404` (il redirect riguarda le pagine). | |||||
| ## Prerequisiti di sistema | ## Prerequisiti di sistema | ||||
| Sul server servono alcuni binari di sistema (richiamati direttamente, non via npm): | Sul server servono alcuni binari di sistema (richiamati direttamente, non via npm): | ||||