Overview
AppSettings
Parametri applicativi, configurazioni ambiente e override runtime.
Questa pagina documenta le chiavi usate dal framework in appsettings.json e appsettings.{Environment}.json.
Convenzioni generali
- I valori sotto
AppSettingssono letti come stringa e poi convertiti (bool/int) dal backend. - Per flag booleani usare
true|false. - Per chiavi numeriche usare interi positivi (secondi/minuti/timeout).
- Le chiavi sono case-sensitive lato JSON.
Logging
Sezione top-level Logging:LogLevel (standard .NET). Valori ammessi per ogni chiave: Trace | Debug | Information | Warning | Error | Critical | None. Hot-reload.
Logging:LogLevel:Default
Significato: livello di log di default per tutte le categorie non sovrascritte.
Default tipico: Warning.
Logging:LogLevel:System
Significato: livello per la categoria System (runtime .NET).
Logging:LogLevel:Microsoft
Significato: livello per la categoria Microsoft (ASP.NET Core, EF, hosting).
AppSettings - Core bootstrap
firstRun
Significato: abilita il flusso di prima configurazione/provisioning.
Valori: true | false.
Default tipico: false.
Nota: i pacchetti di release arrivano con true (il wizard si apre al primo avvio e riporta la chiave a false). Con false i metodi del wizard ([AsmxFirstRun]) rispondono solo agli amministratori.
preScaffold
Significato: abilita pre-scaffolding di oggetti metadata/app.
Valori: true | false.
Default tipico: false.
projectDataFolder
Significato: root progetto applicativo (es. CrmApp) usata per file, assembly e risorse runtime.
Valori: path assoluto filesystem.
projectAssemblyName
Significato: assembly applicativo caricato dinamicamente.
Valori: path assoluto .dll.
defaultSiteRoute
Significato: route iniziale client dopo bootstrap/login.
Valori: hash-route (es. #/).
license-email
Significato: email/licenza installazione.
Valori: email valida.
AppSettings - Database e provider
dbms
Significato: provider DB principale route dati.
Valori enum-like: mssql | mysql | postgresql | oracle | xml.
Default tipico: mssql.
meta-dbms
Significato: provider DB metadata (route di sistema).
Valori enum-like: mssql | mysql | postgresql | oracle | xml.
Default tipico: uguale a dbms.
allowMultipleDBMS
Significato: abilita convivenza provider multipli nel runtime.
Valori: true | false.
Default tipico: false.
connection
Significato: connection string base usata da servizi/scaffolding.
Valori: connection string provider-specific.
DataDBName
Significato: nome database dati di default.
Valori: nome DB.
connectionByUser
Significato: usa connessione/contesto legato all'utente corrente in punti runtime specifici.
Valori: true | false.
Default tipico: false.
AppSettings - CRUD, query, filtri
autoGeneratedQueryTimeout
Significato: timeout (secondi) query SQL autogenerate.
Valori: intero positivo.
Default tipico: 120.
storedProcTimeout
Significato: timeout (secondi) esecuzione stored procedure.
Valori: intero positivo.
enableServerSideCrudChangeLog
Significato: abilita logging lato server delle operazioni CRUD.
Valori: true | false.
Default tipico: true.
optimisticCheckEnabled
Significato: abilita il controllo di concorrenza ottimistica sugli update: il salvataggio viene rifiutato se il record è stato modificato da un altro utente dopo la lettura (confronto del campo di versione/data_modifica). Evita sovrascritture silenziose in scenari multi-utente.
Valori: true | false. Hot-reload.
logicDeleteField
Significato: campo globale per cancellazione logica (se non definito in metadata tabella).
Valori: nome colonna.
logicDeleteValue
Significato: valore che rappresenta "record cancellato logicamente".
Valori: string/numero compatibile col tipo del campo.
Default tipico: 1.
cacheDataMinutes
Significato: cache in memoria dei risultati delle liste (una voce per route e testo della query, per tenant); una scrittura sulla route svuota le sue voci.
Valori: assente o 0 = cache spenta (default, nulla viene tenuto in memoria); intero positivo = minuti di validita' di una voce; -1 = senza scadenza.
Limite: al massimo 500 query per route; per fare posto si tolgono prima le voci scadute, poi le piu' vecchie.
cacheDataIncludedRoutes
Significato: whitelist route cacheabili.
Valori: lista route separata da virgole.
cacheDataExcludedRoutes
Significato: blacklist route da escludere dalla cache.
Valori: lista route separata da virgole.
traceQuery
Significato: abilita tracing query SQL con tempi esecuzione.
Valori: true | false.
Default tipico: false.
sqlVerbose
Significato: abilita output SQL verboso in endpoint/tools che lo supportano.
Valori: true | false.
Su SQL Server la lettura delle liste passa i valori dei filtri e la paginazione come parametri (@wq0, @wq1..., @wq_skip, @wq_take): la query riportata (sqlQuery nella risposta e _log_query con traceQuery) inizia con un DECLARE per ogni parametro con il suo valore, seguito dalla query, ed e' eseguibile cosi' com'e' in SSMS, es. DECLARE @wq0 varchar(8000) = '%Tailspin%'; DECLARE @wq_skip int = 25; DECLARE @wq_take int = 25;. Su MySQL, PostgreSQL e Oracle i valori viaggiano allo stesso modo (@wq0... su MySQL e PostgreSQL, :wq0... su Oracle) e la query riportata ha tre parti: un commento con i parametri e i loro valori (-- WUIC parametri: @wq0 = '%Tailspin%', @wq_skip = 25, ...), la query parametrica cosi' come arriva al DBMS, commentata riga per riga dopo -- WUIC query parametrica: (con @wq0/:wq0 al posto dei valori), e dopo -- WUIC query eseguibile (valori al posto dei parametri): la stessa query con i valori al posto dei segnaposto, l'unica parte non commentata: si incolla ed esegue cosi' com'e' in MySQL Workbench, psql/pgAdmin o SQL Developer. Le query senza parametri restano nel formato di sempre.
logInvoke
Significato: abilita log invocazioni funzioni dinamiche/callback.
Valori: true | false.
maxConcurrentExports
Significato: numero massimo di export XLS eseguiti insieme dal processo; gli altri aspettano in coda e il dialog di progress e la notifica mostrano la posizione ("In coda: posizione N").
Valori: intero positivo.
Nota: se assente o non valido, il backend usa meta' dei core della macchina (almeno 1). Il limite vale per processo: ogni nodo o application pool ha il suo.
Dimensionamento: ogni export in corso usa circa 1,3 core e tiene in memoria il foglio non compresso fino alla chiusura del file (misura del 25/09/2026 su 228k righe x 13 colonne: ~300 MB per export). Su una macchina condivisa con altri application pool conviene un valore piu' basso del default: sul demo pubblico (8 core, 8 application pool) e' 2, perche' 4 export insieme portavano il processo al 60-75% della macchina.
Esempio: "maxConcurrentExports": "2" in AppSettings (appsettings.json o l'overlay di ambiente, es. appsettings.Demo.json); il valore si legge a ogni export, ma dopo una modifica al file va riciclato l'application pool se la configurazione non si ricarica da sola.
maxQueuedExports
Significato: numero massimo di export in attesa; oltre, la richiesta risponde 503 errors.metaservice.export.queue_full (ogni export in coda occupa un thread della richiesta).
Valori: intero positivo.
Nota: se assente o non valido, il backend usa 10.
AppSettings - Sicurezza, login, sessione
customAuthentication
Significato: delega autenticazione a logica custom applicativa.
Valori: true | false.
enableCookieAuthentication
Significato: controlla chi scrive il cookie di sessione k-user, con quale policy e come il server lo verifica.
Valori: false | true | "signed" ("signed" dalla 1.7.21). Default: false. Il cambio richiede riavvio e un nuovo login.
Confronto sintetico delle tre modalita':
| Aspetto | false (client-managed) | true (server-managed, token) | "signed" (server-managed, firmato) |
|---|---|---|---|
| Chi scrive il cookie | Frontend JavaScript dopo il login | Backend via Set-Cookie | Backend via Set-Cookie |
| Formato | k-user=<urlencoded-json> | k-user=<urlencoded-json> | k-user=s1.<scadenza>.<firma>.<json> (firma HMAC-SHA256, contenuto non cifrato) |
HttpOnly | No (leggibile da JS) | Si (protezione XSS) | Si |
SameSite | — | Lax (protezione CSRF) | Lax |
Secure | — | Si in HTTPS, off su http://localhost | Come true |
| Scadenza | — | sessionTimeoutMinutes (default 60), rinnovata a ogni richiesta | Come true; la scadenza e' anche dentro la firma, quindi la fa rispettare il server |
| Verifica server a ogni richiesta | Privilegi e ruolo dichiarati nel cookie confrontati col DB | Token + IP + timeout su DB | Firma e scadenza; privilegi e ruolo confrontati col DB |
| Sessioni per utente | Piu' sessioni concorrenti | Una sola (l'ultimo login vince) | Piu' sessioni concorrenti |
| Cookie modificabile dal client | Si, utente compreso: il DB blocca solo privilegi superiori a quelli dell'utente indicato, non l'identita' (vedi sotto) | No | No: ogni modifica invalida la firma |
| IP binding | No | Si (cambio IP forza re-login) | No |
| Schema DB richiesto | Standard | Colonne token, ip, LastActivityDate | Tabella _wuic_revoked_sessions (creata in automatico all'avvio) |
| Logout | MetaService.logout | MetaService.logoutSession (azzera il token su DB) | MetaService.logoutSession (revoca la sessione di quel browser) |
| Quando usarlo | Solo sviluppo e CI: non autentica (vedi sotto) | Produzione esposta su internet, una sessione per utente | Produzione con piu' sessioni per utente (account condivisi, demo, piu' browser) |
### Comportamento single-session (modalita' true)
Ogni login genera un nuovo token che sovrascrive il precedente. Conseguenze pratiche:
- Se lo stesso utente si logga da due browser diversi (o due finestre anonime), l'ultimo login vince: il primo browser viene disconnesso con il messaggio "Sessione terminata: e' stato effettuato l'accesso con lo stesso utente da un altro browser." — sia navigando una pagina, sia facendo refresh.
- Il browser con l'ultimo login non subisce impatti.
- Due tab nello stesso browser non hanno questo problema: condividono lo stesso cookie, quindi il token si aggiorna su entrambe.
### Modalita' "signed" (dalla 1.7.21)
- Il cookie e' firmato con la chiave cookie-signing-key (vedi sotto). Firma assente o non valida, cookie scaduto o modificato: vale come nessun cookie, la richiesta risponde 401 errors.auth.unauthenticated e il frontend torna al login.
- Come con false, piu' login dello stesso utente convivono: un nuovo login non disconnette gli altri browser. A differenza di false, il client non puo' cambiare nulla del cookie (utente, ruolo, azienda, lingua).
- Ogni login e' una sessione propria (sid dentro il cookie firmato, uguale a ogni rinnovo). Il logout la revoca sul DB (_wuic_revoked_sessions): il cookie di quel browser e ogni sua copia sono rifiutati, le altre sessioni dello stesso utente restano collegate. Dettagli nella sezione seguente.
- Il frontend non scrive mai il cookie: il cambio lingua (MetaService.setCurrentUserLanguage) e il cambio azienda (MetaService.switchAzienda) lo riemettono dal server.
- Script e test che chiamano l'API devono rimandare il cookie ricevuto dal login (cookie jar o Set-Cookie) cosi' com'e', senza costruirlo: backend-api-client.mjs e gli script scaffold-*.ps1 lo fanno gia'.
### Login, logout e piu' sessioni dello stesso utente
Una "sessione" e' un cookie k-user. Le tab e le finestre dello stesso browser (stesso profilo, non in incognito) condividono un solo cookie, quindi sono sempre una sola sessione, in tutte e tre le modalita': un login in una tab vale per tutte (anche con un utente diverso, che sostituisce il precedente), un logout in una tab disconnette anche le altre alla richiesta successiva. Sessioni indipendenti richiedono browser diversi, profili diversi o una finestra in incognito.
| Caso | false | true | "signed" |
|---|---|---|---|
| Secondo login dello stesso utente da un altro browser o dispositivo | Restano collegati entrambi | Il primo browser viene disconnesso alla richiesta successiva (session_replaced) | Restano collegati entrambi |
| Logout da un browser | Il frontend cancella il cookie; sul server non c'e' nulla da chiudere | Token azzerato su DB: il cookie non vale piu' | Revoca la sessione di quel browser; le altre sessioni dello stesso utente restano collegate |
| Copia del cookie usata dopo il logout | Valida. Il cookie contiene solo lo user_id: si puo' anche costruire senza averlo mai ricevuto | Rifiutata (401) | Rifiutata (401): la sessione e' revocata sul DB |
Nessuna richiesta per piu' di sessionTimeoutMinutes | Nessuna scadenza lato server | Sessione scaduta (session_expired): nuovo login | Cookie scaduto: nuovo login, solo su quel browser |
Logout di un altro utente da parte di un amministratore (MetaService.logout) | Nessun effetto lato server | L'utente e' disconnesso subito | Tutte le sue sessioni aperte fino a quel momento sono rifiutate; un nuovo login funziona. Con il multi-tenant vale solo per l'utente di quell'azienda, non per gli utenti con lo stesso id di altre aziende |
Reset della password (MetaService.resetPassword) o nuova password salvata sulla riga utenti | Nessun effetto sulle sessioni | Nessun effetto sulle sessioni aperte; il login successivo usa la nuova password | Tutte le sessioni aperte dell'utente sono rifiutate, compresa quella di chi salva se e' la sua: si rientra con la nuova password (dalla 1.7.21) |
Sessione usata senza pause per piu' di signedSessionMaxLifetimeHours (default 24 ore) | Nessun limite | Nessun limite: scade solo per inattivita' | Rifiutata: nuovo login, anche se e' in uso (dalla 1.7.21) |
| Cambio di indirizzo IP | Nessun effetto | Nuovo login richiesto | Nessun effetto |
| Utente cancellato o ruolo cambiato | Rifiutato alla richiesta successiva | Rifiutato alla richiesta successiva | Rifiutato alla richiesta successiva |
In pratica:
- false non autentica: chi conosce o indovina uno user_id entra come quell'utente. Va usato solo in sviluppo e CI, mai su un'installazione raggiungibile da altri. Per ricordarlo, a ogni avvio il backend scrive nel log la riga [security] enableCookieAuthentication=false: NESSUNA autenticazione... e l'editor AppSettings chiede conferma prima di salvare false.
- true e "signed" chiudono la sessione anche per chi ne ha una copia, sia con il proprio logout sia con quello di un amministratore.
- La differenza e' nelle sessioni concorrenti: con true l'ultimo login butta fuori gli altri browser, con "signed" convivono e il logout chiude solo il browser che lo chiede (quello di un amministratore le chiude tutte). Per chiudere subito le sessioni di tutti gli utenti si cambia cookie-signing-key.
### Trappole note
- Se modifichi appsettings.json a mano mentre il backend e' running, serve un restart (l'editor AppSettings web fa il flush automaticamente).
- Dopo un flip da false a true, i cookie preesistenti non hanno il token → gli utenti devono rifare il login.
- I test E2E (backend-api-client.mjs) rilevano la modalita' automaticamente e funzionano in entrambi i casi.
- Dalla 1.7.13 la verifica di token e scadenza vale su tutti i provider, PostgreSQL e Oracle compresi, e il logout chiude solo la sessione del chiamante: vedi Autenticazione & Autorizzazioni.
- Dopo un cambio di modalita' i cookie gia' emessi non valgono piu' (passando a "signed" manca la firma; tornando a true/false il cookie firmato non e' JSON) → gli utenti devono rifare il login.
- Nell'editor AppSettings la chiave e' un elenco a tre valori; nel file sono accettati sia le stringhe "true"/"false"/"signed" sia i booleani JSON.
cookie-signing-key(dalla 1.7.21)
Significato: chiave HMAC-SHA256 che firma il cookie k-user con enableCookieAuthentication = "signed"; ignorata nelle altre modalita'.
Valori: base64 di almeno 32 byte, es. l'output di [Convert]::ToBase64String([Security.Cryptography.RandomNumberGenerator]::GetBytes(32)).
Default: vuota. Vuota, il backend la genera al primo login e la salva in appsettings.json; se il file non e' scrivibile (o il valore presente non e' valido) usa una chiave solo in memoria e a ogni riavvio serve un nuovo login.
Nota: e' un segreto (chi la conosce puo' firmare cookie validi): non va committata ne' copiata fra installazioni. Con piu' istanze dietro un bilanciatore va impostata a mano, uguale su tutte: lasciata vuota, ogni istanza ne genera una propria e i cookie emessi da una non valgono sulle altre (401 a caso). Cambiarla disconnette tutti gli utenti.
Log: all'avvio, in modalita' "signed", il backend scrive l'impronta della chiave ([security] enableCookieAuthentication=signed: impronta della chiave di firma 1a2b3c4d5e6f7a8b ...: i primi 8 byte dello SHA-256, non la chiave). Impronte diverse fra le istanze = chiavi diverse. Una chiave generata sul momento e' segnalata dalla riga [security] cookie-signing-key generata ora...: se compare a ogni avvio, il valore salvato non viene letto (es. una variabile d'ambiente o un appsettings.{Environment}.json che la imposta vuota).
signedSessionMaxLifetimeHours(dalla 1.7.21)
Significato: durata massima di una sessione "signed", in ore dal login. Il cookie si rinnova a ogni richiesta, ma il rinnovo non sposta l'istante del login: passate queste ore la sessione e' rifiutata anche se e' in uso e serve un nuovo login. Limita quanto a lungo puo' servire una copia rubata del cookie. Ignorata nelle altre modalita'.
Valori: intero; 0 = nessun limite (sconsigliato: una copia del cookie usata con continuita' non scade mai).
Default: 24 (anche se il valore e' vuoto, non numerico o negativo). Letto a ogni richiesta.
Nota: le revoche in _wuic_revoked_sessions si tengono fino a login + durata massima + sessionTimeoutMinutes, cosi' un cookie revocato non torna valido quando la riga viene cancellata.
enableODATAAuthentication
Significato: abilita auth per endpoint OData.
Valori: true | false.
Nota: dalla 1.7.13 gli endpoint dati /odata/* richiedono sempre una sessione valida, indipendentemente da questa chiave. Dalla 1.7.21 anche /odata, /odata/$metadata e /odata/openapi.json (vedi odataPublicMetadata).
odataPublicMetadata(dalla 1.7.21)
Significato: true = /odata, /odata/$metadata e /odata/openapi.json rispondono senza sessione, come fino alla 1.7.20: chiunque vede quali tabelle sono esposte e con quali colonne (non i dati). Per chi ha client che leggono la forma del servizio prima di autenticarsi.
Valori: true | false.
Default: false (serve una sessione, come per i dati). Letta a ogni richiesta.
apiTokensEnabled(dalla 1.7.21)
Significato: token personali per i client OData esterni (Power BI, Excel, script). Ogni utente li crea dal menu utente ("Token API") e valgono solo su /odata, con i permessi del proprietario e nell'azienda in cui sono stati creati (vedi OData, sezione "Token personali").
Valori: true | false.
Default: false. Letta a ogni richiesta (nessun riavvio): spenta, i token esistenti sono rifiutati (401 errors.auth.api_tokens_disabled) e la voce di menu sparisce.
apiTokenMaxLifetimeDays(dalla 1.7.21)
Significato: validita' massima di un token personale, in giorni. Ogni token ha una scadenza: di default 90 giorni (o questo valore, se e' minore).
Valori: intero > 0.
Default: 365 (anche se vuoto, non numerico o <= 0). Letta a ogni richiesta.
apiTokenAllowedRoles(dalla 1.7.22)
Significato: id dei ruoli (separati da virgola) che possono creare e usare token personali. Togliere un ruolo ferma anche i token gia' creati dai suoi utenti; il superadmin passa sempre.
Valori: lista di id ruolo.
Default: vuoto (ogni ruolo). Letta a ogni richiesta.
apiTokenWriteRoles(dalla 1.7.22)
Significato: id dei ruoli (separati da virgola) che possono creare e usare token con la scrittura (odata.write). Togliere un ruolo rende di sola lettura i token di scrittura gia' creati.
Valori: lista di id ruolo.
Default: vuoto (ogni ruolo che puo' creare token). Letta a ogni richiesta.
odataMaxTop(dalla 1.7.22)
Significato: $top massimo accettato su /odata/<entity set>; un valore piu' grande risponde 400.
Valori: intero > 0.
Default: 1000 (anche se vuoto, non numerico o <= 0). Letta a ogni richiesta.
odataMaxExpandDepth(dalla 1.7.22)
Significato: annidamento massimo di $expand (a($expand=b) = 2); piu' profondo risponde 400.
Valori: intero > 0.
Default: 2. Letta a ogni richiesta.
forwardedHeadersKnownProxies/forwardedHeadersKnownNetworks(dalla 1.7.22)
Significato: indirizzi IP (e reti in forma CIDR, es. 10.0.0.0/8) dei reverse proxy o bilanciatori davanti al backend di cui si accetta X-Forwarded-For, oltre a localhost. Senza, l'IP del client e' quello del proxy e i limiti di frequenza (login, token API) colpiscono insieme tutti i client dietro di lui.
Valori: liste separate da virgola.
Default: vuoto (solo localhost). Letta all'avvio.
enableHttpsRedirection
Significato: forza il redirect HTTP→HTTPS + HSTS. Default false (nessun redirect, niente HSTS): l'app risponde sia su HTTP sia su HTTPS. Impostare true SOLO su host con certificato HTTPS valido (attiva UseHttpsRedirection/UseHsts).
Valori: true | false. Default: false. Riavvio.
Significato: abilita accesso ospite.
Valori: true | false.
applyGrantByDefaultToGuest
Significato: applica grant di default anche all'utente guest.
Valori: true | false.
grantDashSaveToGuest
Significato: consente salvataggio dashboard al guest.
Valori: true | false.
default-role-id
Significato: ruolo assegnato in fallback/default.
Valori: intero (ID ruolo).
Nota: per la registrazione deve indicare un ruolo esistente senza diritti admin/superadmin, altrimenti la registrazione viene rifiutata.
registrationEnabled
Significato: abilita la registrazione self-service degli utenti. Senza true esplicito la registrazione non e' disponibile.
Valori: true | false.
Default: false.
loginRateLimitPerIpPerMinute(dalla 1.7.16)
Significato: numero massimo di tentativi di login al minuto (riusciti o no) dallo stesso IP; superato, l'IP riceve HTTP 429 errors.auth.login_rate_limited per 1 minuto. Protegge dal credential stuffing (molti username provati dallo stesso IP).
Valori: intero; 0 = limite per IP spento.
Default: 0 (anche se il valore non e' numerico o e' negativo).
Nota: sulle installazioni esposte a internet conviene impostarlo, es. 30. Il limite per utente (5 password sbagliate in 5 minuti, blocco di 1 minuto) resta sempre attivo e non dipende da questa chiave. Il loopback non e' mai limitato. Fino alla 1.7.15 il limite per IP era fisso a 30 tentativi al minuto, con il solo loopback esente. Dettagli in Autenticazione & Autorizzazioni.
Esempio: "loginRateLimitPerIpPerMinute": "30"; il valore si legge a ogni login, ma dopo una modifica al file va riciclato l'application pool se la configurazione non si ricarica da sola.
loginRateLimitExemptIps(dalla 1.7.16)
Significato: IP esenti dal limite dei tentativi di login, trattati come il loopback: non vengono mai limitati ne' contati. Serve per gli stress test che fanno login di centinaia di utenti dallo stesso IP.
Valori: IP separati da virgola (spazi ammessi). Il confronto e' esatto; la forma IPv4-mapped (::ffff:10.0.2.2) equivale a 10.0.2.2.
Esempio: "loginRateLimitExemptIps": "10.0.2.2, 192.168.1.50".
sessionTimeoutMinutes
Significato: timeout sessione utente in minuti.
Valori: intero positivo.
Default tipico: 60.
notifySessionExpirationBefore
Significato: anticipo (minuti) notifica scadenza sessione.
Valori: intero positivo.
Default tipico: 1 o 2 a seconda ambiente.
enableUserLanguageSwitch
Significato: abilita switch lingua utente in UI.
Valori: true | false.
AppSettings - Password e policy credenziali
IsPwdEncripted
Significato: indica se le password in store sono hashate.
Valori: true | false.
encriptionMethod
Significato: algoritmo hash legacy usato per verifica password.
Valori enum-like: SHA1 | MD5.
Default tipico: SHA1.
password_min_lengthepassword_min_length
Significato: lunghezza minima password (nota: in alcuni file esiste variante con spazio finale nel nome chiave).
Valori: intero positivo.
Default tipico: 8.
AppSettings - Captcha, doppio token, URL applicative
captcha_public_key
Significato: chiave pubblica captcha.
Valori: string.
captcha_private_key
Significato: chiave privata captcha.
Valori: string.
enableDoubleTokenAuthentication
Significato: abilita step di verifica token aggiuntivo nel flusso login/attivazione previsto.
Valori: true | false.
site-url
Significato: URL base sito per link assoluti email/workflow.
Valori: URL assoluto.
content-url
Significato: URL base contenuti/link di conferma (mailing, read-confirm, ecc.).
Valori: URL assoluto.
AppSettings - Email e notifiche mail
email-host
Significato: host SMTP.
Valori: hostname/IP.
email-port
Significato: porta SMTP.
Valori: intero (25, 465, 587, ...).
email-ssl
Significato: abilita TLS/SSL SMTP.
Valori: true | false.
email-user
Significato: username SMTP.
Valori: string.
email-pwd
Significato: password SMTP.
Valori: string/segreto.
email-sender-address-mailing
Significato: mittente email di mailing/notifiche.
Valori: email valida.
email-sender-name-mailing
Significato: display name mittente mailing.
Valori: string.
email-sender-address-registration
Significato: mittente email registrazione/attivazione.
Valori: email valida.
Nota: dalla 1.7.13 non accende piu' la registrazione: serve registrationEnabled = true.
email-admin-registration
Significato: destinatario amministrativo eventi registrazione.
Valori: email valida.
email_confirm_token_subject
Significato: subject template email conferma token.
Valori: string.
email_confirm_token_body
Significato: body template email conferma token.
Valori: string (anche HTML).
AppSettings - Upload e media
uploadFolder
Significato: cartella root upload file/immagini.
Valori: path assoluto. Un valore relativo viene ignorato e si usa <projectDataFolder>/Upload.
Nota: dalla 1.7.13 chi scrive il file e chi lo rilegge per le colonne BLOB usano la stessa risoluzione su tutti i provider.
base64Image
Significato: forza gestione immagini in formato base64 dove previsto.
Valori: true | false.
AppSettings - Report e runtime report viewer
ReportMode
Significato: modalità rendering report nel viewer.
Valori enum-like osservati: web (default fallback) e modalità custom gestite lato controller.
Nota: se assente, il backend usa fallback web.
reportQueryTimeout
Significato: timeout query dataset report (secondi), applicato ai data source SQL rimasti al timeout di default del Designer (30).
Valori: intero positivo.
Nota: se assente o non valido, il backend usa fallback 120.
AppSettings - Record translations (chiavi legacy in AppSettings)
recordTranslationsEnabled
Significato: abilita traduzione record per campo.
Valori: true | false.
recordTranslationsDefaultTableName
Significato: tabella default traduzioni record.
Valori: nome tabella.
recordTranslationsTranslationJsonFieldName
Significato: nome campo JSON traduzioni.
Valori: nome colonna.
recordTranslationsDefaultLanguage
Significato: lingua fallback traduzioni record.
Valori: culture code (es. it-IT, en-US).
recordTranslationsFieldNames
Significato: elenco campi traducibili.
Valori: lista separata da virgole.
Sezioni top-level correlate (fuori da AppSettings)
RecordTranslations:Enabled
Significato: switch principale runtime traduzioni record.
Valori: true | false.
RecordTranslations:DefaultTableName
Significato: tabella fisica traduzioni.
Valori: nome tabella.
RecordTranslations:TranslationJsonFieldName
Significato: campo JSON traduzioni.
Valori: nome colonna.
RecordTranslations:DefaultLanguage
Significato: lingua fallback.
Valori: culture code.
RecordTranslations:FieldNames
Significato: campi traducibili.
Valori: array di stringhe.
Scheduler:Enabled
Significato: abilita hosted service scheduling.
Valori: true | false.
Scheduler:PollSeconds
Significato: polling scheduler in secondi.
Valori: intero positivo.
Scheduler:MaxTasksPerCycle
Significato: massimo task processati per ciclo.
Valori: intero positivo.
Scheduler:RetryDelaySeconds
Significato: ritardo retry task falliti.
Valori: intero positivo.
Scheduler:SqlCommandTimeoutSeconds
Significato: timeout SQL scheduler.
Valori: intero positivo.
Notifications:Enabled
Significato: abilita subsystem notifiche realtime.
Valori: true | false.
Notifications:Mode
Significato: modalità watcher backend notifiche.
Valori enum-like: SqlDependency | Polling.
Notifications:PollSeconds
Significato: polling notifiche se Mode = Polling.
Valori: intero positivo.
notificationInitialSnapshotConcurrency(inAppSettings)
Significato: quanti WebSocket appena aperti leggono e inviano insieme lo snapshot iniziale delle notifiche non lette; gli altri aspettano il loro turno senza occupare un thread ne' una connessione al DB metadati.
Valori: intero; 0 o negativo = nessun limite.
Nota: se assente o non valido, il backend usa 32. Si legge una volta, alla prima connessione WebSocket del processo: dopo una modifica va riavviato il backend. Il limite vale per processo.
Perche': in una raffica di login (load test a 6000 utenti, MySQL) ogni socket nuovo teneva una connessione del pool metadati per lo snapshot, e il pool finiva prima delle richieste normali.
Authentication:OAuth:Enabled
Significato: abilita login OAuth/OIDC.
Valori: true | false.
Authentication:OAuth:Provider
Significato: provider OAuth visualizzato/gestito.
Valori enum-like usato in config: Google (estendibile a provider custom compatibili OIDC).
Authentication:OAuth:Authority
Significato: authority/issuer OIDC.
Valori: URL.
Authentication:OAuth:ClientId
Significato: client id applicazione OAuth.
Valori: string.
Authentication:OAuth:ClientSecret
Significato: client secret OAuth.
Valori: string/segreto.
Authentication:OAuth:RequireHttpsMetadata
Significato: richiede metadati OIDC via HTTPS.
Valori: true | false.
Authentication:OAuth:CallbackPath
Significato: path callback signin.
Valori: path assoluto.
Authentication:OAuth:SignedOutCallbackPath
Significato: path callback signout.
Valori: path assoluto.
Authentication:OAuth:AllowedReturnOrigins
Significato: whitelist origin di ritorno consentite.
Valori: array URL/origin.
Authentication:OAuth:Scopes
Significato: scope richiesti al provider.
Valori: array stringhe (openid, profile, email, ...).
ADFS via OpenID Connect
ADFS 4.0+ (Windows Server 2016+) parla OIDC nativamente: l'integrazione con WUIC non richiede codice nuovo, basta la sezione Authentication.OAuth configurata sul tenant ADFS. La pipeline e' la stessa di Google/Auth0/Keycloak — Provider="OpenIdConnect" instrada il flusso su services.AddOpenIdConnect(...).
Pre-requisiti lato ADFS:
1. Federation Service Name raggiungibile in HTTPS dal backend WUIC (es. https://adfs.contoso.com/adfs).
2. Endpoint OIDC abilitato: verifica che https://adfs.contoso.com/adfs/.well-known/openid-configuration risponda 200 con il discovery document.
3. Application Group registrata in ADFS Management Console (adfs.msc → Application Groups → Add Application Group → "Server application accessing a web API"):
- Redirect URI: https://<host-wuic>/signin-oidc
- Annotare ClientId (auto-generato) e generare un ClientSecret.
4. Issuance Transform Rules sul Web API: emettere almeno email, name, upn come claim (servono al frontend per popolare il profilo utente).
Configurazione lato KonvergenceCore:
"Authentication": {
"OAuth": {
"Enabled": true,
"Provider": "OpenIdConnect",
"Authority": "https://adfs.contoso.com/adfs",
"ClientId": "<application-group-client-id>",
"ClientSecret": "__SET_VIA_SECRET_MANAGER__",
Trappole note:
- `RequireHttpsMetadata=true` obbligatorio in produzione: ADFS espone i metadati firmati su HTTPS; mai disabilitarlo se non in dev contro un ADFS con cert self-signed.
- `CallbackPath` deve corrispondere letteralmente al Redirect URI registrato in ADFS (case-sensitive sulla path). Se cambi uno, cambia anche l'altro.
- Scopes: ADFS supporta
openid,profile,email, e custom scope definiti nelle Application Permissions. Se richiedi uno scope non concesso al client, ADFS risponde coninvalid_scopedurante il consent. - Single Logout (SLO):
SignedOutCallbackPathviene chiamato solo se ADFS e' configurato per emettere il logout token. Se manca, dopo il logout WUIC il browser resta loggato in ADFS e un nuovo login WUIC e' silent SSO. - Coesistenza con LDAP: la sezione
Authentication.OAuth(federazione browser-redirect) eAuthentication.Ldap(bind diretto da username/password) sono indipendenti. Possono coesistere — il front-end mostra un pulsante "Login con ADFS" oltre al form locale. - Provider distinto da Google: il branch
Provider==GoogleusaAddGoogle()con scope cablati. Per ADFS impostare `Provider=OpenIdConnect` (nonGoogle) altrimenti la pipeline punta aaccounts.google.com. - ADFS 2.0 / 3.0 (Windows Server 2008R2-2012R2): parlano solo WS-Federation o SAML 2.0, non OIDC. Per quei tenant servirebbe un PackageReference aggiuntivo (
Microsoft.AspNetCore.Authentication.WsFederationoSustainsys.Saml2) e un nuovo branch inStartup.ConfigureServices— fuori scope di questa sezione.
Authentication:Ldap (LDAP / Active Directory)
WUIC supporta autenticazione LDAP/AD come provider opzionale prima del login DB tradizionale (LDAP-first). Quando Authentication:Ldap:Enabled=true, il backend tenta una bind verso il directory server configurato; se la bind riesce e l'utente non esiste ancora nella tabella utenti locale, viene auto-provisionato con Authentication:Ldap:DefaultRoleId (o, in fallback, AppSettings:default-role-id). Se LDAP è offline e FallbackToDbOnFailure=true, il login ricade automaticamente sul provider DB — utile per garantire l'accesso admin/admin anche in caso di outage del directory server.
Esempio minimo:
"Authentication": {
"Ldap": {
"Enabled": true,
"Host": "ldap.corp.example.com",
"Port": 389,
"UseStartTls": true,
"BaseDn": "OU=Users,DC=corp,DC=example,DC=com",
Chiavi disponibili:
Authentication:Ldap:Enabled
Significato: abilita il provider LDAP davanti al DB. Quando false, il flusso e' identico a un deploy senza la sezione.
Valori: true | false.
Authentication:Ldap:Host
Significato: hostname del directory server (AD DC, OpenLDAP, ApacheDS).
Valori: string.
Authentication:Ldap:Port
Significato: porta del directory server.
Valori: intero. Tipicamente 389 (plain o StartTLS) o 636 (LDAPS).
Authentication:Ldap:UseSsl
Significato: apre la connessione direttamente in LDAPS (TLS at-connect).
Valori: true | false.
Authentication:Ldap:UseStartTls
Significato: apre in plain e poi promuove a TLS via StartTLS extended op (RFC 4511).
Valori: true | false. Mutuamente esclusivo con UseSsl.
Authentication:Ldap:BaseDn
Significato: DN base sotto cui cercare gli utenti.
Valori: string DN (es. DC=example,DC=com).
Authentication:Ldap:UserSearchFilter
Significato: filter LDAP per individuare la entry utente. Il placeholder {username} viene sostituito con lo username (RFC 4515 escaping applicato lato C# per prevenire injection).
Valori: string filter. Default: (&(objectClass=user)(sAMAccountName={username})) (Active Directory). Per OpenLDAP usare tipicamente (&(objectClass=inetOrgPerson)(uid={username})).
Authentication:Ldap:UsernameAttribute
Significato: attributo della entry da cui leggere lo username canonico (usato come username_column_name nella tabella utenti locale).
Valori: string. Default: sAMAccountName.
Authentication:Ldap:DisplayNameAttribute
Significato: attributo da cui leggere il display name (usato come user_description_column_name).
Valori: string. Default: displayName.
Authentication:Ldap:EmailAttribute
Significato: attributo da cui leggere l'email (usato come email_column_name).
Valori: string. Default: mail.
Authentication:Ldap:BindDn
Significato: DN del service account read-only usato per la search. Vuoto = anonymous bind (alcuni AD lo permettono per ricerche limitate, OpenLDAP di solito no).
Valori: string DN.
Authentication:Ldap:BindPassword
Significato: password del service account. Mai committare: usare secret manager/env var (Authentication__Ldap__BindPassword) o riempire post-install dall'editor AppSettings.
Valori: string/segreto.
Authentication:Ldap:ConnectTimeoutSeconds
Significato: timeout di connessione (in secondi) verso il directory server. Allo scadere il login fa fallback al DB se FallbackToDbOnFailure=true.
Valori: intero positivo. Default: 5.
Authentication:Ldap:FallbackToDbOnFailure
Significato: se LDAP e' irraggiungibile o ritorna errore non legato alle credenziali, ricade sul login DB tradizionale. Garantisce l'accesso admin/admin durante outage del directory.
Valori: true | false. Default: true.
Authentication:Ldap:AutoProvision
Significato: al primo login LDAP riuscito di un utente sconosciuto al DB locale, crea automaticamente la riga in _metadati__tabelle.user_table_name (default utenti) con il default role.
Valori: true | false. Default: true.
Authentication:Ldap:DefaultRoleId
Significato: id ruolo assegnato in auto-provisioning. Override di AppSettings:default-role-id. Se entrambi null, il default e' 1.
Valori: intero o null.
Trappole note
- Bind password mai committata: il template
appsettings.linux.*.jsonusa__SET_LDAP_BIND_PASSWORD__come placeholder; il deploy script la sostituisce. Per i template "dev" (appsettings.json,appsettings.Development.json) il valore di default e' la stringa vuota — mai un segreto reale. - AD vs OpenLDAP filter: AD usa
sAMAccountName, OpenLDAP usauid. Cambiare di conseguenzaUserSearchFiltereUsernameAttribute. - StartTLS su porta 636: errore comune. La 636 e' LDAPS (
UseSsl=true); StartTLS gira sulla 389. - Nessun cambiamento al cookie path: utenti LDAP e DB ricevono lo stesso cookie
k-user. La differenza e' solo come vengono autenticate le credenziali. - Sentinel password: il provisioner inserisce
'__LDAP__'come valore della password column locale per gli utenti auto-provisionati. Non viene mai usata per autenticare (la bind LDAP ha gia' validato), ma la colonna deve essere NOT NULL-compatibile. - Toggle a runtime: tutte le chiavi
Authentication:Ldap:*sonoRestartAndLogout: editarle dall'AppSettings editor richiede restart pulito.
AppSettings - License
Chiavi della licenza WUIC (firma RSA verificata al bootstrap). Hot-reload.
license-email
Significato: email a cui è intestata la licenza (vedi anche Core bootstrap).
Valori: email valida.
license-payload
Significato: payload della licenza in Base64 (tier, feature abilitate, scadenza, fingerprint macchina). Letto e verificato contro license-signature + license-public-key-pem.
Valori: stringa Base64.
license-signature
Significato: firma RSA del license-payload. Se non valida, la licenza è rifiutata e le feature gated restano disattive.
Valori: stringa Base64.
license-public-key-pem
Significato: chiave pubblica RSA (PEM) usata per verificare la firma. La chiave privata non lascia mai la macchina di emissione.
Valori: PEM.
SQL Retry (SqlMapperRetry)
Retry automatico con backoff sulle operazioni SQL transienti (deadlock, timeout, connessione persa). Tutte le chiavi richiedono riavvio.
I pacchetti di release usano MaxAttempts = 3 e BaseDelayMs = 100, e la pipeline di rilascio rifiuta valori inferiori: se un'installazione aggiornata ha valori piu' bassi, riportali a questi.
SqlMapperRetry:Enabled
Significato: abilita il retry automatico sugli errori SQL transienti.
Valori: true | false.
SqlMapperRetry:MaxAttempts
Significato: numero massimo di tentativi prima di propagare l'errore.
Valori: intero positivo.
SqlMapperRetry:BaseDelayMs
Significato: ritardo base (ms) per il backoff esponenziale tra i tentativi.
Valori: intero positivo.
SqlMapperRetry:UseJitter
Significato: aggiunge jitter casuale al delay per evitare thundering-herd.
Valori: true | false.
SqlMapperRetry:RetryReadOperationsOnly
Significato: limita il retry alle sole operazioni di lettura (le write non-idempotenti non vengono ritentate).
Valori: true | false.
SqlMapperRetry:RetryOnTransaction
Significato: consente il retry anche quando l'operazione è dentro una transazione esplicita.
Valori: true | false.
Crash Reporting (CrashReporting)
Invio degli stacktrace anonimizzati al server crash-reporter WUIC. Richiede consenso GDPR esplicito prima dell'attivazione. Tutte le chiavi richiedono riavvio (middleware/hosted service registrati a startup in base a Enabled).
CrashReporting:Enabled
Significato: invia i dettagli d'errore anonimizzati al server privato WUIC. Richiede il consenso GDPR esplicito prima dell'attivazione.
Valori: true | false.
CrashReporting:UpstreamUrl
Significato: endpoint del receiver. Default https://errors.wuic-framework.com. Da sovrascrivere solo per deploy on-prem self-hosted.
Valori: URL.
CrashReporting:ClientId
Significato: override del client id. Se vuoto, viene usata l'email dal payload di licenza come client_id autoritativo.
Valori: string.
CrashReporting:DisclaimerAcceptedVersion
Significato: campo di audit read-only impostato dal flusso di consenso. Incrementato quando il testo del disclaimer cambia (forza il re-consenso).
Valori: string.
CrashReporting:DisclaimerAcceptedAt
Significato: timestamp di audit read-only impostato dal flusso di consenso.
Valori: string (data/ora).
CrashReporting:MaxQueueSize
Significato: capacità del channel bounded; le entry più vecchie vengono scartate sotto crash-storm sostenuti.
Valori: intero positivo.
CrashReporting:MaxBreadcrumbsLen
Significato: limite dei breadcrumb per report (byte UTF-8). Troncato se superato.
Valori: intero positivo.
CrashReporting:DedupTtlSeconds
Significato: finestra entro cui stack hash identici vengono contati come duplicati.
Valori: intero positivo.
CrashReporting:DedupFlushEvery
Significato: flush ogni N-esima occorrenza ripetuta (1 = nessun dedup, invio sempre).
Valori: intero positivo.
RAG Chatbot (LLM)
Configurazione del componente <wuic-rag-chatbot> e dell'engine RAG .NET (WuicRagEngine). Le chiavi sono lette hot-reload dal backend .NET. Non hard-coded.
rag-llm-provider
Significato: provider del modello conversazionale. Va impostato esplicitamente. anthropic usa il formato Anthropic (Claude); openai/openrouter/ollama usano un endpoint OpenAI-compatible. ollama punta a un Ollama locale via rag-llm-base-url (es. http://HOST:11434/v1). Se vuoto o non impostato il chatbot resta in retrieval-only (nessun LLM invocato): non c'è un provider di default.
Valori: anthropic | openai | openrouter | ollama | vuoto (= retrieval-only, nessun LLM).
rag-llm-api-key
Significato: UNICA fonte della api key, indipendente dal provider scelto sopra. Valore speciale agent-sdk = usa l'Agent SDK (claude CLI) via subscription invece dell'API a consumo, se installato. Per ollama una key dummy (es. ollama) e' sufficiente. Vuoto = LLM disabilitato (solo retrieval). Mai committare in repo: usare secret manager / env var override.
Valori: string (api key del provider) | agent-sdk | ollama (dummy).
rag-llm-base-url
Significato: override dell'endpoint del provider. Obbligatorio per `ollama` (es. http://HOST:11434/v1). Vuoto = default per provider (https://api.anthropic.com, https://api.openai.com/v1, https://openrouter.ai/api/v1).
Valori: URL.
rag-llm-default-chat-model
Significato: modello usato dal chatbot per ogni nuovo turn (determina anche il context window max).
Valori: dipende dal provider, es. claude-haiku-4-5-20251001 (anthropic) | qwen2.5-coder:32b (ollama).
rag-auto-compact-threshold
Significato: soglia in numero di turn oltre la quale il backend lancia automaticamente un compact best-effort pre-Ask (riassume i turn vecchi in _rag_chat_sessions.context_summary). Settare a 0 per disabilitare l'auto-compact (l'utente puo comunque triggerare /compact manualmente via UI).
Valori: intero >=0. Default consigliato: 30.
Note operative
- In produzione usare sempre secret manager/variabili ambiente per password/token (
email-pwd,ClientSecret, captcha key, ecc.). - Se abiliti
allowMultipleDBMS, validare chedbmsemeta-dbmssiano entrambi supportati dal deploy. - Per notifiche realtime,
Notifications:Mode=SqlDependencyrichiede setup SQL compatibile; in alternativa usarePolling. - L'editor AppSettings (
/api/AppSettings/*) verifica lato server sessione e ruolosuperadmin: un utente senza quel ruolo riceve 401, anche se si chiamaadmino ha un ruolo di nome admin.
appsettings.json reale (snapshot)
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Authentication": {
Screenshot
