Overview
Autenticazione & Autorizzazioni
Questa sezione raccoglie il flusso login e i livelli autorizzativi principali: UI di accesso, metadata (tabella/colonna/menu) e route-guard lato host Angular.
1) Login
Screen riferimento:
manual__auth_login__01.png: schermata login (username/password + azioni auth).
Captcha (abilitazione):
- Il captcha viene abilitato quando entrambe le chiavi sono valorizzate:
captcha_public_keycaptcha_private_key- La configurazione pre-login viene esposta da
GET /api/Meta/AuthConfig(captchaEnabled,captchaSiteKey).
Registrazione (abilitazione):
- La registrazione viene esposta in UI quando
registrationEnabled = truenellaAuthConfig. - Nel backend la registrazione e' spenta per default: si accende solo con
AppSettings:registrationEnabled = true.default-role-iddeve indicare un ruolo esistente senza dirittiadmin/superadmin, altrimenti la registrazione viene rifiutata. - In login compare il link
Registrati; nel form register sono previsti username, email, password e (se attivo) captcha.
Recupero password:
- In login e disponibile il link
Password dimenticata?. - La richiesta reset usa
MetaService.requestPasswordResete invia email con link contenentetoken. - Chiavi minime da configurare per il flusso email:
email-hostemail-portemail-sslemail-useremail-pwdemail-sender-address-registrationsite-url(base URL usata per costruire il link di reset).
AppSettings consigliati (estratto):
{
"AppSettings": {
"captcha_public_key": "your-public-key",
"captcha_private_key": "your-private-key",
"registrationEnabled": "true",
"email-sender-address-registration": "no-reply@your-domain.tld",
Limite dei tentativi di login
MetaService.login controlla il limite prima di verificare credenziali e captcha. Oltre il limite risponde HTTP 429 errors.auth.login_rate_limited, con i secondi di attesa in retryAfterSeconds. La risposta non dice quale limite e' scattato, per non rivelare se lo username esiste.
- Per utente (sempre attivo, non configurabile): 5 password sbagliate in 5 minuti sullo stesso username bloccano quell'utente per 1 minuto. Un login riuscito azzera il contatore. Protegge dal brute force sul singolo account.
- Per IP (
loginRateLimitPerIpPerMinute, dalla 1.7.16): numero massimo di tentativi al minuto, riusciti o no, dallo stesso IP; superato, l'IP e' bloccato per 1 minuto. Default0= spento (anche con un valore non numerico o negativo). Sulle installazioni esposte a internet impostalo, es.30: e' l'unica difesa dal credential stuffing, cioe' molti username diversi provati dallo stesso IP, che il limite per utente non ferma. - IP esenti (
loginRateLimitExemptIps, dalla 1.7.16): IP separati da virgola trattati come il loopback, quindi mai limitati ne' contati. Servono agli stress test che fanno login di centinaia di utenti dallo stesso IP (una VM VirtualBox in NAT vede l'host come10.0.2.2). Il confronto e' esatto; la forma IPv4-mapped::ffff:10.0.2.2equivale a10.0.2.2. - Loopback (
127.0.0.1,::1,::ffff:127.x.x.x): mai limitato. - Le due chiavi si leggono a ogni login, quindi una modifica vale senza riavvio se la configurazione si ricarica da sola (altrimenti va riciclato l'application pool).
- Fino alla 1.7.15 il limite per IP era fisso a 30 tentativi al minuto, con il solo loopback esente.
{
"AppSettings": {
"loginRateLimitPerIpPerMinute": "30",
"loginRateLimitExemptIps": "10.0.2.2, 192.168.1.50"
}
}1bis) Modello flag admin (server + client)
Il framework distingue tre flag con granularita' crescente, valutati in sequenza dal piu' restrittivo al piu' permissivo:
| Flag | Sorgente DB | Scope | Usato in |
|---|---|---|---|
isSuperAdmin | ruoli.superadmin (bit) | Privilegio massimo: puo' modificare metadati di progetto | Gate server su tutte le mutazioni metadata (RawHelpers.checkAdmin) + UI-gate client (UserInfoService.isUserAdmin / isCurrentUserAdmin) |
isRoleAdmin | ruoli.admin (bit) | Ruolo "Amministratore" nominale (es. seed id_ruolo=2) | Solo fallback-grant per-route (non supera checkAdmin) |
isAdmin (legacy) | utenti.isAdmin (bit) | Flag per-utente storico | Solo fallback-grant per-route (non supera checkAdmin) |
Le due semantiche applicative:
- Gate "mutazione metadata" (scaffolding, save boardcontent, edit colonne/tabelle metadata, ecc.):
- server: RawHelpers.checkAdmin(uid) — fallisce con AuthenticationException("Need administrative rights!") se !user.isSuperAdmin.
- client: UI nasconde/disabilita le azioni lette da UserInfoService.isUserAdmin(userLike) che ritorna userLike.isSuperAdmin.
- Gli endpoint coinvolti sono ~40.
- Fallback-grant per-route (quando una tabella ha
md_grant_by_default = falsee nessun grant esplicito utente/ruolo/azienda):
- server: applyTableRestrictions — concede view/edit/insert/delete se user.hasAdminGrant, dove:
hasAdminGrant = isSuperAdmin OR isRoleAdmin OR isAdmin - La cascade e' permissiva per retrocompatibilita': un superadmin supera sempre, ma anche un ruolo admin=1 o un utente con il vecchio utenti.isAdmin=1 resta autorizzato.
Payload k-user (cookie / sessionStorage) post-login contiene entrambi i flag:
{
"user_id": 100274,
"user_name": "admin",
"role": "Admin",
"role_id": 1,
"isAdmin": true,
"isSuperAdmin": true,
Note operative:
- Il flag legacy
utenti.isAdminresta popolato sul modellouserma non va piu' usato per decisioni di autorizzazione nuove: preferire sempreisSuperAdmin(gate) ohasAdminGrant(fallback permissivo). - Lato Angular host, il route-guard
roleRouteCanMatchGuardusa la labelrole(stringa"Admin"/"Amministratore"/ ecc.) per match conFRAMEWORK_ROUTE_ROLE_RULES. E' una decisione di routing applicativa, indipendente dai flag DB.
1ter) Chiamate AsmxProxy: login obbligatorio per default
Dalla 1.7.13 ogni metodo raggiungibile da /api/Meta/AsmxProxy/{servizio}.{metodo} richiede una sessione valida, salvo quelli dichiarati anonimi. Vale per MetaService, scaffolding e per i servizi dell'applicazione in WEB_UI_CRAFTER.ProjectData.Servizi.
[AsmxAnonymous](namespaceWEB_UI_CRAFTER.Helpers): metodo chiamabile senza login, comelogin,me,GetTranslation,registrationStatus.[AsmxAdmin]: metodo riservato agli amministratori (ruolosuperadmin). La classescaffoldinge' marcata per intero: tutti i suoi metodi, su ogni DBMS, sono riservati agli amministratori.[AsmxFirstRun]: anonimo solo finche'firstRune'true, poi riservato agli amministratori.- Il proxy invoca solo le classi di servizio: un nome di classe completo di altri namespace viene rifiutato.
Un servizio dell'applicazione che deve rispondere prima del login va marcato con [AsmxAnonymous]; senza, risponde 401 errors.auth.unauthenticated.
Sessione, logout e lista utenti
- Con
enableCookieAuthentication = truetoken, scadenza e sostituzione della sessione da parte di un nuovo login sono verificati lato server su tutti i provider, PostgreSQL e Oracle compresi. - Con
enableCookieAuthentication = "signed"(dalla 1.7.21) il cookiek-usere' del server e firmato HMAC: piu' sessioni per utente come confalse, ma un cookie senza firma valida, scaduto o modificato risponde 401errors.auth.unauthenticated. Confalseprivilegi e ruolo dichiarati nel cookie sono confrontati col DB a ogni richiesta. Confronto delle tre modalita' e chiavecookie-signing-keyin AppSettings. - Lo
user_iddel cookiek-usere' accettato solo se intero; un cookie illeggibile risponde 401errors.auth.unauthenticated. logoutSession,logoutelogOutForceagiscono solo sulla sessione del chiamante; chiudere la sessione di un altro utente richiede diritti di amministratore.- La lista utenti non restituisce piu' token e IP della sessione: il codice client non deve contarci.
2) Metadati autorizzazioni: tabelle, colonne, menu
Screen ripresi dalla documentazione metadata esistente:
metadata-integration__metadata-editor-auth-table__desktop.png: autorizzazioni tabella.metadata-integration__metadata-editor-auth-table-edit-popup__desktop.png: popup edit autorizzazione tabella.metadata-integration__metadata-editor-auth-column__desktop.png: autorizzazioni colonna.metadata-integration__metadata-editor-auth-column-edit-popup__desktop.png: popup edit autorizzazione colonna.metadata-integration__metadata-menu-management__desktop.png: gestione menu metadata.metadata-integration__metadata-menu-management-edit-popup__desktop.png: popup edit voce menu.
Metadati coinvolti:
_Metadati_Utenti_Autorizzazioni_Tabelle: permessi view/edit/insert/delete a livello route/tabella._Metadati_Utenti_Autorizzazioni_Colonne: permessi field-level (es. editabilita/required per ruolo o utente)._metadati__menu: visibilita/navigazione voci menu in combinazione con i permessi route.md_record_restriction_key_user_field_list(route): restrizione dei record per utente, ruolo o azienda per chi non e' admin. Vale in lettura (griglie, lookup, OData) e, dalla 1.7.21, anche in scrittura:updateRecord,deleteRecordedeleteRecordByIDsu una riga che la restrizione nasconde all'utente rispondono 403errors.auth.route_read_forbidden(reasonrow_not_visible); prima la WHERE della UPDATE/DELETE conteneva solo la chiave. Stessa regola via OData (404): vedi OData.
3) Utilizzo route-guard (host project)
Obiettivo: bloccare route riservate in base ai ruoli utente e reindirizzare a /unauthorized.
File host da modificare (esempio WuicTest):
C:\src\Wuic\WuicTest\wwwroot\src\app\routing\route-role-map.tsC:\src\Wuic\WuicTest\wwwroot\src\app\routing\role-route.guard.tsC:\src\Wuic\WuicTest\wwwroot\src\app\wuic-bridges\routes.tsC:\src\Wuic\WuicTest\wwwroot\src\app\app.routes.tsC:\src\Wuic\WuicTest\wwwroot\src\app\component\unauthorized\unauthorized.component.ts
Esempio 1: mappa regole ruolo/route
export const FRAMEWORK_ROUTE_ROLE_RULES: RouteRoleRule[] = [
{ key: 'designer', routePattern: 'designer', roles: ['admin'] },
{ key: 'workflow-designer', routePattern: 'workflow-designer', roles: ['admin'] },
{ key: 'dashboard', routePattern: ':route/dashboard', roles: ['admin'] },
{ key: 'report-designer', routePattern: ':route/report-designer', roles: ['admin'] }
];Esempio 2: aggancio guard alle route
{
path: ':route/dashboard',
loadComponent: () => import('wuic-framework-lib-src/component/designer/designer.route.component').then((m) => m.DesignerRouteComponent),
canMatch: [menuRouteAccessCanMatchGuard, roleRouteCanMatchGuard],
canActivate: [menuRouteAccessCanActivateGuard, roleRouteCanActivateGuard],
data: { breadcrumbs: 'dashboard', roleRuleKey: 'dashboard' }
}Esempio 3: redirect su pagina unauthorized
return router.createUrlTree(['/unauthorized'], {
queryParams: { from: attemptedPath || '/' }
});Note operative:
roleRuleKeyinroute.datae preferibile quando la route contiene parametri dinamici.- In fallback, il guard puo usare il
route.pathper match diretto. - La pagina
unauthorizeddeve essere registrata nelle route host e mostrare la route richiesta (queryParam from).
Screenshot
