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 AppSettings sono 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':

Aspettofalse (client-managed)true (server-managed, token)"signed" (server-managed, firmato)
Chi scrive il cookieFrontend JavaScript dopo il loginBackend via Set-CookieBackend via Set-Cookie
Formatok-user=<urlencoded-json>k-user=<urlencoded-json>k-user=s1.<scadenza>.<firma>.<json> (firma HMAC-SHA256, contenuto non cifrato)
HttpOnlyNo (leggibile da JS)Si (protezione XSS)Si
SameSite—Lax (protezione CSRF)Lax
Secure—Si in HTTPS, off su http://localhostCome true
Scadenza—sessionTimeoutMinutes (default 60), rinnovata a ogni richiestaCome true; la scadenza e' anche dentro la firma, quindi la fa rispettare il server
Verifica server a ogni richiestaPrivilegi e ruolo dichiarati nel cookie confrontati col DBToken + IP + timeout su DBFirma e scadenza; privilegi e ruolo confrontati col DB
Sessioni per utentePiu' sessioni concorrentiUna sola (l'ultimo login vince)Piu' sessioni concorrenti
Cookie modificabile dal clientSi, utente compreso: il DB blocca solo privilegi superiori a quelli dell'utente indicato, non l'identita' (vedi sotto)NoNo: ogni modifica invalida la firma
IP bindingNoSi (cambio IP forza re-login)No
Schema DB richiestoStandardColonne token, ip, LastActivityDateTabella _wuic_revoked_sessions (creata in automatico all'avvio)
LogoutMetaService.logoutMetaService.logoutSession (azzera il token su DB)MetaService.logoutSession (revoca la sessione di quel browser)
Quando usarloSolo sviluppo e CI: non autentica (vedi sotto)Produzione esposta su internet, una sessione per utenteProduzione 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.

Casofalsetrue"signed"
Secondo login dello stesso utente da un altro browser o dispositivoRestano collegati entrambiIl primo browser viene disconnesso alla richiesta successiva (session_replaced)Restano collegati entrambi
Logout da un browserIl frontend cancella il cookie; sul server non c'e' nulla da chiudereToken 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 logoutValida. Il cookie contiene solo lo user_id: si puo' anche costruire senza averlo mai ricevutoRifiutata (401)Rifiutata (401): la sessione e' revocata sul DB
Nessuna richiesta per piu' di sessionTimeoutMinutesNessuna scadenza lato serverSessione scaduta (session_expired): nuovo loginCookie scaduto: nuovo login, solo su quel browser
Logout di un altro utente da parte di un amministratore (MetaService.logout)Nessun effetto lato serverL'utente e' disconnesso subitoTutte 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 utentiNessun effetto sulle sessioniNessun effetto sulle sessioni aperte; il login successivo usa la nuova passwordTutte 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 limiteNessun limite: scade solo per inattivita'Rifiutata: nuovo login, anche se e' in uso (dalla 1.7.21)
Cambio di indirizzo IPNessun effettoNuovo login richiestoNessun effetto
Utente cancellato o ruolo cambiatoRifiutato alla richiesta successivaRifiutato alla richiesta successivaRifiutato 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_length e password_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 (in AppSettings)

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:

Snippet 1JSON
"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 con invalid_scope durante il consent.
  • Single Logout (SLO): SignedOutCallbackPath viene 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) e Authentication.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==Google usa AddGoogle() con scope cablati. Per ADFS impostare `Provider=OpenIdConnect` (non Google) altrimenti la pipeline punta a accounts.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.WsFederation o Sustainsys.Saml2) e un nuovo branch in Startup.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:

Snippet 2JSON
"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.*.json usa __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 usa uid. Cambiare di conseguenza UserSearchFilter e UsernameAttribute.
  • 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:* sono RestartAndLogout: 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 che dbms e meta-dbms siano entrambi supportati dal deploy.
  • Per notifiche realtime, Notifications:Mode=SqlDependency richiede setup SQL compatibile; in alternativa usare Polling.
  • L'editor AppSettings (/api/AppSettings/*) verifica lato server sessione e ruolo superadmin: un utente senza quel ruolo riceve 401, anche se si chiama admin o ha un ruolo di nome admin.

appsettings.json reale (snapshot)

Snippet 3JSON
{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "Authentication": {

Screenshot

appsettings / manual-appsettings-01
appsettings / manual-appsettings-01