emilsoftware-utilities 2.0.0-dev.53 → 2.0.0-dev.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1019,6 +1019,7 @@ Content-Type: application/json
1019
1019
 
1020
1020
  { "email": "utente@example.com", "password": "Segreta123!" }`, "Login con password"),
1021
1021
  p(`La risposta include Result.token (il JWT) e il profilo utente con ruoli e permessi. Se l'account \xE8 passwordless, ometti la password: il server risponde con un challenge via email (vedi la sezione su 2FA e passwordless).`),
1022
+ note("info", `Autorizzazioni lato client`, `La fonte autorevole \xE8 Result.userGrants.grants: contiene i grant effettivi (ruoli + permessi diretti, livello massimo). Non leggere i permessi dal JWT e non usare Result.utente.permissions: non sono popolati. Per i superutenti grants contiene tutti i menu attivi al livello 30.`),
1022
1023
  good("ts", [
1023
1024
  "const res = await fetch('/api/accessi/auth/login', {",
1024
1025
  " method: 'POST',",
@@ -1235,7 +1236,7 @@ POST /api/accessi/auth/confirm-reset-password/{token}
1235
1236
  title: "Ruoli e permessi",
1236
1237
  summary: "Assegnazioni sostitutive e composizione dei grant.",
1237
1238
  blocks: [
1238
- p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant diretti prevalgono su quelli dei ruoli; i superutenti vedono tutti i menu attivi.`),
1239
+ p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant effettivi sommano ruoli e permessi diretti prendendo il livello massimo per menu; un livello 0 non annulla un grant del ruolo. I superutenti ricevono tutti i menu attivi al livello massimo.`),
1239
1240
  table(
1240
1241
  ["Metodo", "Percorso", "Descrizione"],
1241
1242
  [
@@ -1533,7 +1534,7 @@ function buildAiDigest() {
1533
1534
  }
1534
1535
 
1535
1536
  // src/accessi-module/Console/changelog.md
1536
- var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\n\n## Non rilasciato\n\n### Cambiamenti\n- Console: rimosse le sezioni **Filtri utente** e **Tipi filtro**. I filtri applicativi sono deprecati e non vengono pi\xF9 letti n\xE9 scritti dalla console; le tabelle restano invariate e la gestione torna manuale sul database.\n- Console: le conferme operative (es. \"Ruoli aggiornati\") ora compaiono come notifiche in alto a destra e scompaiono automaticamente.\n- Console: il salvataggio del profilo non invia pi\xF9 i campi della tabella `FILTRI`, evitando payload rifiutati dal backend. `UserService` tocca `FILTRI` solo se il chiamante invia esplicitamente almeno un campo legacy.\n- Console: scheda utente riorganizzata. \"Indietro\" resta nella barra superiore; stato registrazione, disattivazione ed eliminazione definitiva sono in un form coerente \"Stato e sicurezza\", con icone e tooltip allineati.\n- Console: pagina Changelog a larghezza piena, con intestazioni di versione evidenziate e sezioni orizzontali leggibili.\n- Seed `npm run accessi:seed-admin`: crea o aggiorna un admin completo (`FLGSUPER = 1` e `FLGADMIN = 1`) e corregge il nome colonna usato in precedenza (`FLGADMIN`).\n- Console: la pagina di default nella scheda utente \xE8 ora un autocomplete con ricerca. Propone tutte le rotte del catalogo menu, si filtra scrivendo ed \xE8 ancora possibile inserire un valore libero.\n\n## 2.0.0-dev.50 (2026-10-15)\n\n### Cambiamenti\n- ORM: `Row` e `DbValue` sono i tipi predefiniti di `Orm.query` e `Orm.execute`; `transactionQuery` usa `Row[]`. Gli usi generici non devono pi\xF9 dichiarare `Record<string, unknown>`.\n- Build: il contratto OpenAPI generato (`src/accessi-module/openapi/`) \xE8 ignorato da Git e viene rigenerato da `npm run generate:accessi-openapi`.\n\n## 2.0.0-dev.49 (2026-10-15)\n\n### Cambiamenti\n- Documentazione HTTP rinominata da Swagger a **OpenAPI**: il modulo unificato espone `/accessi/openapi` e `/accessi/openapi.json`; `setupSwagger` usa `/openapi` come default.\n- Il path \xE8 configurabile tramite `EmilsoftwareOptions.swagger` (utile per far convivere pi\xF9 documentazioni).\n- Allineati log di avvio, Wiki e dispatch delle rotte del modulo.\n- L'alias storico `<path>-json` resta supportato.\n\n### Note\n- I consumer che aprivano `/swagger` o `/accessi/swagger` devono aggiornare l'URL alla nuova documentazione OpenAPI.\n\n## 2.0.0-dev.48 (2026-10-13)\n\n### Correzioni\n- `RestUtilities.sendErrorMessage` restituisce status HTTP specifici (400/401/403/4xx/5xx) e codici generici (`VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `INTERNAL_ERROR`, `REQUEST_ERROR`), senza usare codici `ACCESSI_*` nel business helper.\n- Il messaggio restituito \xE8 quello reale dell'eccezione; gli errori testuali di validazione vengono riconosciuti come 400.\n\n## 2.0.0-dev.47 (2026-10-13)\n\n### Correzioni\n- Errore schema database reso generico: codice `DATABASE_SCHEMA_OUTDATED` e messaggio senza riferimenti specifici ad Accessi.\n\n## 2.0.0-dev.46 (2026-10-12)\n\n### Correzioni\n- `sendBaseResponse` resta retro-compatibile quando il terzo argomento \xE8 un tag stringa invece dello status HTTP.\n- Gestita la serializzazione dei `bigint`; gli errori di risposta vengono registrati con stack tramite `sendErrorMessage`.\n- `AccessiHttpExceptionFilter` registra gli errori con stack per facilitare la diagnosi.\n\n## 2.0.0-dev.45 (2026-10-12)\n\n### Correzioni\n- `set-gdpr` e l'aggiornamento di `flagGdpr` sono idempotenti: `UPDATE OR INSERT` su `UTENTI_GDPR` con `MATCHING (CODUTE, DATACC)`.\n- Evitate violazioni di chiave primaria e risposte 500 quando il consenso GDPR viene registrato pi\xF9 volte nello stesso giorno.\n\n## 2.0.0-dev.44\n\n### Aggiunte\n- Endpoint pubblico `GET /api/accessi/auth/bootstrap-admin/status` (`getAdminBootstrapStatus`): la console mostra la sezione \"Crea utente admin\" **solo** se `adminBootstrap.enabled` \xE8 `true`.\n\n### Correzioni\n- Rimossi i caratteri corrotti (mojibake UTF-8 \u2194 Windows-1252) da DTO, servizi, controller, console, OpenAPI e client generato (es. \"L'email \xE8 gi\xE0 associata a un utente Accessi.\").\n\n## 2.0.0-dev.43\n\n### Cambiamenti\n- Gerarchia dei ruoli formalizzata **ADMIN > SUPER > utente**: un superutente non puo' gestire un admin su `update-user`, `delete-user` (soft e hard), `set-password`, `force-password-reset`, `set-stato`, `assign-roles` e `assign-permissions` (`ensureCanManageTargetUser`, 403 se violato).\n- Guard **ultimo admin attivo**: non puo' essere disattivato, bloccato, eliminato o privato del flag Admin (400), per evitare il lockout della console.\n- Creazione utente (`create-managed-user` e registrazione pubblica) resa **atomica** rispetto all'invio email: fail-fast se SMTP non configurato e rollback dell'utente creato se l'invio fallisce (`ACCESSI_USER_INVITE_EMAIL_FAILED`).\n- Console: form password separato (Invio imposta la password invece di salvare il profilo), scheda utente in sola lettura per target di rango superiore.\n- SSO: anche il superutente puo' creare utenti SSO e gestirne identita/policy (`federated-auth/users/*`) sugli utenti non-admin; il catalogo dei provider SSO resta riservato agli admin.\n- Coerenza: i filtri utente (`saveFiltriUtente`) rispettano la gerarchia; il flag GDPR impostato dal profilo allinea anche `DATGDPR`; la scheda in sola lettura disabilita anche il collegamento SSO.\n- Eliminazione definitiva: se l'utente ha dati collegati (tabelle esterne/FK) risponde 400 con `code: ACCESSI_USER_DELETE_HAS_REFERENCES` e messaggio esplicito, suggerendo il soft delete (la hard delete resta invariata quando non ci sono vincoli).\n- Consenso GDPR completo: `update-user` con `flagGdpr` scrive anche la riga storica `UTENTI_GDPR`; in console il pulsante \"Registra consenso GDPR\" usa `set-gdpr`.\n- Console: aggiunto il pulsante \"Invia reset password legacy\" (super e admin).\n- Console: **auto-ingresso dal frontend** tramite ticket monouso (`POST /api/accessi/console/entry` + apertura di `#entry=<ticket>`), senza JWT nell'URL; il ticket e' legato all'utente, dura 60s, e' monouso e lo scambio e' protetto da rate limit (`publicAuthRateLimit.consoleEntry`).\n\n## 2.0.0-dev.42\n\n### Aggiunte\n- Gestione dei flag di privilegio dalla console Accessi:\n - un admin pu\xF2 assegnare o togliere sia `FLGADMIN` sia `FLGSUPER`;\n - un superutente senza `FLGADMIN` pu\xF2 gestire solo `FLGSUPER`;\n - i flag sono impostabili in creazione (utente locale o SSO) e in modifica profilo.\n- Eliminazione utenti con due modalit\xE0:\n - soft delete (`delete-user`, `STAREG=DELETE`) per admin e superutenti;\n - eliminazione definitiva (`delete-user-permanent`, solo admin) che rimuove in transazione utente e record collegati.\n- Gestione completa dell'utente dalla console (admin): anagrafica estesa (cellulare, lingua, avatar, metadata JSON), GDPR, scadenza password, tutti i filtri (`FILTRI`) e **impostazione diretta della password** (`POST /api/accessi/user/set-password/:codiceUtente`), senza accesso al database.\n- Scritture utente idempotenti: `UTENTI_CONFIG` in `UPDATE OR INSERT ... MATCHING (CODUTE)`; i campi non inviati restano invariati.\n- Policy password moderna quando si imposta una password (reset, set-password admin, bootstrap): minimo 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comune. In caso di violazione risposta 400 con `code: ACCESSI_WEAK_PASSWORD` e `details`. La policy **non** si applica al login, cos\xEC le password preesistenti deboli continuano a funzionare.\n\n### Correzioni\n- Il backend verifica i flag in base al ruolo dell'utente connesso (`ensurePrivilegeFlagChanges`): inviare un flag non consentito restituisce 403.\n\n## 2.0.0-dev.33\n\n### Cambiamenti\n- Rimosse le logiche applicative residue dallo schema Accessi:\n - `MENU_GRP.NOMECAMPO`\n - `UTENTI.ENABLEIA`\n - `UTENTI_CONFIG.PAGDEF`\n - `UTENTI.CELLUTE`\n- Telefono canonico: `UTENTI_CONFIG.CELLULARE` (allargato a `VARCHAR(30)`).\n- Migrazione automatica: copia `UTENTI.CELLUTE` \u2192 `UTENTI_CONFIG.CELLULARE`, poi `DROP` delle colonne obsolete.\n- Ruoli chiariti:\n - `FLGSUPER`: superutente, riceve **tutte** le abilitazioni al livello massimo (30) dopo il login; in console vede **solo** la sezione **Utenti**.\n - `FLGADMIN`: accede a **tutta** la console (catalogo, token, SSO, utenti) ma **non** influisce sulle abilitazioni.\n- Aggiunta la sezione **Changelog** in console (sotto la Wiki).\n\n### Note\n- `FILTRI` e `FILTRI_TIPO` restano invariati (piano di migrazione futuro).\n- I backend che usavano i campi rimossi devono gestirli in tabelle proprie.\n\n## 2.0.0-dev.32\n\n### Cambiamenti\n- Rimosso il supporto ai flag applicativi in `UTENTI_CONFIG`: `FLGMOP`, `FLGPIANA`, `FLGADDETTI`, `FLGOSPITI`, `FLGPIANARFID`, `FLGCONTA`, `FLGCUBI`, `FLGCICLPASS`, `FLGDIPENDENTI`, `FLGINVENTARI`, `CAUMOV`, `NUMMAC`, `RAGSOCCLI`.\n- Rinominata la colonna `FLGADMINCONFIG` in `FLGADMIN` (con copia dei valori e `DROP`).\n\n## 2.0.0-dev.31\n\n### Aggiunte\n- Esposizione dei servizi Accessi all'host Express (`getAccessiModuleServices`) per delegare login, SSO e token senza reimplementarli.\n\n### Correzioni\n- `UserService.getUsers`: filtri estesi (`codiceUtente[]`, `numRep[]`, `codDipendente`, `cellulare`, `tipFil`, `flagSuper`).\n\n## 2.0.0-dev.24\n\n### Cambiamenti (breaking)\n- Chiavi JWT derivate per scopo (`access`, `reset`, `2fa`) e claim `typ` obbligatorio: gli access token precedenti non sono pi\xF9 validi.\n- Endpoint Allegati protetti di default.\n- Pool di connessioni attivo di default (`poolSize` 5).\n\n### Aggiunte\n- Service token macchina-a-macchina (gestione, guard, console).\n- CRUD completo dei cataloghi dalla console Accessi.\n- Supporto Firebird 2.5 nel migrator e nei test di integrazione.\n";
1537
+ var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\n\n## Non rilasciato\n\n### Cambiamenti\n- Console: rimosse le sezioni **Filtri utente** e **Tipi filtro**. I filtri applicativi sono deprecati e non vengono pi\xF9 letti n\xE9 scritti dalla console; le tabelle restano invariate e la gestione torna manuale sul database.\n- Console: le conferme operative (es. \"Ruoli aggiornati\") ora compaiono come notifiche in alto a destra e scompaiono automaticamente.\n- Console: il salvataggio del profilo non invia pi\xF9 i campi della tabella `FILTRI`, evitando payload rifiutati dal backend. `UserService` tocca `FILTRI` solo se il chiamante invia esplicitamente almeno un campo legacy.\n- Console: scheda utente riorganizzata. \"Indietro\" resta nella barra superiore; stato registrazione, disattivazione ed eliminazione definitiva sono in un form coerente \"Stato e sicurezza\", con icone e tooltip allineati.\n- Console: pagina Changelog a larghezza piena, con intestazioni di versione evidenziate e sezioni orizzontali leggibili.\n- Seed `npm run accessi:seed-admin`: crea o aggiorna un admin completo (`FLGSUPER = 1` e `FLGADMIN = 1`) e corregge il nome colonna usato in precedenza (`FLGADMIN`).\n- Console: la pagina di default nella scheda utente \xE8 ora un autocomplete con ricerca. Propone tutte le rotte del catalogo menu, si filtra scrivendo ed \xE8 ancora possibile inserire un valore libero.\n- Autorizzazioni: i grant effettivi sommano ruoli e permessi diretti prendendo il livello massimo per menu. Un permesso diretto con livello inferiore (o 0) non annulla pi\xF9 il grant del ruolo.\n- Ruoli legacy: un menu senza descrizione non viene pi\xF9 scartato e un `TIPABI` mancante viene letto come livello 10, evitando utenti con ruolo ma senza accesso.\n- Grant dei ruoli arricchiti con i metadati di navigazione del catalogo menu (`pagina`, icona, gruppo, tipo): il frontend riceve gli stessi campi dei grant diretti e pu\xF2 costruire correttamente menu e rotte.\n\n## 2.0.0-dev.50 (2026-10-15)\n\n### Cambiamenti\n- ORM: `Row` e `DbValue` sono i tipi predefiniti di `Orm.query` e `Orm.execute`; `transactionQuery` usa `Row[]`. Gli usi generici non devono pi\xF9 dichiarare `Record<string, unknown>`.\n- Build: il contratto OpenAPI generato (`src/accessi-module/openapi/`) \xE8 ignorato da Git e viene rigenerato da `npm run generate:accessi-openapi`.\n\n## 2.0.0-dev.49 (2026-10-15)\n\n### Cambiamenti\n- Documentazione HTTP rinominata da Swagger a **OpenAPI**: il modulo unificato espone `/accessi/openapi` e `/accessi/openapi.json`; `setupSwagger` usa `/openapi` come default.\n- Il path \xE8 configurabile tramite `EmilsoftwareOptions.swagger` (utile per far convivere pi\xF9 documentazioni).\n- Allineati log di avvio, Wiki e dispatch delle rotte del modulo.\n- L'alias storico `<path>-json` resta supportato.\n\n### Note\n- I consumer che aprivano `/swagger` o `/accessi/swagger` devono aggiornare l'URL alla nuova documentazione OpenAPI.\n\n## 2.0.0-dev.48 (2026-10-13)\n\n### Correzioni\n- `RestUtilities.sendErrorMessage` restituisce status HTTP specifici (400/401/403/4xx/5xx) e codici generici (`VALIDATION_ERROR`, `UNAUTHORIZED`, `FORBIDDEN`, `INTERNAL_ERROR`, `REQUEST_ERROR`), senza usare codici `ACCESSI_*` nel business helper.\n- Il messaggio restituito \xE8 quello reale dell'eccezione; gli errori testuali di validazione vengono riconosciuti come 400.\n\n## 2.0.0-dev.47 (2026-10-13)\n\n### Correzioni\n- Errore schema database reso generico: codice `DATABASE_SCHEMA_OUTDATED` e messaggio senza riferimenti specifici ad Accessi.\n\n## 2.0.0-dev.46 (2026-10-12)\n\n### Correzioni\n- `sendBaseResponse` resta retro-compatibile quando il terzo argomento \xE8 un tag stringa invece dello status HTTP.\n- Gestita la serializzazione dei `bigint`; gli errori di risposta vengono registrati con stack tramite `sendErrorMessage`.\n- `AccessiHttpExceptionFilter` registra gli errori con stack per facilitare la diagnosi.\n\n## 2.0.0-dev.45 (2026-10-12)\n\n### Correzioni\n- `set-gdpr` e l'aggiornamento di `flagGdpr` sono idempotenti: `UPDATE OR INSERT` su `UTENTI_GDPR` con `MATCHING (CODUTE, DATACC)`.\n- Evitate violazioni di chiave primaria e risposte 500 quando il consenso GDPR viene registrato pi\xF9 volte nello stesso giorno.\n\n## 2.0.0-dev.44\n\n### Aggiunte\n- Endpoint pubblico `GET /api/accessi/auth/bootstrap-admin/status` (`getAdminBootstrapStatus`): la console mostra la sezione \"Crea utente admin\" **solo** se `adminBootstrap.enabled` \xE8 `true`.\n\n### Correzioni\n- Rimossi i caratteri corrotti (mojibake UTF-8 \u2194 Windows-1252) da DTO, servizi, controller, console, OpenAPI e client generato (es. \"L'email \xE8 gi\xE0 associata a un utente Accessi.\").\n\n## 2.0.0-dev.43\n\n### Cambiamenti\n- Gerarchia dei ruoli formalizzata **ADMIN > SUPER > utente**: un superutente non puo' gestire un admin su `update-user`, `delete-user` (soft e hard), `set-password`, `force-password-reset`, `set-stato`, `assign-roles` e `assign-permissions` (`ensureCanManageTargetUser`, 403 se violato).\n- Guard **ultimo admin attivo**: non puo' essere disattivato, bloccato, eliminato o privato del flag Admin (400), per evitare il lockout della console.\n- Creazione utente (`create-managed-user` e registrazione pubblica) resa **atomica** rispetto all'invio email: fail-fast se SMTP non configurato e rollback dell'utente creato se l'invio fallisce (`ACCESSI_USER_INVITE_EMAIL_FAILED`).\n- Console: form password separato (Invio imposta la password invece di salvare il profilo), scheda utente in sola lettura per target di rango superiore.\n- SSO: anche il superutente puo' creare utenti SSO e gestirne identita/policy (`federated-auth/users/*`) sugli utenti non-admin; il catalogo dei provider SSO resta riservato agli admin.\n- Coerenza: i filtri utente (`saveFiltriUtente`) rispettano la gerarchia; il flag GDPR impostato dal profilo allinea anche `DATGDPR`; la scheda in sola lettura disabilita anche il collegamento SSO.\n- Eliminazione definitiva: se l'utente ha dati collegati (tabelle esterne/FK) risponde 400 con `code: ACCESSI_USER_DELETE_HAS_REFERENCES` e messaggio esplicito, suggerendo il soft delete (la hard delete resta invariata quando non ci sono vincoli).\n- Consenso GDPR completo: `update-user` con `flagGdpr` scrive anche la riga storica `UTENTI_GDPR`; in console il pulsante \"Registra consenso GDPR\" usa `set-gdpr`.\n- Console: aggiunto il pulsante \"Invia reset password legacy\" (super e admin).\n- Console: **auto-ingresso dal frontend** tramite ticket monouso (`POST /api/accessi/console/entry` + apertura di `#entry=<ticket>`), senza JWT nell'URL; il ticket e' legato all'utente, dura 60s, e' monouso e lo scambio e' protetto da rate limit (`publicAuthRateLimit.consoleEntry`).\n\n## 2.0.0-dev.42\n\n### Aggiunte\n- Gestione dei flag di privilegio dalla console Accessi:\n - un admin pu\xF2 assegnare o togliere sia `FLGADMIN` sia `FLGSUPER`;\n - un superutente senza `FLGADMIN` pu\xF2 gestire solo `FLGSUPER`;\n - i flag sono impostabili in creazione (utente locale o SSO) e in modifica profilo.\n- Eliminazione utenti con due modalit\xE0:\n - soft delete (`delete-user`, `STAREG=DELETE`) per admin e superutenti;\n - eliminazione definitiva (`delete-user-permanent`, solo admin) che rimuove in transazione utente e record collegati.\n- Gestione completa dell'utente dalla console (admin): anagrafica estesa (cellulare, lingua, avatar, metadata JSON), GDPR, scadenza password, tutti i filtri (`FILTRI`) e **impostazione diretta della password** (`POST /api/accessi/user/set-password/:codiceUtente`), senza accesso al database.\n- Scritture utente idempotenti: `UTENTI_CONFIG` in `UPDATE OR INSERT ... MATCHING (CODUTE)`; i campi non inviati restano invariati.\n- Policy password moderna quando si imposta una password (reset, set-password admin, bootstrap): minimo 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comune. In caso di violazione risposta 400 con `code: ACCESSI_WEAK_PASSWORD` e `details`. La policy **non** si applica al login, cos\xEC le password preesistenti deboli continuano a funzionare.\n\n### Correzioni\n- Il backend verifica i flag in base al ruolo dell'utente connesso (`ensurePrivilegeFlagChanges`): inviare un flag non consentito restituisce 403.\n\n## 2.0.0-dev.33\n\n### Cambiamenti\n- Rimosse le logiche applicative residue dallo schema Accessi:\n - `MENU_GRP.NOMECAMPO`\n - `UTENTI.ENABLEIA`\n - `UTENTI_CONFIG.PAGDEF`\n - `UTENTI.CELLUTE`\n- Telefono canonico: `UTENTI_CONFIG.CELLULARE` (allargato a `VARCHAR(30)`).\n- Migrazione automatica: copia `UTENTI.CELLUTE` \u2192 `UTENTI_CONFIG.CELLULARE`, poi `DROP` delle colonne obsolete.\n- Ruoli chiariti:\n - `FLGSUPER`: superutente, riceve **tutte** le abilitazioni al livello massimo (30) dopo il login; in console vede **solo** la sezione **Utenti**.\n - `FLGADMIN`: accede a **tutta** la console (catalogo, token, SSO, utenti) ma **non** influisce sulle abilitazioni.\n- Aggiunta la sezione **Changelog** in console (sotto la Wiki).\n\n### Note\n- `FILTRI` e `FILTRI_TIPO` restano invariati (piano di migrazione futuro).\n- I backend che usavano i campi rimossi devono gestirli in tabelle proprie.\n\n## 2.0.0-dev.32\n\n### Cambiamenti\n- Rimosso il supporto ai flag applicativi in `UTENTI_CONFIG`: `FLGMOP`, `FLGPIANA`, `FLGADDETTI`, `FLGOSPITI`, `FLGPIANARFID`, `FLGCONTA`, `FLGCUBI`, `FLGCICLPASS`, `FLGDIPENDENTI`, `FLGINVENTARI`, `CAUMOV`, `NUMMAC`, `RAGSOCCLI`.\n- Rinominata la colonna `FLGADMINCONFIG` in `FLGADMIN` (con copia dei valori e `DROP`).\n\n## 2.0.0-dev.31\n\n### Aggiunte\n- Esposizione dei servizi Accessi all'host Express (`getAccessiModuleServices`) per delegare login, SSO e token senza reimplementarli.\n\n### Correzioni\n- `UserService.getUsers`: filtri estesi (`codiceUtente[]`, `numRep[]`, `codDipendente`, `cellulare`, `tipFil`, `flagSuper`).\n\n## 2.0.0-dev.24\n\n### Cambiamenti (breaking)\n- Chiavi JWT derivate per scopo (`access`, `reset`, `2fa`) e claim `typ` obbligatorio: gli access token precedenti non sono pi\xF9 validi.\n- Endpoint Allegati protetti di default.\n- Pool di connessioni attivo di default (`poolSize` 5).\n\n### Aggiunte\n- Service token macchina-a-macchina (gestione, guard, console).\n- CRUD completo dei cataloghi dalla console Accessi.\n- Supporto Firebird 2.5 nel migrator e nei test di integrazione.\n";
1537
1538
 
1538
1539
  // src/accessi-module/Console/help.ts
1539
1540
  var p2 = (text) => ({ kind: "p", text });
@@ -252,6 +252,7 @@ exports.WIKI_SECTIONS = [
252
252
  blocks: [
253
253
  code('http', `POST /api/accessi/auth/login\nContent-Type: application/json\n\n{ "email": "utente@example.com", "password": "Segreta123!" }`, 'Login con password'),
254
254
  p(`La risposta include Result.token (il JWT) e il profilo utente con ruoli e permessi. Se l'account è passwordless, ometti la password: il server risponde con un challenge via email (vedi la sezione su 2FA e passwordless).`),
255
+ note('info', `Autorizzazioni lato client`, `La fonte autorevole è Result.userGrants.grants: contiene i grant effettivi (ruoli + permessi diretti, livello massimo). Non leggere i permessi dal JWT e non usare Result.utente.permissions: non sono popolati. Per i superutenti grants contiene tutti i menu attivi al livello 30.`),
255
256
  good('ts', [
256
257
  "const res = await fetch('/api/accessi/auth/login', {",
257
258
  " method: 'POST',",
@@ -438,7 +439,7 @@ exports.WIKI_SECTIONS = [
438
439
  title: 'Ruoli e permessi',
439
440
  summary: 'Assegnazioni sostitutive e composizione dei grant.',
440
441
  blocks: [
441
- p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant diretti prevalgono su quelli dei ruoli; i superutenti vedono tutti i menu attivi.`),
442
+ p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant effettivi sommano ruoli e permessi diretti prendendo il livello massimo per menu; un livello 0 non annulla un grant del ruolo. I superutenti ricevono tutti i menu attivi al livello massimo.`),
442
443
  table(['Metodo', 'Percorso', 'Descrizione'], [
443
444
  ['GET', '/api/accessi/permission/roles', 'Ruoli con i menu associati.'],
444
445
  ['POST', '/api/accessi/permission/create-role', 'Crea un ruolo (i menu sono obbligatori).'],
@@ -1 +1 @@
1
- {"version":3,"file":"wiki.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/wiki.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Contenuto della Wiki della console Accessi.\n *\n * I contenuti sono dati strutturati (non HTML) così da poter essere resi in modo\n * coerente dalla console e convertiti in un digest compatto per l'IA. Nessun\n * segreto o dato specifico di un ambiente deve finire qui.\n */\n\nexport type WikiTone = 'info' | 'ok' | 'warn' | 'danger';\n\nexport type WikiBlock =\n | { kind: 'p'; text: string }\n | { kind: 'h'; text: string }\n | { kind: 'list'; items: string[]; ordered?: boolean }\n | { kind: 'code'; language: string; title?: string; code: string; variant?: 'good' | 'bad' }\n | { kind: 'callout'; tone: WikiTone; title?: string; text: string }\n | { kind: 'table'; head: string[]; rows: string[][] };\n\nexport interface WikiSection {\n id: string;\n group: string;\n title: string;\n summary: string;\n blocks: WikiBlock[];\n}\n\nconst p = (text: string): WikiBlock => ({ kind: 'p', text });\nconst h = (text: string): WikiBlock => ({ kind: 'h', text });\nconst list = (items: string[], ordered = false): WikiBlock => ({ kind: 'list', items, ordered });\nconst code = (language: string, codeText: string, title?: string, variant?: 'good' | 'bad'): WikiBlock => ({ kind: 'code', language, title, code: codeText, variant });\nconst good = (language: string, codeText: string, title?: string): WikiBlock => code(language, codeText, title, 'good');\nconst bad = (language: string, codeText: string, title?: string): WikiBlock => code(language, codeText, title, 'bad');\nconst note = (tone: WikiTone, title: string, text: string): WikiBlock => ({ kind: 'callout', tone, title, text });\nconst table = (head: string[], rows: string[][]): WikiBlock => ({ kind: 'table', head, rows });\n\nexport const WIKI_SECTIONS: WikiSection[] = [\n // -------------------------------------------------------------------------\n {\n id: 'panoramica',\n group: 'Per iniziare',\n title: 'Panoramica e architettura',\n summary: 'Che cosa fa Accessi, come si incastra nel backend e come scorre una richiesta.',\n blocks: [\n p(`Accessi è un modulo Node/NestJS che aggiunge a un backend già esistente: autenticazione JWT, autorizzazione per ruoli e menu, gestione utenti, reset password, codici email (2FA e passwordless), SSO generico, token macchina-a-macchina e una console amministrativa. Il database è Firebird/InterBase.`),\n h(`Che cosa possiede il modulo`),\n list([\n `Le rotte sotto /api/accessi/* (login, utenti, ruoli, menu, SSO, token, console).`,\n `Lo schema database del dominio Accessi, riconciliato automaticamente al bootstrap.`,\n `Il middleware di autorizzazione authorizeAccessi per le rotte della tua applicazione.`,\n `La console web su /api/accessi/console.`,\n ]),\n h(`Che cosa resta alla tua applicazione`),\n list([\n `Il proprio frontend e le proprie rotte di business.`,\n `La validazione dei token dei provider SSO esterni (Accessi non li vede).`,\n `I segreti: jwtOptions.secret, SMTP, database ed encryptionKey non raggiungono mai il browser.`,\n ]),\n h(`Flusso di una richiesta protetta`),\n list([\n `Il client chiama una rotta della tua app con header Authorization: Bearer <jwt>.`,\n `La tua rotta esegue authorizeAccessi, che verifica firma HS256, scadenza e stato utente corrente.`,\n `Se passi un requisito, il modulo carica ruoli e grant dell'utente e li valuta.`,\n `Su successo req.user, req.data e (se richiesto) req.userGrants sono popolati; altrimenti risponde con il contratto errore Accessi.`,\n ], true),\n note('info', `Due modalità di bootstrap`, `Express esistente: initializeAccessiModule(app, options). NestJS esistente: AccessiModule.forRoot(options) nei tuoi imports. Non usarle entrambe.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'installazione',\n group: 'Per iniziare',\n title: 'Installazione',\n summary: 'Dipendenze, peer e build.',\n blocks: [\n code('bash', `npm install emilsoftware-utilities`, 'Installazione'),\n p(`La libreria dipende da node-firebird per il database e da Express 5 come peer per il montaggio. È consigliato Node 18 o superiore.`),\n note('warn', `Un solo modulo per processo`, `AccessiModule è @Global(): inizializzalo una sola volta per processo. In caso di più istanze, usa un processo per istanza.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'bootstrap-express',\n group: 'Per iniziare',\n title: 'Bootstrap su Express esistente',\n summary: 'initializeAccessiModule: ordine corretto e attesa prima di listen.',\n blocks: [\n p(`Chiama initializeAccessiModule prima di aprire la porta HTTP. Durante il bootstrap, authorizeAccessi risponde 503 ACCESSI_AUTH_INITIALIZING invece di autorizzare richieste parziali.`),\n good('ts', [\n \"import express from 'express';\",\n \"import { initializeAccessiModule, authorizeAccessi } from 'emilsoftware-utilities';\",\n '',\n 'const app = express();',\n 'app.use(express.json());',\n '',\n 'await initializeAccessiModule(app, options); // prima di app.listen',\n '',\n \"app.get('/api/ordini', authorizeAccessi, (req, res) => {\",\n ' res.json({ utente: req.user });',\n '});',\n '',\n \"app.get('/api/ordini/scrivi', (req, res, next) =>\",\n ' authorizeAccessi(req, res, next, {',\n \" requirements: [{ menuCode: 'ORDINI', minPermissionLevel: 20 }],\",\n ' }),',\n '(req, res) => res.json({ ok: true }));',\n '',\n 'app.listen(3000);',\n ].join('\\n'), 'Express + Accessi'),\n bad('ts', [\n 'const app = express();',\n 'app.listen(3000); // porta aperta prima del bootstrap',\n 'await initializeAccessiModule(app, options); // le richieste nel frattempo ricevono 503',\n ].join('\\n'), 'Non aprire la porta prima del bootstrap'),\n h(`Rotte montate`),\n p(`Accessi intercetta solo /api/accessi/*, /accessi/openapi* e /accessi/openapi.json. Tutte le altre rotte passano intatte al tuo stack Express.`),\n note('ok', `req.user`, `authorizeAccessi popola req.user con il payload utente corrente (utente, codiceUtente, flag) e req.data come alias storico. req.userGrants è presente solo se hai richiesto dei requisiti.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'bootstrap-nest',\n group: 'Per iniziare',\n title: 'Bootstrap su NestJS esistente',\n summary: 'AccessiModule.forRoot e uso dei provider esportati.',\n blocks: [\n p(`Se la tua applicazione è già NestJS, importa AccessiModule.forRoot(options). Il modulo è @Global e riesporta i servizi (AuthService, UserService, PermissionService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, AuthenticateGenService).`),\n good('ts', [\n \"import { Module } from '@nestjs/common';\",\n \"import { AccessiModule } from 'emilsoftware-utilities';\",\n '',\n '@Module({',\n ' imports: [AccessiModule.forRoot(options)],',\n '})',\n 'export class AppModule {}',\n ].join('\\n'), 'app.module.ts'),\n p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'opzioni',\n group: 'Configurazione',\n title: 'AccessiOptions (riferimento completo)',\n summary: 'Ogni campo di configurazione con tipo, obbligo e note.',\n blocks: [\n table(\n ['Campo', 'Tipo', 'Obbligo', 'Note'],\n [\n ['databaseOptions', 'Options (node-firebird)', 'sì', 'Connessione Firebird al database Accessi.'],\n ['confirmationEmailUrl', 'string', 'sì', 'Base URL usata per il reset password.'],\n ['confirmationEmailReturnUrl', 'string', 'sì', 'URL di ritorno dopo il reset.'],\n ['confirmationEmailPrefix', 'string', 'no', 'Prefisso passato alla pagina di reset.'],\n ['customResetPage', 'string', 'no', 'Sostituisce la pagina HTML di reset inclusa.'],\n ['encryptionKey', 'string', 'sì', 'Compatibilità e migrazione delle password legacy.'],\n ['mockDemoUser', 'boolean', 'sì', 'Utenti fittizi admin e demo solo fuori produzione.'],\n ['passwordExpiration', 'boolean', 'no', 'Attiva il controllo della scadenza password al login.'],\n ['passwordExpirationDays', 'number', 'no', 'Valore predefinito: 90 giorni.'],\n ['legacyPasswordMigrationOnStartup', 'boolean', 'no', 'Predefinito true: converte le password legacy.'],\n ['autoUpdateDatabase', 'boolean', 'no', 'Predefinito true: riconcilia lo schema al boot.'],\n ['jwtOptions', '{ secret, expiresIn }', 'sì', 'Firma HS256 e durata dei JWT Accessi.'],\n ['emailOptions', 'EmailOptions', 'sì', 'SMTP per reset e codici email.'],\n ['publicAuthRateLimit', 'PublicAuthRateLimitOptions', 'no', 'Limiti in-memory sugli endpoint pubblici.'],\n ['publicRegistration', '{ enabled }', 'no', 'Registrazione pubblica: disattivata per impostazione predefinita.'],\n ['federatedAuthentication', 'FederatedAuthenticationOptions', 'no', `Abilita l'SSO generico.`],\n ['extensionFieldsOptions', 'ExtensionFieldsOptions[]', 'no', 'Tabelle esterne allegate al profilo.'],\n ['serviceTokens', '{ enabled, defaultTtlDays }', 'no', 'Token macchina-a-macchina, abilitati per impostazione predefinita.'],\n ['catalogScripts', '{ enabled, folder, ... }', 'no', 'Catalog migrations SQL eseguite dopo la riconciliazione dello schema.'],\n ['adminBootstrap', '{ enabled, superUser }', 'no', 'Token casuale a ogni avvio per creare il primo admin dalla console.'],\n ['publicBaseUrl', 'string', 'no', 'Base URL usata solo per loggare console/swagger/API.'],\n ],\n ),\n good('ts', [\n \"import type { AccessiOptions } from 'emilsoftware-utilities';\",\n '',\n 'export const options: AccessiOptions = {',\n ' databaseOptions: {',\n ' host: process.env.ACCESSI_DB_HOST!,',\n ' port: Number(process.env.ACCESSI_DB_PORT ?? 3050),',\n ' database: process.env.ACCESSI_DB_NAME!,',\n ' user: process.env.ACCESSI_DB_USER!,',\n ' password: process.env.ACCESSI_DB_PASSWORD!,',\n ' },',\n \" confirmationEmailUrl: 'https://app.example.com',\",\n \" confirmationEmailReturnUrl: 'https://app.example.com/login',\",\n ' encryptionKey: process.env.ACCESSI_ENCRYPTION_KEY!,',\n ' mockDemoUser: false,',\n ' passwordExpiration: true,',\n ' passwordExpirationDays: 90,',\n ' jwtOptions: {',\n \" secret: process.env.ACCESSI_JWT_SECRET!, // almeno 32 caratteri casuali, unico per ambiente\",\n \" expiresIn: '24h',\",\n ' },',\n ' emailOptions: {',\n ' host: process.env.SMTP_HOST!,',\n ' port: Number(process.env.SMTP_PORT ?? 587),',\n ' secure: false,',\n ' requireTLS: true,',\n ' tls: { rejectUnauthorized: true },',\n \" from: 'no-reply@example.com',\",\n ' auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },',\n ' },',\n ' publicRegistration: { enabled: false },',\n ' serviceTokens: { enabled: true, defaultTtlDays: 365 },',\n '};',\n ].join('\\n'), 'Configurazione tipica'),\n bad('ts', [\n \"jwtOptions: { secret: 'accessi', expiresIn: '24h' }, // segreto debole e condiviso\",\n 'mockDemoUser: true, // MAI in produzione',\n ].join('\\n'), 'Errori comuni'),\n note('danger', `Segreti`, `Non versionare i segreti e non passarli al frontend. jwtOptions.secret deve essere identico tra le istanze del backend e diverso da quello dei provider SSO.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'database',\n group: 'Configurazione',\n title: 'Schema e aggiornamento del database',\n summary: 'Tabelle, riconciliazione automatica e CLI.',\n blocks: [\n p(`Accessi possiede lo schema del dominio. Con autoUpdateDatabase true (predefinito) lo riconcilia a ogni avvio; con false verifica comunque la compatibilità in sola lettura e blocca gli avvii incompatibili.`),\n h(`Tabelle principali`),\n list([\n `PARAMETRI, UTENTI, UTENTI_CONFIG, UTENTI_PWD, UTENTI_OLDPWD, UTENTI_GDPR`,\n `RUOLI, MENU_GRP, MENU_TIPI, MENU, ABILITAZIONI, RUOLI_MNU, UTENTI_RUOLI`,\n `FILTRI, FILTRI_TIPO`,\n `SSO_PROVIDER, UTENTI_IDENTITA_EXT`,\n `ACCESSI_2FA, ACCESSI_SERVICE_TOKEN, ACCESSI_CATALOG_SCRIPT`,\n ]),\n code('bash', `npm run db:update:accessi # riconcilia lo schema\\nnpm run db:check:accessi # verifica senza modificare`, 'CLI schema'),\n note('warn', `Versioni`, `La libreria richiede uno schema compatibile con la versione corrente (ACCESSI_SCHEMA_VERSION). Se la verifica fallisce, leggi il codice errore ACCESSI_DATABASE_SCHEMA_OUTDATED ed esegui db:update:accessi.`),\n note('info', `Schema esatto`, `La riconciliazione allinea le tabelle Accessi a \"niente in piu, niente in meno\": le colonne non previste vengono segnalate come incompatibili e rimosse automaticamente (dopo rename e copie dei dati legacy). Gli indici, i trigger e i CHECK extra non previsti non vengono tolti, ma un indice non previsto su una tabella Accessi viene riportato come avviso senza bloccare l'avvio.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'catalog-migrations',\n group: 'Configurazione',\n title: 'Catalog migrations SQL',\n summary: 'Allinea menu, ruoli e cataloghi su piu database in modo idempotente.',\n blocks: [\n p(`Gli script SQL applicativi (menu, ruoli, tipi, cataloghi) restano nel repository del backend ospitante e Accessi li esegue dopo la riconciliazione dello schema. Ogni script viene tracciato nella tabella ACCESSI_CATALOG_SCRIPT (nome + checksum), quindi viene applicato una sola volta per database.`),\n h(`Configurazione`),\n good('ts', [\n 'catalogScripts: {',\n \" enabled: true,\",\n \" folder: './db/accessi-catalog', // versionata nel repo del backend\",\n \" onChecksumMismatch: 'error', // 'error' (default) | 'warn' | 'reapply'\",\n \" stopOnError: true,\",\n '}',\n ].join('\\n'), 'AccessiOptions'),\n list([\n 'Gli script sono ordinati per percorso (usa prefissi numerici: 0001_, 0002_, ...).',\n 'Devono essere idempotenti e ripetibili: UPDATE OR INSERT ... MATCHING, MERGE, DELETE+INSERT mirati.',\n 'Non includere utenti: le catalog migrations allineano solo catalogo e configurazione.',\n 'Supportati stringhe, commenti e statement multipli; SET TERM non serve perché gli script sono solo DML.',\n ]),\n note('danger', `Solo righe (DML)`, `Vincolo tassativo: uno script puo eseguire solo INSERT, UPDATE, DELETE, MERGE, SELECT, EXECUTE. Qualunque DDL (CREATE/ALTER/DROP, trigger, procedure, GRANT, transazioni) viene rifiutato. Lo schema si aggiorna solo con db:update:accessi.`),\n note('info', `File non conformi`, `Un file con statement non consentiti non viene applicato ne registrato nel ledger; il run prosegue con gli altri e il report riporta una voce per ogni file con i relativi messaggi di errore (visibili anche con db:catalog:accessi:check).`),\n note('info', `allowDdl`, `Disattivato di default. Impostarlo a true disabilita la validazione e permette DDL: usarlo solo consapevolmente e mai come sostituto dell'updater dello schema.`),\n code('bash', `# Applica (schema + catalog migrations) su un database\\nACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi\\n\\n# Elenca solo quelle da applicare / modificate\\nACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi:check`, 'CLI catalog migrations'),\n note('info', `Idempotenza`, `Uno script gia applicato con lo stesso checksum viene saltato. Se il file cambia dopo l'applicazione, con policy error il run si interrompe: non modificare uno script applicato, aggiungine uno nuovo.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'login',\n group: 'Autenticazione',\n title: 'Login locale e JWT',\n summary: 'Endpoint di login, risposta e gestione della password scaduta.',\n blocks: [\n code('http', `POST /api/accessi/auth/login\\nContent-Type: application/json\\n\\n{ \"email\": \"utente@example.com\", \"password\": \"Segreta123!\" }`, 'Login con password'),\n p(`La risposta include Result.token (il JWT) e il profilo utente con ruoli e permessi. Se l'account è passwordless, ometti la password: il server risponde con un challenge via email (vedi la sezione su 2FA e passwordless).`),\n good('ts', [\n \"const res = await fetch('/api/accessi/auth/login', {\",\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json' },\",\n ' body: JSON.stringify({ email, password }),',\n '});',\n 'const body = await res.json();',\n 'if (body.Result?.challenge) { /* richiedi il codice */ }',\n 'const token = body.Result?.token;',\n ].join('\\n'), 'Chiamata dal frontend'),\n note('warn', `Password scaduta`, `Con passwordExpiration attivo, una password scaduta produce una PasswordExpiredResponse con code PASSWORD_EXPIRED. Il client deve avviare il reset della password.`),\n h(`Verifica del token`),\n code('http', `POST /api/accessi/auth/get-user-by-token\\nContent-Type: application/json\\n\\n{ \"token\": \"<jwt>\" }`, 'get-user-by-token'),\n p(`Usalo per verificare un JWT e ottenere il profilo corrente (utile al bootstrap di una SPA). Restituisce Result.userData.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'passwordless-2fa',\n group: 'Autenticazione',\n title: 'Codici email, 2FA e passwordless',\n summary: 'Verifica in due passi, challenge e policy per utente.',\n blocks: [\n p(`La verifica con codice email è per-utente (flagDueFattori / UTENTI_CONFIG.FLG2FATT) ed è disattivata per impostazione predefinita. Con passwordlessLoginEnabled (FLGPWDLESS, richiede la 2FA attiva) l'accesso avviene con il solo codice email.`),\n table(\n ['Configurazione', 'Accesso locale', 'Accesso SSO'],\n [\n ['2FA disattivata', 'Password', 'Provider'],\n ['2FA attiva', 'Password + codice email', 'Provider + codice email'],\n ['2FA + passwordless', 'Solo codice email', 'Provider + codice email'],\n ],\n ),\n code('json', `{\\n \"Result\": {\\n \"challenge\": {\\n \"twoFactorRequired\": true,\\n \"challengeId\": \"64-caratteri-esadecimali\",\\n \"method\": \"email\",\\n \"expiresAt\": \"2026-09-11T14:00:00.000Z\",\\n \"resendAfterSeconds\": 60\\n }\\n }\\n}`, 'Risposta con challenge'),\n code('http', `POST /api/accessi/auth/two-factor/verify\\n{ \"challengeId\": \"...\", \"code\": \"012345\" }\\n\\nPOST /api/accessi/auth/two-factor/resend\\n{ \"challengeId\": \"...\" }`, 'Verifica e reinvio'),\n list([\n `Il codice ha 6 cifre, dura 10 minuti, consente 5 tentativi totali e al massimo 3 invii per challenge.`,\n `Fino alla verifica non sono presenti token, profilo o grant.`,\n `Il challenge non è un JWT e non va usato nell'header Authorization.`,\n ]),\n bad('ts', [\n '// Sbagliato: usare il challenge come token',\n \"fetch('/api/ordini', { headers: { Authorization: 'Bearer ' + challengeId } }); // 401\",\n ].join('\\n'), 'Il challenge non è un token'),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'reset-password',\n group: 'Autenticazione',\n title: 'Reset password',\n summary: 'Invio email e conferma con token monouso.',\n blocks: [\n code('http', `POST /api/accessi/email/send-reset-password-email\\n{ \"email\": \"utente@example.com\" }\\n\\nPOST /api/accessi/auth/confirm-reset-password/{token}\\n{ \"newPassword\": \"NuovaSegreta123!\" }`, 'Reset in due passi'),\n p(`L'invio non rivela se l'email esiste (anti-enumerazione). Il token di reset è monouso ed è firmato con una chiave dedicata.`),\n note('warn', `Policy password (solo quando si imposta)`, `Reset, set-password admin e bootstrap richiedono almeno 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comuni. Se non conforme l'API risponde 400 con code ACCESSI_WEAK_PASSWORD e l'elenco dei requisiti in details. La policy NON si applica al login: le password preesistenti eventualmente deboli continuano ad autenticare e vengono migrate senza blocco.`),\n note('info', `Pagina personalizzata`, `Imposta customResetPage per usare la tua pagina di reset invece di quella inclusa. confirmationEmailReturnUrl e confirmationEmailPrefix controllano il ritorno.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'middleware',\n group: 'Autorizzazione',\n title: 'authorizeAccessi (middleware)',\n summary: 'Proteggere le rotte della tua app con il JWT Accessi.',\n blocks: [\n p(`authorizeAccessi è un middleware Express che verifica il JWT, ricarica lo stato utente corrente e applica i requisiti. Su successo prosegue; su errore risponde direttamente con il contratto Accessi (non chiamare un tuo next(error)).`),\n good('ts', [\n \"app.get('/api/report',\",\n ' (req, res, next) => authorizeAccessi(req, res, next, {',\n \" requirements: [{ menuCode: 'REPORT', minPermissionLevel: 10 }],\",\n ' }),',\n ' (req, res) => res.json({ user: req.user, grants: req.userGrants }),',\n ');',\n ].join('\\n'), 'Rotta protetta'),\n h(`Che cosa trovi nella request`),\n list([\n `req.user e req.data: payload utente corrente (alias storici).`,\n `req.userGrants: grant effettivi, presente solo quando hai richiesto dei requisiti.`,\n ]),\n bad('ts', [\n \"app.get('/api/report', authorizeAccessi, (req, res) => {\",\n ' // Sbagliato: assumere che req.userGrants esista senza requisiti',\n ' const grants = req.userGrants.grants; // possibile TypeError',\n '});',\n ].join('\\n'), 'Non dare per scontato req.userGrants'),\n note('danger', `Lo stato utente è sempre ricontrollato`, `Un JWT valido non basta: se l'utente è stato bloccato, eliminato o ha cambiato policy, il middleware risponde 401 AUTH_USER_DISABLED.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'requisiti',\n group: 'Autorizzazione',\n title: 'Requisiti e livelli di abilitazione',\n summary: 'DSL accessiRequirement: permission, and, or, not, custom.',\n blocks: [\n p(`I requisiti si compongono con accessiRequirement. La forma storica requirements è un AND di permessi. Il livello è TipoAbilitazione: NESSUNA=0, LETTURA=10, SCRITTURA=20, SPECIAL=30; il confronto è tipoAbilitazione >= soglia.`),\n good('ts', [\n \"import { authorizeAccessi, accessiRequirement as r } from 'emilsoftware-utilities';\",\n '',\n '// AND (entrambi i menu)',\n 'const both = r.and(r.permission(\"ORDINI\", 20), r.permission(\"CLIENTI\", 10));',\n '',\n '// OR',\n 'const either = r.or(r.permission(\"REPORT\", 10), r.permission(\"EXPORT\", 20));',\n '',\n '// NOT + custom',\n 'const policy = r.and(',\n ' r.permission(\"AREA_RISERVATA\", 10),',\n ' r.not(r.permission(\"BLOCCO_TOTALE\", 0)),',\n ');',\n '',\n 'app.get(\"/api/riservato\", (req, res, next) => authorizeAccessi(req, res, next, {',\n ' requirementTree: policy,',\n ' customRequirementHandlers: {',\n ' stessoReparto: async (ctx) => ctx.userCode === Number(ctx.req.query.reparto),',\n ' },',\n '}), handler);',\n ].join('\\n'), 'Policy composte'),\n bad('ts', [\n '// Sbagliato: AND/OR senza figli -> errore di configurazione (500 AUTH_REQUIREMENTS_MISCONFIGURED)',\n 'const empty = r.and();',\n ].join('\\n'), 'Configurazione non valida'),\n note('warn', `custom senza handler`, `Un requisito custom la cui chiave non ha un handler produce 500 AUTH_REQUIREMENTS_MISCONFIGURED: è un errore di programmazione, non un 403.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'utenti',\n group: 'Dominio',\n title: 'Utenti',\n summary: 'Creazione, aggiornamento, stati e GDPR.',\n blocks: [\n h(`Stati di registrazione (STAREG)`),\n table(\n ['Valore', 'Nome', 'Effetto sul login'],\n [\n ['0', 'NULL', 'Non confermato'],\n ['5', 'INSERT', 'Non confermato'],\n ['10', 'INVIO', 'Richiede il rinnovo della password'],\n ['20', 'CONF', 'Valido'],\n ['50', 'DELETE', 'Bloccato (soft delete)'],\n ['99', 'BLOCC', 'Bloccato'],\n ],\n ),\n h(`Endpoint principali (superutente salvo diversamente indicato)`),\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/user/get-users', 'Elenco con paginazione (limit e offset, header X-Total-Count).'],\n ['POST', '/api/accessi/user/create-managed-user', 'Crea un utente locale e invia il reset password.'],\n ['PUT', '/api/accessi/user/update-user/:codiceUtente', 'Aggiorna profilo e policy (campi privilegiati solo superutente).'],\n ['DELETE', '/api/accessi/user/delete-user/:codiceUtente', 'Soft delete: STAREG=DELETE. Admin o superutente.'],\n ['DELETE', '/api/accessi/user/delete-user-permanent/:codiceUtente', 'Eliminazione definitiva: rimuove utente e record collegati. Solo admin.'],\n ['POST', '/api/accessi/user/set-password/:codiceUtente', 'Imposta direttamente la password: hash lato backend e scadenza secondo policy, nessuna email.'],\n ['POST', '/api/accessi/user/set-stato', 'Imposta lo stato di registrazione.'],\n ['PATCH', '/api/accessi/user/set-gdpr/:codiceUtente', 'Registra il consenso GDPR.'],\n ['POST', '/api/accessi/user/register', 'Registrazione pubblica, solo se publicRegistration.enabled.'],\n ],\n ),\n note('warn', `Eliminazione: soft o definitiva`, `Il soft delete (STAREG=DELETE) è disponibile per admin e superutenti ed è reversibile. L'eliminazione definitiva rimuove utente e record collegati in transazione ed è riservata agli admin; il superutente senza flag Admin può solo flaggare l'utente come eliminato. Se l'utente ha dati collegati in altre tabelle (per esempio campi estesi del backend) l'eliminazione definitiva fallisce con code ACCESSI_USER_DELETE_HAS_REFERENCES e messaggio esplicito: usa allora il soft delete.`),\n note('ok', `Creazione con invito atomica`, `La creazione utente con invio email è atomica: se SMTP non è configurato si fallisce prima di scrivere, e se l'invio fallisce l'utente creato viene annullato (codice ACCESSI_USER_INVITE_EMAIL_FAILED). Non restano utenti senza password. In alternativa un admin crea l'utente e imposta la password dal form dedicato.`),\n note('ok', `Admin: controllo completo del profilo`, `Dalla scheda utente un admin modifica tutti i campi Accessi: anagrafica, cellulare, lingua, avatar, pagina di default, metadata JSON, 2FA/passwordless/login con password, flag, GDPR, stato registrazione e scadenza password. La password si imposta direttamente con set-password. Le scritture sono idempotenti (UPDATE OR INSERT su UTENTI_CONFIG) e verificate dal backend; non serve accedere al database.`),\n note('info', `Consenso GDPR`, `Il pulsante \"Registra consenso GDPR\" completa il processo: imposta FLGGDPR e DATGDPR e scrive la riga storica in UTENTI_GDPR (endpoint set-gdpr). Anche impostare flagGdpr via update-user completa il processo con la stessa riga storica.`),\n good('ts', [\n \"await fetch('/api/accessi/user/create-managed-user', {\",\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + adminToken },\",\n ' body: JSON.stringify({',\n \" email: 'nuovo@example.com',\",\n \" nome: 'Mario',\",\n \" cognome: 'Rossi',\",\n ' roles: [1, 2],',\n \" permissions: [{ codiceMenu: 'REPORT', tipoAbilitazione: 10 }],\",\n ' }),',\n '});',\n ].join('\\n'), 'Creazione di un utente gestito'),\n bad('ts', [\n '// Sbagliato: inviare flag privilegiati dal frontend pubblico',\n 'fetch(\"/api/accessi/user/register\", {',\n ' method: \"POST\",',\n ' body: JSON.stringify({ email, flagSuper: true }), // ignorato o negato',\n '});',\n ].join('\\n'), 'Privilegi dal client'),\n note('info', `Campi di dominio`, `UTENTI_CONFIG contiene i campi di dominio Accessi: FLGSUPER, FLGADMIN, FLG2FATT (2FA), FLGPWDLESS (passwordless), FLGPASSWORD (login locale), CODLINGUA, CELLULARE, PAGDEF (pagina di default) e JSON_METADATA. I flag/colonne applicativi (FLGMOP, FLGPIANA, FLGADDETTI, FLGOSPITI, FLGPIANARFID, FLGCONTA, FLGCUBI, FLGCICLPASS, FLGDIPENDENTI, FLGINVENTARI, CAUMOV, NUMMAC, RAGSOCCLI, ENABLEIA, CELLUTE e NOMECAMPO) sono stati rimossi: i backend devono gestirli in tabelle proprie. Il telefono canonico e UTENTI_CONFIG.CELLULARE.`),\n note('info', `Autorizzazioni`, `I gruppi/menu si abilitano tramite ABILITAZIONI, RUOLI_MNU e UTENTI_RUOLI. Il vecchio meccanismo NOMECAMPO (flag applicativo su UTENTI_CONFIG) e stato rimosso.`),\n note('info', `Campi estesi`, `extensionFieldsOptions allega colonne di tabelle applicative al profilo (per esempio la ragione sociale). Le colonne sono in whitelist: non costruirle mai da input HTTP.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'ruoli',\n group: 'Dominio',\n title: 'Ruoli e permessi',\n summary: 'Assegnazioni sostitutive e composizione dei grant.',\n blocks: [\n p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant diretti prevalgono su quelli dei ruoli; i superutenti vedono tutti i menu attivi.`),\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/permission/roles', 'Ruoli con i menu associati.'],\n ['POST', '/api/accessi/permission/create-role', 'Crea un ruolo (i menu sono obbligatori).'],\n ['PUT', '/api/accessi/permission/update-role/:codiceRuolo', 'Aggiorna un ruolo (menu sostitutivi).'],\n ['DELETE', '/api/accessi/permission/delete-role/:codiceRuolo', 'Elimina ruolo, mapping e assegnazioni.'],\n ['POST', '/api/accessi/permission/assign-roles/:codiceUtente', `Sostituisce i ruoli dell'utente.`],\n ['POST', '/api/accessi/permission/assign-permissions/:codiceUtente', 'Sostituisce i permessi diretti.'],\n ['GET', '/api/accessi/permission/grants/:codiceUtente', 'Grant effettivi, permessi diretti e ruoli.'],\n ],\n ),\n good('ts', [\n 'const role = {',\n \" descrizioneRuolo: 'Gestore ordini',\",\n ' menu: [',\n \" { codiceMenu: 'ORDINI', tipoAbilitazione: 20 },\",\n \" { codiceMenu: 'CLIENTI', tipoAbilitazione: 10 },\",\n ' ],',\n '};',\n 'await createRole(role); // POST /api/accessi/permission/create-role',\n '',\n \"// Sostitutivo: questa lista diventa l'intero set di ruoli dell'utente\",\n 'await assignRolesToUser(codiceUtente, { roles: [3, 7] });',\n ].join('\\n'), 'Creazione di un ruolo e assegnazione'),\n bad('ts', [\n \"// Sbagliato: pensare che l'assegnazione sia incrementale\",\n 'await assignRolesToUser(codiceUtente, { roles: [3] });',\n \"// ora l'utente ha SOLO il ruolo 3, non 3 più quelli di prima\",\n ].join('\\n'), 'Assegnazione non additiva'),\n note('warn', `Menu obbligatori`, `create-role e update-role rifiutano un ruolo senza menu. Se ti serve un ruolo senza accessi, non crearlo.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'menu',\n group: 'Dominio',\n title: 'Menu, gruppi e tipi menu',\n summary: 'Catalogo di navigazione e CRUD completo (superutente).',\n blocks: [\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/permission/menus', 'Menu attivi.'],\n ['GET', '/api/accessi/permission/menu-types', 'Catalogo dei tipi menu.'],\n ['GET', '/api/accessi/permission/groups-with-menus?includeDisabled=true', 'Gruppi con menu (anche disabilitati).'],\n ['POST', '/api/accessi/permission/menus', 'Crea un menu.'],\n ['PUT', '/api/accessi/permission/menus/:codiceMenu', 'Aggiorna un menu.'],\n ['DELETE', '/api/accessi/permission/menus/:codiceMenu', 'Elimina un menu (con grant e associazioni ai ruoli).'],\n ['POST', '/api/accessi/permission/menu-groups', 'Crea un gruppo.'],\n ['PUT', '/api/accessi/permission/menu-groups/:codiceGruppo', 'Aggiorna un gruppo.'],\n ['DELETE', '/api/accessi/permission/menu-groups/:codiceGruppo', 'Elimina un gruppo (solo se vuoto).'],\n ['POST/PUT/DELETE', '/api/accessi/permission/menu-types[/:codiceTipo]', 'CRUD dei tipi menu.'],\n ],\n ),\n p(`I codici (CODMNU, CODGRP, CODTIP) sono chiavi immutabili: si indicano in creazione e poi si passano nel percorso. Un gruppo o un tipo si elimina solo se non è referenziato.`),\n good('ts', [\n \"await createMenuGroup({ codiceGruppo: 'A', descrizioneGruppo: 'Amministrazione', ordineGruppo: 1, enabled: true });\",\n \"await createMenuType({ codiceTipo: 'X', descrizioneTipo: 'Operativo' });\",\n \"await createMenu({ codiceMenu: 'ORDINI', descrizioneMenu: 'Ordini', codiceGruppo: 'A', tipo: 'X', ordineMenu: 10, enabled: true });\",\n ].join('\\n'), 'Catalogo in ordine'),\n bad('ts', [\n '// Sbagliato: eliminare un gruppo che contiene menu -> 400 con spiegazione',\n \"await deleteMenuGroup('A'); // contiene N menu\",\n ].join('\\n'), 'Vincoli di integrità'),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'sso',\n group: 'SSO',\n title: 'SSO generico (federated authentication)',\n summary: 'Il backend valida il provider, Accessi gestisce identità e sessioni.',\n blocks: [\n p(`Accessi non valida i token Azure, Google o SAML: è il TUO backend a validare il flusso SSO e a passare solo { provider, subject } verificati a FederatedAuthService. Così issuer, secret e token restano fuori dal modulo.`),\n good('ts', [\n \"import { FederatedAuthService } from 'emilsoftware-utilities';\",\n '',\n '// Nel tuo callback SSO, dopo aver validato il token del provider:',\n 'const verified = { provider: \"azure-ad-acme\", subject: claims.oid };',\n 'const result = await federatedAuthService.authenticate(verified);',\n '',\n 'if (result.login.challenge) {',\n ' // 2FA attiva: restituisci il challenge al frontend',\n ' return res.json({ challenge: result.login.challenge });',\n '}',\n 'return res.json({ login: result.login, token: result.token });',\n ].join('\\n'), 'Login SSO'),\n h(`API amministrative (superutente)`),\n list([\n `GET/POST /api/accessi/federated-auth/providers, PATCH/DELETE providers/:provider`,\n `GET/POST /api/accessi/federated-auth/users/:codiceUtente/identities`,\n `POST /api/accessi/federated-auth/users (crea un utente SSO-only)`,\n `PATCH /api/accessi/federated-auth/users/:codiceUtente/password-login`,\n ]),\n bad('ts', [\n \"// Sbagliato: far validare il token del provider ad Accessi o fidarsi del frontend\",\n 'await federatedAuthService.authenticate({ provider, subject: req.body.email }); // subject non verificato',\n ].join('\\n'), `Non fidarti dell'input`),\n note('warn', `Provider in uso`, `Un provider si elimina solo se nessuna identità è collegata; altrimenti disabilitalo. La chiave del provider deve coincidere con la configurazione del backend.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'service-token',\n group: 'Token di servizio',\n title: 'Token macchina-a-macchina',\n summary: 'Autenticazione tecnica con scope: non è un superutente.',\n blocks: [\n p(`Formato: st_<id>.<segreto> (segreto da 256 bit). Nel database si salva solo lo SHA-256 del segreto; il segreto è mostrato una sola volta. La verifica restituisce { tokenId, label, scopes }: nessun ruolo o grant.`),\n note('danger', `Non è un superutente`, `Un service token non apre le API Accessi: abilita solo le rotte della tua app che proteggi con ServiceTokenGuard.`),\n good('ts', [\n \"import { ServiceTokenGuard, RequireServiceTokenScopes } from 'emilsoftware-utilities';\",\n '',\n '@UseGuards(ServiceTokenGuard)',\n \"@RequireServiceTokenScopes('ia')\",\n '@Post(\"internal/ask\")',\n 'ask() { /* req.accessiServiceToken = { tokenId, label, scopes } */ }',\n ].join('\\n'), 'Rotta tecnica protetta'),\n h(`Gestione (superutente)`),\n list([\n `POST /api/accessi/service-token (label, scopes, expiresAt o ttlDays) -> segreto mostrato una volta`,\n `GET /api/accessi/service-token?includeRevoked=true`,\n `DELETE /api/accessi/service-token/:tokenId (revoca immediata)`,\n `POST /api/accessi/service-token/:tokenId/rotate`,\n ]),\n bad('ts', [\n '// Sbagliato: usare un service token per autenticare un utente umano',\n \"fetch('/api/profilo', { headers: { Authorization: 'Bearer ' + serviceToken } }); // req.user assente\",\n ].join('\\n'), 'Non è un utente'),\n note('warn', `Scadenza`, `Senza expiresAt né defaultTtlDays il token non scade: imposta serviceTokens.defaultTtlDays o una scadenza esplicita.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'console',\n group: 'Console',\n title: 'Console amministrativa',\n summary: 'Accesso, sezioni e che cosa si può fare.',\n blocks: [\n p(`La console è servita dal modulo su /api/accessi/console. Non ha stato proprio: ogni azione chiama le API protette con il JWT dell'utente connesso.`),\n list([\n `Utenti: elenco, creazione locale o SSO, profilo, ruoli, grant diretti, stato, 2FA.`,\n `Ruoli e grant: CRUD dei ruoli con l'albero delle abilitazioni.`,\n `Menu e gruppi: CRUD di gruppi, menu e tipi menu.`,\n `Token di servizio: creazione, rotazione, revoca.`,\n `SSO: provider e identità.`,\n `Wiki e Changelog: documentazione e novità della libreria.`,\n `SQL: esporta il DDL delle entità Accessi (tabelle, generatori, FK, indici, check, trigger) per replicare il database.`,\n ]),\n note('info', `Superutente (FLGSUPER)`, `Riceve TUTTE le abilitazioni al livello massimo (30) dopo il login. In console vede solo la sezione Utenti e gestisce utenti, ruoli e grant.`),\n note('info', `Admin (FLGADMIN)`, `Accede a tutta la console (catalogo, token, SSO, utenti) ma NON conferisce abilitazioni: i menu restano quelli assegnati via ABILITAZIONI/RUOLI_MNU. Gerarchia: ADMIN > SUPER > utente.`),\n note('info', `Gerarchia dei ruoli`, `Vale ADMIN (2) > SUPER (1) > utente comune (0). Un attore può gestire solo utenti di rango pari o inferiore: un superutente può amministrare totalmente utenti comuni e superutenti (profilo, password, flag, ruoli, grant, stato, eliminazione e identità/utenti SSO) ma NON può toccare un admin. Un admin può gestire chiunque. La regola è imposta dal backend (ensureCanManageTargetUser): un tentativo non consentito restituisce 403.`),\n note('info', `Gestione dei flag`, `Dalla scheda utente i flag sono mostrati in base al ruolo di chi è connesso: un admin può assegnare o togliere sia il flag Admin sia il flag Superutente; un superutente senza flag Admin può gestire solo il flag Superutente. I flag si impostano anche in creazione (utente locale o SSO).`),\n note('warn', `Ultimo admin`, `Per non restare chiusi fuori dalla console, l'ultimo admin attivo non può essere disattivato, bloccato, eliminato né privato del flag Admin: l'operazione restituisce 400 finché non viene promosso un altro admin.`),\n note('info', `Accesso`, `Apri /api/accessi/console e accedi con un utente con FLGSUPER o FLGADMIN. Le sezioni sono raggiungibili anche tramite deep link (per esempio /api/accessi/console/menu-e-gruppi/menu).`),\n note('info', `Bootstrap admin`, `Con adminBootstrap.enabled il backend stampa a ogni avvio un token casuale (monouso, valido fino al riavvio). La sezione \"Crea utente admin\" nella schermata di login compare solo quando adminBootstrap è abilitato (stato esposto da GET /api/accessi/auth/bootstrap-admin/status): incolla il token e crea il primo admin senza email di conferma. La password è opzionale: se assente viene generata e mostrata una sola volta.`),\n note('ok', `Auto-ingresso dal frontend`, `Se un frontend ha già una sessione Accessi valida e l'utente è abilitato alla console, può aprire la console senza rifare il login: \\`POST /api/accessi/console/entry\\` con \\`Authorization: Bearer <jwt>\\` restituisce un ticket monouso; apri poi \\`/api/accessi/console#entry=<ticket>\\`. Il JWT non finisce mai nell'URL; il ticket dura 60 secondi ed è utilizzabile una sola volta (endpoint di scambio protetto da rate limit).`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'errori',\n group: 'Riferimenti',\n title: 'Contratto errori e codici',\n summary: 'Formato della risposta e codici stabili ACCESSI_* e AUTH_*.',\n blocks: [\n p(`Gli errori HTTP delle API Accessi usano un contratto stabile: severity, status, statusCode, code, error (campo legacy uguale a code), message e details per gli errori di validazione.`),\n code('json', `{\\n \"severity\": \"error\",\\n \"status\": 400,\\n \"statusCode\": 2,\\n \"code\": \"ACCESSI_VALIDATION_ERROR\",\\n \"error\": \"ACCESSI_VALIDATION_ERROR\",\\n \"message\": \"La richiesta contiene dati non validi.\",\\n \"details\": [\"email: deve essere una stringa.\"]\\n}`, 'Errore di validazione'),\n table(\n ['Codice', 'Quando'],\n [\n ['ACCESSI_VALIDATION_ERROR', 'Body o parametri non validi (400).'],\n ['ACCESSI_AUTH_INITIALIZING', 'Bootstrap in corso (503).'],\n ['ACCESSI_AUTH_NOT_INITIALIZED', 'Modulo non inizializzato (500).'],\n ['AUTH_HEADER_MISSING / AUTH_TOKEN_MISSING', 'Header Authorization assente (401).'],\n ['AUTH_TOKEN_INVALID', 'JWT non valido o scaduto (401).'],\n ['AUTH_USER_DISABLED', 'Utente non più autorizzato (401).'],\n ['AUTH_INSUFFICIENT_PERMISSIONS', 'Requisiti non soddisfatti (403).'],\n ['AUTH_REQUIREMENTS_MISCONFIGURED', 'Policy malformata o handler custom mancante (500).'],\n ['ACCESSI_DATABASE_SCHEMA_OUTDATED', 'Schema del database incompatibile.'],\n ['ACCESSI_SERVICE_TOKEN_RATE_LIMITED', 'Troppi tentativi (429).'],\n ],\n ),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'endpoint',\n group: 'Riferimenti',\n title: 'Indice degli endpoint',\n summary: 'Tutte le rotte /api/accessi/* in una tabella.',\n blocks: [\n table(\n ['Area', 'Endpoint principali'],\n [\n ['Auth', 'POST auth/login, auth/get-user-by-token, auth/confirm-reset-password/:token, auth/two-factor/verify, auth/two-factor/resend'],\n ['Email', 'POST email/send-reset-password-email, GET email/reset-password-page/:token'],\n ['Utenti', 'GET user/get-users, POST user/create-managed-user, PUT user/update-user/:codiceUtente, DELETE user/delete-user/:codiceUtente, DELETE user/delete-user-permanent/:codiceUtente, POST user/set-password/:codiceUtente, POST user/set-stato, PATCH user/set-gdpr/:codiceUtente, POST user/register'],\n ['Permessi', 'GET permission/roles, POST permission/create-role, PUT permission/update-role/:codiceRuolo, DELETE permission/delete-role/:codiceRuolo, POST permission/assign-roles/:codiceUtente, POST permission/assign-permissions/:codiceUtente, GET permission/grants/:codiceUtente, GET permission/menus, GET permission/menu-types, GET permission/groups-with-menus'],\n ['Catalogo', 'POST/PUT/DELETE permission/menus[/:codiceMenu], permission/menu-groups[/:codiceGruppo], permission/menu-types[/:codiceTipo]'],\n ['Configurator', 'PATCH configurator/menus/:codiceMenu/enabled, PATCH configurator/groups/:codiceGruppo/enabled'],\n ['SSO', 'GET/POST federated-auth/providers, PATCH/DELETE providers/:provider, GET/POST users/:codiceUtente/identities, POST users, PATCH users/:codiceUtente/password-login'],\n ['Token', 'POST/GET service-token, DELETE service-token/:tokenId, POST service-token/:tokenId/rotate'],\n ['Schema', 'GET schema (DDL completo + entità presenti, solo admin)'],\n ['Console', 'GET console, console/:view, console/:view/:sub, console/assets/:file'],\n ],\n ),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'antipattern',\n group: 'Riferimenti',\n title: 'Antipattern e checklist',\n summary: 'Che cosa non fare, in breve.',\n blocks: [\n list([\n `Non aprire la porta HTTP prima di initializeAccessiModule.`,\n `Non usare authorizeAccessi come unico strato: valida sempre anche l'input applicativo.`,\n `Non dare per scontato che req.userGrants esista se non hai richiesto dei requisiti.`,\n `Non considerare incrementali assignRolesToUser e assignPermissionsToUser: sono sostitutivi.`,\n `Non far validare i token SSO ad Accessi: passagli solo { provider, subject } verificati.`,\n `Non usare un service token come identità utente.`,\n `Non versionare segreti; jwtOptions.secret deve essere unico per ambiente.`,\n `Non eliminare gruppi o tipi in uso: gestisci l'errore 400 e disabilita invece.`,\n `Non usare il challenge 2FA come JWT.`,\n ]),\n note('ok', `Checklist di rilascio`, `Schema verificato (db:check), segreti da un secret manager, mockDemoUser=false, rate limit pubblico configurato, SMTP reale testato, serviceTokens.defaultTtlDays impostato.`),\n ],\n },\n];\n\n/** Elenco ordinato dei gruppi per l'indice. */\nexport function wikiGroups(): Array<{ group: string; sections: WikiSection[] }> {\n const groups: Array<{ group: string; sections: WikiSection[] }> = [];\n for (const section of WIKI_SECTIONS) {\n let bucket = groups.find((item) => item.group === section.group);\n if (!bucket) {\n bucket = { group: section.group, sections: [] };\n groups.push(bucket);\n }\n bucket.sections.push(section);\n }\n return groups;\n}\n\n/**\n * Digest compatto in Markdown per l'IA che costruisce il backend integratore.\n * Deriva dagli stessi dati della wiki, quindi resta sincronizzato.\n */\nexport function buildAiDigest(): string {\n const lines: string[] = [\n `# Accessi (emilsoftware-utilities) - guida compatta per l'integrazione backend`,\n '',\n `Modulo NestJS/Express + Firebird per autenticazione JWT, autorizzazione per menu, utenti, 2FA e passwordless, SSO generico, service token e console.`,\n '',\n ];\n\n for (const { group, sections } of wikiGroups()) {\n lines.push(`## ${group}`);\n for (const section of sections) {\n lines.push(`### ${section.title}`);\n lines.push(section.summary);\n for (const block of section.blocks) {\n switch (block.kind) {\n case 'p':\n lines.push(block.text);\n break;\n case 'h':\n lines.push(`**${block.text}**`);\n break;\n case 'list':\n block.items.forEach((item, index) => lines.push(`${block.ordered ? `${index + 1}.` : '-'} ${item}`));\n break;\n case 'code':\n lines.push('```' + block.language);\n lines.push(block.code);\n lines.push('```');\n break;\n case 'callout':\n lines.push(`[${block.tone.toUpperCase()}] ${block.title ? block.title + ': ' : ''}${block.text}`);\n break;\n case 'table':\n lines.push('| ' + block.head.join(' | ') + ' |');\n lines.push('| ' + block.head.map(() => '---').join(' | ') + ' |');\n block.rows.forEach((row) => lines.push('| ' + row.join(' | ') + ' |'));\n break;\n }\n }\n lines.push('');\n }\n }\n\n return lines.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim();\n}\n"]}
1
+ {"version":3,"file":"wiki.js","sourceRoot":"","sources":["../../../src/accessi-module/Console/wiki.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Contenuto della Wiki della console Accessi.\n *\n * I contenuti sono dati strutturati (non HTML) così da poter essere resi in modo\n * coerente dalla console e convertiti in un digest compatto per l'IA. Nessun\n * segreto o dato specifico di un ambiente deve finire qui.\n */\n\nexport type WikiTone = 'info' | 'ok' | 'warn' | 'danger';\n\nexport type WikiBlock =\n | { kind: 'p'; text: string }\n | { kind: 'h'; text: string }\n | { kind: 'list'; items: string[]; ordered?: boolean }\n | { kind: 'code'; language: string; title?: string; code: string; variant?: 'good' | 'bad' }\n | { kind: 'callout'; tone: WikiTone; title?: string; text: string }\n | { kind: 'table'; head: string[]; rows: string[][] };\n\nexport interface WikiSection {\n id: string;\n group: string;\n title: string;\n summary: string;\n blocks: WikiBlock[];\n}\n\nconst p = (text: string): WikiBlock => ({ kind: 'p', text });\nconst h = (text: string): WikiBlock => ({ kind: 'h', text });\nconst list = (items: string[], ordered = false): WikiBlock => ({ kind: 'list', items, ordered });\nconst code = (language: string, codeText: string, title?: string, variant?: 'good' | 'bad'): WikiBlock => ({ kind: 'code', language, title, code: codeText, variant });\nconst good = (language: string, codeText: string, title?: string): WikiBlock => code(language, codeText, title, 'good');\nconst bad = (language: string, codeText: string, title?: string): WikiBlock => code(language, codeText, title, 'bad');\nconst note = (tone: WikiTone, title: string, text: string): WikiBlock => ({ kind: 'callout', tone, title, text });\nconst table = (head: string[], rows: string[][]): WikiBlock => ({ kind: 'table', head, rows });\n\nexport const WIKI_SECTIONS: WikiSection[] = [\n // -------------------------------------------------------------------------\n {\n id: 'panoramica',\n group: 'Per iniziare',\n title: 'Panoramica e architettura',\n summary: 'Che cosa fa Accessi, come si incastra nel backend e come scorre una richiesta.',\n blocks: [\n p(`Accessi è un modulo Node/NestJS che aggiunge a un backend già esistente: autenticazione JWT, autorizzazione per ruoli e menu, gestione utenti, reset password, codici email (2FA e passwordless), SSO generico, token macchina-a-macchina e una console amministrativa. Il database è Firebird/InterBase.`),\n h(`Che cosa possiede il modulo`),\n list([\n `Le rotte sotto /api/accessi/* (login, utenti, ruoli, menu, SSO, token, console).`,\n `Lo schema database del dominio Accessi, riconciliato automaticamente al bootstrap.`,\n `Il middleware di autorizzazione authorizeAccessi per le rotte della tua applicazione.`,\n `La console web su /api/accessi/console.`,\n ]),\n h(`Che cosa resta alla tua applicazione`),\n list([\n `Il proprio frontend e le proprie rotte di business.`,\n `La validazione dei token dei provider SSO esterni (Accessi non li vede).`,\n `I segreti: jwtOptions.secret, SMTP, database ed encryptionKey non raggiungono mai il browser.`,\n ]),\n h(`Flusso di una richiesta protetta`),\n list([\n `Il client chiama una rotta della tua app con header Authorization: Bearer <jwt>.`,\n `La tua rotta esegue authorizeAccessi, che verifica firma HS256, scadenza e stato utente corrente.`,\n `Se passi un requisito, il modulo carica ruoli e grant dell'utente e li valuta.`,\n `Su successo req.user, req.data e (se richiesto) req.userGrants sono popolati; altrimenti risponde con il contratto errore Accessi.`,\n ], true),\n note('info', `Due modalità di bootstrap`, `Express esistente: initializeAccessiModule(app, options). NestJS esistente: AccessiModule.forRoot(options) nei tuoi imports. Non usarle entrambe.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'installazione',\n group: 'Per iniziare',\n title: 'Installazione',\n summary: 'Dipendenze, peer e build.',\n blocks: [\n code('bash', `npm install emilsoftware-utilities`, 'Installazione'),\n p(`La libreria dipende da node-firebird per il database e da Express 5 come peer per il montaggio. È consigliato Node 18 o superiore.`),\n note('warn', `Un solo modulo per processo`, `AccessiModule è @Global(): inizializzalo una sola volta per processo. In caso di più istanze, usa un processo per istanza.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'bootstrap-express',\n group: 'Per iniziare',\n title: 'Bootstrap su Express esistente',\n summary: 'initializeAccessiModule: ordine corretto e attesa prima di listen.',\n blocks: [\n p(`Chiama initializeAccessiModule prima di aprire la porta HTTP. Durante il bootstrap, authorizeAccessi risponde 503 ACCESSI_AUTH_INITIALIZING invece di autorizzare richieste parziali.`),\n good('ts', [\n \"import express from 'express';\",\n \"import { initializeAccessiModule, authorizeAccessi } from 'emilsoftware-utilities';\",\n '',\n 'const app = express();',\n 'app.use(express.json());',\n '',\n 'await initializeAccessiModule(app, options); // prima di app.listen',\n '',\n \"app.get('/api/ordini', authorizeAccessi, (req, res) => {\",\n ' res.json({ utente: req.user });',\n '});',\n '',\n \"app.get('/api/ordini/scrivi', (req, res, next) =>\",\n ' authorizeAccessi(req, res, next, {',\n \" requirements: [{ menuCode: 'ORDINI', minPermissionLevel: 20 }],\",\n ' }),',\n '(req, res) => res.json({ ok: true }));',\n '',\n 'app.listen(3000);',\n ].join('\\n'), 'Express + Accessi'),\n bad('ts', [\n 'const app = express();',\n 'app.listen(3000); // porta aperta prima del bootstrap',\n 'await initializeAccessiModule(app, options); // le richieste nel frattempo ricevono 503',\n ].join('\\n'), 'Non aprire la porta prima del bootstrap'),\n h(`Rotte montate`),\n p(`Accessi intercetta solo /api/accessi/*, /accessi/openapi* e /accessi/openapi.json. Tutte le altre rotte passano intatte al tuo stack Express.`),\n note('ok', `req.user`, `authorizeAccessi popola req.user con il payload utente corrente (utente, codiceUtente, flag) e req.data come alias storico. req.userGrants è presente solo se hai richiesto dei requisiti.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'bootstrap-nest',\n group: 'Per iniziare',\n title: 'Bootstrap su NestJS esistente',\n summary: 'AccessiModule.forRoot e uso dei provider esportati.',\n blocks: [\n p(`Se la tua applicazione è già NestJS, importa AccessiModule.forRoot(options). Il modulo è @Global e riesporta i servizi (AuthService, UserService, PermissionService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, AuthenticateGenService).`),\n good('ts', [\n \"import { Module } from '@nestjs/common';\",\n \"import { AccessiModule } from 'emilsoftware-utilities';\",\n '',\n '@Module({',\n ' imports: [AccessiModule.forRoot(options)],',\n '})',\n 'export class AppModule {}',\n ].join('\\n'), 'app.module.ts'),\n p(`Il global prefix api e le rotte /api/accessi/* sono gestiti dal modulo. Le tue rotte Nest si proteggono con JwtSimpleGuard usando il JWT Accessi.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'opzioni',\n group: 'Configurazione',\n title: 'AccessiOptions (riferimento completo)',\n summary: 'Ogni campo di configurazione con tipo, obbligo e note.',\n blocks: [\n table(\n ['Campo', 'Tipo', 'Obbligo', 'Note'],\n [\n ['databaseOptions', 'Options (node-firebird)', 'sì', 'Connessione Firebird al database Accessi.'],\n ['confirmationEmailUrl', 'string', 'sì', 'Base URL usata per il reset password.'],\n ['confirmationEmailReturnUrl', 'string', 'sì', 'URL di ritorno dopo il reset.'],\n ['confirmationEmailPrefix', 'string', 'no', 'Prefisso passato alla pagina di reset.'],\n ['customResetPage', 'string', 'no', 'Sostituisce la pagina HTML di reset inclusa.'],\n ['encryptionKey', 'string', 'sì', 'Compatibilità e migrazione delle password legacy.'],\n ['mockDemoUser', 'boolean', 'sì', 'Utenti fittizi admin e demo solo fuori produzione.'],\n ['passwordExpiration', 'boolean', 'no', 'Attiva il controllo della scadenza password al login.'],\n ['passwordExpirationDays', 'number', 'no', 'Valore predefinito: 90 giorni.'],\n ['legacyPasswordMigrationOnStartup', 'boolean', 'no', 'Predefinito true: converte le password legacy.'],\n ['autoUpdateDatabase', 'boolean', 'no', 'Predefinito true: riconcilia lo schema al boot.'],\n ['jwtOptions', '{ secret, expiresIn }', 'sì', 'Firma HS256 e durata dei JWT Accessi.'],\n ['emailOptions', 'EmailOptions', 'sì', 'SMTP per reset e codici email.'],\n ['publicAuthRateLimit', 'PublicAuthRateLimitOptions', 'no', 'Limiti in-memory sugli endpoint pubblici.'],\n ['publicRegistration', '{ enabled }', 'no', 'Registrazione pubblica: disattivata per impostazione predefinita.'],\n ['federatedAuthentication', 'FederatedAuthenticationOptions', 'no', `Abilita l'SSO generico.`],\n ['extensionFieldsOptions', 'ExtensionFieldsOptions[]', 'no', 'Tabelle esterne allegate al profilo.'],\n ['serviceTokens', '{ enabled, defaultTtlDays }', 'no', 'Token macchina-a-macchina, abilitati per impostazione predefinita.'],\n ['catalogScripts', '{ enabled, folder, ... }', 'no', 'Catalog migrations SQL eseguite dopo la riconciliazione dello schema.'],\n ['adminBootstrap', '{ enabled, superUser }', 'no', 'Token casuale a ogni avvio per creare il primo admin dalla console.'],\n ['publicBaseUrl', 'string', 'no', 'Base URL usata solo per loggare console/swagger/API.'],\n ],\n ),\n good('ts', [\n \"import type { AccessiOptions } from 'emilsoftware-utilities';\",\n '',\n 'export const options: AccessiOptions = {',\n ' databaseOptions: {',\n ' host: process.env.ACCESSI_DB_HOST!,',\n ' port: Number(process.env.ACCESSI_DB_PORT ?? 3050),',\n ' database: process.env.ACCESSI_DB_NAME!,',\n ' user: process.env.ACCESSI_DB_USER!,',\n ' password: process.env.ACCESSI_DB_PASSWORD!,',\n ' },',\n \" confirmationEmailUrl: 'https://app.example.com',\",\n \" confirmationEmailReturnUrl: 'https://app.example.com/login',\",\n ' encryptionKey: process.env.ACCESSI_ENCRYPTION_KEY!,',\n ' mockDemoUser: false,',\n ' passwordExpiration: true,',\n ' passwordExpirationDays: 90,',\n ' jwtOptions: {',\n \" secret: process.env.ACCESSI_JWT_SECRET!, // almeno 32 caratteri casuali, unico per ambiente\",\n \" expiresIn: '24h',\",\n ' },',\n ' emailOptions: {',\n ' host: process.env.SMTP_HOST!,',\n ' port: Number(process.env.SMTP_PORT ?? 587),',\n ' secure: false,',\n ' requireTLS: true,',\n ' tls: { rejectUnauthorized: true },',\n \" from: 'no-reply@example.com',\",\n ' auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },',\n ' },',\n ' publicRegistration: { enabled: false },',\n ' serviceTokens: { enabled: true, defaultTtlDays: 365 },',\n '};',\n ].join('\\n'), 'Configurazione tipica'),\n bad('ts', [\n \"jwtOptions: { secret: 'accessi', expiresIn: '24h' }, // segreto debole e condiviso\",\n 'mockDemoUser: true, // MAI in produzione',\n ].join('\\n'), 'Errori comuni'),\n note('danger', `Segreti`, `Non versionare i segreti e non passarli al frontend. jwtOptions.secret deve essere identico tra le istanze del backend e diverso da quello dei provider SSO.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'database',\n group: 'Configurazione',\n title: 'Schema e aggiornamento del database',\n summary: 'Tabelle, riconciliazione automatica e CLI.',\n blocks: [\n p(`Accessi possiede lo schema del dominio. Con autoUpdateDatabase true (predefinito) lo riconcilia a ogni avvio; con false verifica comunque la compatibilità in sola lettura e blocca gli avvii incompatibili.`),\n h(`Tabelle principali`),\n list([\n `PARAMETRI, UTENTI, UTENTI_CONFIG, UTENTI_PWD, UTENTI_OLDPWD, UTENTI_GDPR`,\n `RUOLI, MENU_GRP, MENU_TIPI, MENU, ABILITAZIONI, RUOLI_MNU, UTENTI_RUOLI`,\n `FILTRI, FILTRI_TIPO`,\n `SSO_PROVIDER, UTENTI_IDENTITA_EXT`,\n `ACCESSI_2FA, ACCESSI_SERVICE_TOKEN, ACCESSI_CATALOG_SCRIPT`,\n ]),\n code('bash', `npm run db:update:accessi # riconcilia lo schema\\nnpm run db:check:accessi # verifica senza modificare`, 'CLI schema'),\n note('warn', `Versioni`, `La libreria richiede uno schema compatibile con la versione corrente (ACCESSI_SCHEMA_VERSION). Se la verifica fallisce, leggi il codice errore ACCESSI_DATABASE_SCHEMA_OUTDATED ed esegui db:update:accessi.`),\n note('info', `Schema esatto`, `La riconciliazione allinea le tabelle Accessi a \"niente in piu, niente in meno\": le colonne non previste vengono segnalate come incompatibili e rimosse automaticamente (dopo rename e copie dei dati legacy). Gli indici, i trigger e i CHECK extra non previsti non vengono tolti, ma un indice non previsto su una tabella Accessi viene riportato come avviso senza bloccare l'avvio.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'catalog-migrations',\n group: 'Configurazione',\n title: 'Catalog migrations SQL',\n summary: 'Allinea menu, ruoli e cataloghi su piu database in modo idempotente.',\n blocks: [\n p(`Gli script SQL applicativi (menu, ruoli, tipi, cataloghi) restano nel repository del backend ospitante e Accessi li esegue dopo la riconciliazione dello schema. Ogni script viene tracciato nella tabella ACCESSI_CATALOG_SCRIPT (nome + checksum), quindi viene applicato una sola volta per database.`),\n h(`Configurazione`),\n good('ts', [\n 'catalogScripts: {',\n \" enabled: true,\",\n \" folder: './db/accessi-catalog', // versionata nel repo del backend\",\n \" onChecksumMismatch: 'error', // 'error' (default) | 'warn' | 'reapply'\",\n \" stopOnError: true,\",\n '}',\n ].join('\\n'), 'AccessiOptions'),\n list([\n 'Gli script sono ordinati per percorso (usa prefissi numerici: 0001_, 0002_, ...).',\n 'Devono essere idempotenti e ripetibili: UPDATE OR INSERT ... MATCHING, MERGE, DELETE+INSERT mirati.',\n 'Non includere utenti: le catalog migrations allineano solo catalogo e configurazione.',\n 'Supportati stringhe, commenti e statement multipli; SET TERM non serve perché gli script sono solo DML.',\n ]),\n note('danger', `Solo righe (DML)`, `Vincolo tassativo: uno script puo eseguire solo INSERT, UPDATE, DELETE, MERGE, SELECT, EXECUTE. Qualunque DDL (CREATE/ALTER/DROP, trigger, procedure, GRANT, transazioni) viene rifiutato. Lo schema si aggiorna solo con db:update:accessi.`),\n note('info', `File non conformi`, `Un file con statement non consentiti non viene applicato ne registrato nel ledger; il run prosegue con gli altri e il report riporta una voce per ogni file con i relativi messaggi di errore (visibili anche con db:catalog:accessi:check).`),\n note('info', `allowDdl`, `Disattivato di default. Impostarlo a true disabilita la validazione e permette DDL: usarlo solo consapevolmente e mai come sostituto dell'updater dello schema.`),\n code('bash', `# Applica (schema + catalog migrations) su un database\\nACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi\\n\\n# Elenca solo quelle da applicare / modificate\\nACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi:check`, 'CLI catalog migrations'),\n note('info', `Idempotenza`, `Uno script gia applicato con lo stesso checksum viene saltato. Se il file cambia dopo l'applicazione, con policy error il run si interrompe: non modificare uno script applicato, aggiungine uno nuovo.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'login',\n group: 'Autenticazione',\n title: 'Login locale e JWT',\n summary: 'Endpoint di login, risposta e gestione della password scaduta.',\n blocks: [\n code('http', `POST /api/accessi/auth/login\\nContent-Type: application/json\\n\\n{ \"email\": \"utente@example.com\", \"password\": \"Segreta123!\" }`, 'Login con password'),\n p(`La risposta include Result.token (il JWT) e il profilo utente con ruoli e permessi. Se l'account è passwordless, ometti la password: il server risponde con un challenge via email (vedi la sezione su 2FA e passwordless).`),\n note('info', `Autorizzazioni lato client`, `La fonte autorevole è Result.userGrants.grants: contiene i grant effettivi (ruoli + permessi diretti, livello massimo). Non leggere i permessi dal JWT e non usare Result.utente.permissions: non sono popolati. Per i superutenti grants contiene tutti i menu attivi al livello 30.`),\n good('ts', [\n \"const res = await fetch('/api/accessi/auth/login', {\",\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json' },\",\n ' body: JSON.stringify({ email, password }),',\n '});',\n 'const body = await res.json();',\n 'if (body.Result?.challenge) { /* richiedi il codice */ }',\n 'const token = body.Result?.token;',\n ].join('\\n'), 'Chiamata dal frontend'),\n note('warn', `Password scaduta`, `Con passwordExpiration attivo, una password scaduta produce una PasswordExpiredResponse con code PASSWORD_EXPIRED. Il client deve avviare il reset della password.`),\n h(`Verifica del token`),\n code('http', `POST /api/accessi/auth/get-user-by-token\\nContent-Type: application/json\\n\\n{ \"token\": \"<jwt>\" }`, 'get-user-by-token'),\n p(`Usalo per verificare un JWT e ottenere il profilo corrente (utile al bootstrap di una SPA). Restituisce Result.userData.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'passwordless-2fa',\n group: 'Autenticazione',\n title: 'Codici email, 2FA e passwordless',\n summary: 'Verifica in due passi, challenge e policy per utente.',\n blocks: [\n p(`La verifica con codice email è per-utente (flagDueFattori / UTENTI_CONFIG.FLG2FATT) ed è disattivata per impostazione predefinita. Con passwordlessLoginEnabled (FLGPWDLESS, richiede la 2FA attiva) l'accesso avviene con il solo codice email.`),\n table(\n ['Configurazione', 'Accesso locale', 'Accesso SSO'],\n [\n ['2FA disattivata', 'Password', 'Provider'],\n ['2FA attiva', 'Password + codice email', 'Provider + codice email'],\n ['2FA + passwordless', 'Solo codice email', 'Provider + codice email'],\n ],\n ),\n code('json', `{\\n \"Result\": {\\n \"challenge\": {\\n \"twoFactorRequired\": true,\\n \"challengeId\": \"64-caratteri-esadecimali\",\\n \"method\": \"email\",\\n \"expiresAt\": \"2026-09-11T14:00:00.000Z\",\\n \"resendAfterSeconds\": 60\\n }\\n }\\n}`, 'Risposta con challenge'),\n code('http', `POST /api/accessi/auth/two-factor/verify\\n{ \"challengeId\": \"...\", \"code\": \"012345\" }\\n\\nPOST /api/accessi/auth/two-factor/resend\\n{ \"challengeId\": \"...\" }`, 'Verifica e reinvio'),\n list([\n `Il codice ha 6 cifre, dura 10 minuti, consente 5 tentativi totali e al massimo 3 invii per challenge.`,\n `Fino alla verifica non sono presenti token, profilo o grant.`,\n `Il challenge non è un JWT e non va usato nell'header Authorization.`,\n ]),\n bad('ts', [\n '// Sbagliato: usare il challenge come token',\n \"fetch('/api/ordini', { headers: { Authorization: 'Bearer ' + challengeId } }); // 401\",\n ].join('\\n'), 'Il challenge non è un token'),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'reset-password',\n group: 'Autenticazione',\n title: 'Reset password',\n summary: 'Invio email e conferma con token monouso.',\n blocks: [\n code('http', `POST /api/accessi/email/send-reset-password-email\\n{ \"email\": \"utente@example.com\" }\\n\\nPOST /api/accessi/auth/confirm-reset-password/{token}\\n{ \"newPassword\": \"NuovaSegreta123!\" }`, 'Reset in due passi'),\n p(`L'invio non rivela se l'email esiste (anti-enumerazione). Il token di reset è monouso ed è firmato con una chiave dedicata.`),\n note('warn', `Policy password (solo quando si imposta)`, `Reset, set-password admin e bootstrap richiedono almeno 8 caratteri con maiuscola, minuscola, cifra e carattere speciale, senza spazi e non comuni. Se non conforme l'API risponde 400 con code ACCESSI_WEAK_PASSWORD e l'elenco dei requisiti in details. La policy NON si applica al login: le password preesistenti eventualmente deboli continuano ad autenticare e vengono migrate senza blocco.`),\n note('info', `Pagina personalizzata`, `Imposta customResetPage per usare la tua pagina di reset invece di quella inclusa. confirmationEmailReturnUrl e confirmationEmailPrefix controllano il ritorno.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'middleware',\n group: 'Autorizzazione',\n title: 'authorizeAccessi (middleware)',\n summary: 'Proteggere le rotte della tua app con il JWT Accessi.',\n blocks: [\n p(`authorizeAccessi è un middleware Express che verifica il JWT, ricarica lo stato utente corrente e applica i requisiti. Su successo prosegue; su errore risponde direttamente con il contratto Accessi (non chiamare un tuo next(error)).`),\n good('ts', [\n \"app.get('/api/report',\",\n ' (req, res, next) => authorizeAccessi(req, res, next, {',\n \" requirements: [{ menuCode: 'REPORT', minPermissionLevel: 10 }],\",\n ' }),',\n ' (req, res) => res.json({ user: req.user, grants: req.userGrants }),',\n ');',\n ].join('\\n'), 'Rotta protetta'),\n h(`Che cosa trovi nella request`),\n list([\n `req.user e req.data: payload utente corrente (alias storici).`,\n `req.userGrants: grant effettivi, presente solo quando hai richiesto dei requisiti.`,\n ]),\n bad('ts', [\n \"app.get('/api/report', authorizeAccessi, (req, res) => {\",\n ' // Sbagliato: assumere che req.userGrants esista senza requisiti',\n ' const grants = req.userGrants.grants; // possibile TypeError',\n '});',\n ].join('\\n'), 'Non dare per scontato req.userGrants'),\n note('danger', `Lo stato utente è sempre ricontrollato`, `Un JWT valido non basta: se l'utente è stato bloccato, eliminato o ha cambiato policy, il middleware risponde 401 AUTH_USER_DISABLED.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'requisiti',\n group: 'Autorizzazione',\n title: 'Requisiti e livelli di abilitazione',\n summary: 'DSL accessiRequirement: permission, and, or, not, custom.',\n blocks: [\n p(`I requisiti si compongono con accessiRequirement. La forma storica requirements è un AND di permessi. Il livello è TipoAbilitazione: NESSUNA=0, LETTURA=10, SCRITTURA=20, SPECIAL=30; il confronto è tipoAbilitazione >= soglia.`),\n good('ts', [\n \"import { authorizeAccessi, accessiRequirement as r } from 'emilsoftware-utilities';\",\n '',\n '// AND (entrambi i menu)',\n 'const both = r.and(r.permission(\"ORDINI\", 20), r.permission(\"CLIENTI\", 10));',\n '',\n '// OR',\n 'const either = r.or(r.permission(\"REPORT\", 10), r.permission(\"EXPORT\", 20));',\n '',\n '// NOT + custom',\n 'const policy = r.and(',\n ' r.permission(\"AREA_RISERVATA\", 10),',\n ' r.not(r.permission(\"BLOCCO_TOTALE\", 0)),',\n ');',\n '',\n 'app.get(\"/api/riservato\", (req, res, next) => authorizeAccessi(req, res, next, {',\n ' requirementTree: policy,',\n ' customRequirementHandlers: {',\n ' stessoReparto: async (ctx) => ctx.userCode === Number(ctx.req.query.reparto),',\n ' },',\n '}), handler);',\n ].join('\\n'), 'Policy composte'),\n bad('ts', [\n '// Sbagliato: AND/OR senza figli -> errore di configurazione (500 AUTH_REQUIREMENTS_MISCONFIGURED)',\n 'const empty = r.and();',\n ].join('\\n'), 'Configurazione non valida'),\n note('warn', `custom senza handler`, `Un requisito custom la cui chiave non ha un handler produce 500 AUTH_REQUIREMENTS_MISCONFIGURED: è un errore di programmazione, non un 403.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'utenti',\n group: 'Dominio',\n title: 'Utenti',\n summary: 'Creazione, aggiornamento, stati e GDPR.',\n blocks: [\n h(`Stati di registrazione (STAREG)`),\n table(\n ['Valore', 'Nome', 'Effetto sul login'],\n [\n ['0', 'NULL', 'Non confermato'],\n ['5', 'INSERT', 'Non confermato'],\n ['10', 'INVIO', 'Richiede il rinnovo della password'],\n ['20', 'CONF', 'Valido'],\n ['50', 'DELETE', 'Bloccato (soft delete)'],\n ['99', 'BLOCC', 'Bloccato'],\n ],\n ),\n h(`Endpoint principali (superutente salvo diversamente indicato)`),\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/user/get-users', 'Elenco con paginazione (limit e offset, header X-Total-Count).'],\n ['POST', '/api/accessi/user/create-managed-user', 'Crea un utente locale e invia il reset password.'],\n ['PUT', '/api/accessi/user/update-user/:codiceUtente', 'Aggiorna profilo e policy (campi privilegiati solo superutente).'],\n ['DELETE', '/api/accessi/user/delete-user/:codiceUtente', 'Soft delete: STAREG=DELETE. Admin o superutente.'],\n ['DELETE', '/api/accessi/user/delete-user-permanent/:codiceUtente', 'Eliminazione definitiva: rimuove utente e record collegati. Solo admin.'],\n ['POST', '/api/accessi/user/set-password/:codiceUtente', 'Imposta direttamente la password: hash lato backend e scadenza secondo policy, nessuna email.'],\n ['POST', '/api/accessi/user/set-stato', 'Imposta lo stato di registrazione.'],\n ['PATCH', '/api/accessi/user/set-gdpr/:codiceUtente', 'Registra il consenso GDPR.'],\n ['POST', '/api/accessi/user/register', 'Registrazione pubblica, solo se publicRegistration.enabled.'],\n ],\n ),\n note('warn', `Eliminazione: soft o definitiva`, `Il soft delete (STAREG=DELETE) è disponibile per admin e superutenti ed è reversibile. L'eliminazione definitiva rimuove utente e record collegati in transazione ed è riservata agli admin; il superutente senza flag Admin può solo flaggare l'utente come eliminato. Se l'utente ha dati collegati in altre tabelle (per esempio campi estesi del backend) l'eliminazione definitiva fallisce con code ACCESSI_USER_DELETE_HAS_REFERENCES e messaggio esplicito: usa allora il soft delete.`),\n note('ok', `Creazione con invito atomica`, `La creazione utente con invio email è atomica: se SMTP non è configurato si fallisce prima di scrivere, e se l'invio fallisce l'utente creato viene annullato (codice ACCESSI_USER_INVITE_EMAIL_FAILED). Non restano utenti senza password. In alternativa un admin crea l'utente e imposta la password dal form dedicato.`),\n note('ok', `Admin: controllo completo del profilo`, `Dalla scheda utente un admin modifica tutti i campi Accessi: anagrafica, cellulare, lingua, avatar, pagina di default, metadata JSON, 2FA/passwordless/login con password, flag, GDPR, stato registrazione e scadenza password. La password si imposta direttamente con set-password. Le scritture sono idempotenti (UPDATE OR INSERT su UTENTI_CONFIG) e verificate dal backend; non serve accedere al database.`),\n note('info', `Consenso GDPR`, `Il pulsante \"Registra consenso GDPR\" completa il processo: imposta FLGGDPR e DATGDPR e scrive la riga storica in UTENTI_GDPR (endpoint set-gdpr). Anche impostare flagGdpr via update-user completa il processo con la stessa riga storica.`),\n good('ts', [\n \"await fetch('/api/accessi/user/create-managed-user', {\",\n \" method: 'POST',\",\n \" headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + adminToken },\",\n ' body: JSON.stringify({',\n \" email: 'nuovo@example.com',\",\n \" nome: 'Mario',\",\n \" cognome: 'Rossi',\",\n ' roles: [1, 2],',\n \" permissions: [{ codiceMenu: 'REPORT', tipoAbilitazione: 10 }],\",\n ' }),',\n '});',\n ].join('\\n'), 'Creazione di un utente gestito'),\n bad('ts', [\n '// Sbagliato: inviare flag privilegiati dal frontend pubblico',\n 'fetch(\"/api/accessi/user/register\", {',\n ' method: \"POST\",',\n ' body: JSON.stringify({ email, flagSuper: true }), // ignorato o negato',\n '});',\n ].join('\\n'), 'Privilegi dal client'),\n note('info', `Campi di dominio`, `UTENTI_CONFIG contiene i campi di dominio Accessi: FLGSUPER, FLGADMIN, FLG2FATT (2FA), FLGPWDLESS (passwordless), FLGPASSWORD (login locale), CODLINGUA, CELLULARE, PAGDEF (pagina di default) e JSON_METADATA. I flag/colonne applicativi (FLGMOP, FLGPIANA, FLGADDETTI, FLGOSPITI, FLGPIANARFID, FLGCONTA, FLGCUBI, FLGCICLPASS, FLGDIPENDENTI, FLGINVENTARI, CAUMOV, NUMMAC, RAGSOCCLI, ENABLEIA, CELLUTE e NOMECAMPO) sono stati rimossi: i backend devono gestirli in tabelle proprie. Il telefono canonico e UTENTI_CONFIG.CELLULARE.`),\n note('info', `Autorizzazioni`, `I gruppi/menu si abilitano tramite ABILITAZIONI, RUOLI_MNU e UTENTI_RUOLI. Il vecchio meccanismo NOMECAMPO (flag applicativo su UTENTI_CONFIG) e stato rimosso.`),\n note('info', `Campi estesi`, `extensionFieldsOptions allega colonne di tabelle applicative al profilo (per esempio la ragione sociale). Le colonne sono in whitelist: non costruirle mai da input HTTP.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'ruoli',\n group: 'Dominio',\n title: 'Ruoli e permessi',\n summary: 'Assegnazioni sostitutive e composizione dei grant.',\n blocks: [\n p(`I ruoli contengono una lista di menu con livello. Le assegnazioni (ruoli e permessi diretti) sono SOSTITUTIVE: invia sempre la lista completa desiderata. I grant effettivi sommano ruoli e permessi diretti prendendo il livello massimo per menu; un livello 0 non annulla un grant del ruolo. I superutenti ricevono tutti i menu attivi al livello massimo.`),\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/permission/roles', 'Ruoli con i menu associati.'],\n ['POST', '/api/accessi/permission/create-role', 'Crea un ruolo (i menu sono obbligatori).'],\n ['PUT', '/api/accessi/permission/update-role/:codiceRuolo', 'Aggiorna un ruolo (menu sostitutivi).'],\n ['DELETE', '/api/accessi/permission/delete-role/:codiceRuolo', 'Elimina ruolo, mapping e assegnazioni.'],\n ['POST', '/api/accessi/permission/assign-roles/:codiceUtente', `Sostituisce i ruoli dell'utente.`],\n ['POST', '/api/accessi/permission/assign-permissions/:codiceUtente', 'Sostituisce i permessi diretti.'],\n ['GET', '/api/accessi/permission/grants/:codiceUtente', 'Grant effettivi, permessi diretti e ruoli.'],\n ],\n ),\n good('ts', [\n 'const role = {',\n \" descrizioneRuolo: 'Gestore ordini',\",\n ' menu: [',\n \" { codiceMenu: 'ORDINI', tipoAbilitazione: 20 },\",\n \" { codiceMenu: 'CLIENTI', tipoAbilitazione: 10 },\",\n ' ],',\n '};',\n 'await createRole(role); // POST /api/accessi/permission/create-role',\n '',\n \"// Sostitutivo: questa lista diventa l'intero set di ruoli dell'utente\",\n 'await assignRolesToUser(codiceUtente, { roles: [3, 7] });',\n ].join('\\n'), 'Creazione di un ruolo e assegnazione'),\n bad('ts', [\n \"// Sbagliato: pensare che l'assegnazione sia incrementale\",\n 'await assignRolesToUser(codiceUtente, { roles: [3] });',\n \"// ora l'utente ha SOLO il ruolo 3, non 3 più quelli di prima\",\n ].join('\\n'), 'Assegnazione non additiva'),\n note('warn', `Menu obbligatori`, `create-role e update-role rifiutano un ruolo senza menu. Se ti serve un ruolo senza accessi, non crearlo.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'menu',\n group: 'Dominio',\n title: 'Menu, gruppi e tipi menu',\n summary: 'Catalogo di navigazione e CRUD completo (superutente).',\n blocks: [\n table(\n ['Metodo', 'Percorso', 'Descrizione'],\n [\n ['GET', '/api/accessi/permission/menus', 'Menu attivi.'],\n ['GET', '/api/accessi/permission/menu-types', 'Catalogo dei tipi menu.'],\n ['GET', '/api/accessi/permission/groups-with-menus?includeDisabled=true', 'Gruppi con menu (anche disabilitati).'],\n ['POST', '/api/accessi/permission/menus', 'Crea un menu.'],\n ['PUT', '/api/accessi/permission/menus/:codiceMenu', 'Aggiorna un menu.'],\n ['DELETE', '/api/accessi/permission/menus/:codiceMenu', 'Elimina un menu (con grant e associazioni ai ruoli).'],\n ['POST', '/api/accessi/permission/menu-groups', 'Crea un gruppo.'],\n ['PUT', '/api/accessi/permission/menu-groups/:codiceGruppo', 'Aggiorna un gruppo.'],\n ['DELETE', '/api/accessi/permission/menu-groups/:codiceGruppo', 'Elimina un gruppo (solo se vuoto).'],\n ['POST/PUT/DELETE', '/api/accessi/permission/menu-types[/:codiceTipo]', 'CRUD dei tipi menu.'],\n ],\n ),\n p(`I codici (CODMNU, CODGRP, CODTIP) sono chiavi immutabili: si indicano in creazione e poi si passano nel percorso. Un gruppo o un tipo si elimina solo se non è referenziato.`),\n good('ts', [\n \"await createMenuGroup({ codiceGruppo: 'A', descrizioneGruppo: 'Amministrazione', ordineGruppo: 1, enabled: true });\",\n \"await createMenuType({ codiceTipo: 'X', descrizioneTipo: 'Operativo' });\",\n \"await createMenu({ codiceMenu: 'ORDINI', descrizioneMenu: 'Ordini', codiceGruppo: 'A', tipo: 'X', ordineMenu: 10, enabled: true });\",\n ].join('\\n'), 'Catalogo in ordine'),\n bad('ts', [\n '// Sbagliato: eliminare un gruppo che contiene menu -> 400 con spiegazione',\n \"await deleteMenuGroup('A'); // contiene N menu\",\n ].join('\\n'), 'Vincoli di integrità'),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'sso',\n group: 'SSO',\n title: 'SSO generico (federated authentication)',\n summary: 'Il backend valida il provider, Accessi gestisce identità e sessioni.',\n blocks: [\n p(`Accessi non valida i token Azure, Google o SAML: è il TUO backend a validare il flusso SSO e a passare solo { provider, subject } verificati a FederatedAuthService. Così issuer, secret e token restano fuori dal modulo.`),\n good('ts', [\n \"import { FederatedAuthService } from 'emilsoftware-utilities';\",\n '',\n '// Nel tuo callback SSO, dopo aver validato il token del provider:',\n 'const verified = { provider: \"azure-ad-acme\", subject: claims.oid };',\n 'const result = await federatedAuthService.authenticate(verified);',\n '',\n 'if (result.login.challenge) {',\n ' // 2FA attiva: restituisci il challenge al frontend',\n ' return res.json({ challenge: result.login.challenge });',\n '}',\n 'return res.json({ login: result.login, token: result.token });',\n ].join('\\n'), 'Login SSO'),\n h(`API amministrative (superutente)`),\n list([\n `GET/POST /api/accessi/federated-auth/providers, PATCH/DELETE providers/:provider`,\n `GET/POST /api/accessi/federated-auth/users/:codiceUtente/identities`,\n `POST /api/accessi/federated-auth/users (crea un utente SSO-only)`,\n `PATCH /api/accessi/federated-auth/users/:codiceUtente/password-login`,\n ]),\n bad('ts', [\n \"// Sbagliato: far validare il token del provider ad Accessi o fidarsi del frontend\",\n 'await federatedAuthService.authenticate({ provider, subject: req.body.email }); // subject non verificato',\n ].join('\\n'), `Non fidarti dell'input`),\n note('warn', `Provider in uso`, `Un provider si elimina solo se nessuna identità è collegata; altrimenti disabilitalo. La chiave del provider deve coincidere con la configurazione del backend.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'service-token',\n group: 'Token di servizio',\n title: 'Token macchina-a-macchina',\n summary: 'Autenticazione tecnica con scope: non è un superutente.',\n blocks: [\n p(`Formato: st_<id>.<segreto> (segreto da 256 bit). Nel database si salva solo lo SHA-256 del segreto; il segreto è mostrato una sola volta. La verifica restituisce { tokenId, label, scopes }: nessun ruolo o grant.`),\n note('danger', `Non è un superutente`, `Un service token non apre le API Accessi: abilita solo le rotte della tua app che proteggi con ServiceTokenGuard.`),\n good('ts', [\n \"import { ServiceTokenGuard, RequireServiceTokenScopes } from 'emilsoftware-utilities';\",\n '',\n '@UseGuards(ServiceTokenGuard)',\n \"@RequireServiceTokenScopes('ia')\",\n '@Post(\"internal/ask\")',\n 'ask() { /* req.accessiServiceToken = { tokenId, label, scopes } */ }',\n ].join('\\n'), 'Rotta tecnica protetta'),\n h(`Gestione (superutente)`),\n list([\n `POST /api/accessi/service-token (label, scopes, expiresAt o ttlDays) -> segreto mostrato una volta`,\n `GET /api/accessi/service-token?includeRevoked=true`,\n `DELETE /api/accessi/service-token/:tokenId (revoca immediata)`,\n `POST /api/accessi/service-token/:tokenId/rotate`,\n ]),\n bad('ts', [\n '// Sbagliato: usare un service token per autenticare un utente umano',\n \"fetch('/api/profilo', { headers: { Authorization: 'Bearer ' + serviceToken } }); // req.user assente\",\n ].join('\\n'), 'Non è un utente'),\n note('warn', `Scadenza`, `Senza expiresAt né defaultTtlDays il token non scade: imposta serviceTokens.defaultTtlDays o una scadenza esplicita.`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'console',\n group: 'Console',\n title: 'Console amministrativa',\n summary: 'Accesso, sezioni e che cosa si può fare.',\n blocks: [\n p(`La console è servita dal modulo su /api/accessi/console. Non ha stato proprio: ogni azione chiama le API protette con il JWT dell'utente connesso.`),\n list([\n `Utenti: elenco, creazione locale o SSO, profilo, ruoli, grant diretti, stato, 2FA.`,\n `Ruoli e grant: CRUD dei ruoli con l'albero delle abilitazioni.`,\n `Menu e gruppi: CRUD di gruppi, menu e tipi menu.`,\n `Token di servizio: creazione, rotazione, revoca.`,\n `SSO: provider e identità.`,\n `Wiki e Changelog: documentazione e novità della libreria.`,\n `SQL: esporta il DDL delle entità Accessi (tabelle, generatori, FK, indici, check, trigger) per replicare il database.`,\n ]),\n note('info', `Superutente (FLGSUPER)`, `Riceve TUTTE le abilitazioni al livello massimo (30) dopo il login. In console vede solo la sezione Utenti e gestisce utenti, ruoli e grant.`),\n note('info', `Admin (FLGADMIN)`, `Accede a tutta la console (catalogo, token, SSO, utenti) ma NON conferisce abilitazioni: i menu restano quelli assegnati via ABILITAZIONI/RUOLI_MNU. Gerarchia: ADMIN > SUPER > utente.`),\n note('info', `Gerarchia dei ruoli`, `Vale ADMIN (2) > SUPER (1) > utente comune (0). Un attore può gestire solo utenti di rango pari o inferiore: un superutente può amministrare totalmente utenti comuni e superutenti (profilo, password, flag, ruoli, grant, stato, eliminazione e identità/utenti SSO) ma NON può toccare un admin. Un admin può gestire chiunque. La regola è imposta dal backend (ensureCanManageTargetUser): un tentativo non consentito restituisce 403.`),\n note('info', `Gestione dei flag`, `Dalla scheda utente i flag sono mostrati in base al ruolo di chi è connesso: un admin può assegnare o togliere sia il flag Admin sia il flag Superutente; un superutente senza flag Admin può gestire solo il flag Superutente. I flag si impostano anche in creazione (utente locale o SSO).`),\n note('warn', `Ultimo admin`, `Per non restare chiusi fuori dalla console, l'ultimo admin attivo non può essere disattivato, bloccato, eliminato né privato del flag Admin: l'operazione restituisce 400 finché non viene promosso un altro admin.`),\n note('info', `Accesso`, `Apri /api/accessi/console e accedi con un utente con FLGSUPER o FLGADMIN. Le sezioni sono raggiungibili anche tramite deep link (per esempio /api/accessi/console/menu-e-gruppi/menu).`),\n note('info', `Bootstrap admin`, `Con adminBootstrap.enabled il backend stampa a ogni avvio un token casuale (monouso, valido fino al riavvio). La sezione \"Crea utente admin\" nella schermata di login compare solo quando adminBootstrap è abilitato (stato esposto da GET /api/accessi/auth/bootstrap-admin/status): incolla il token e crea il primo admin senza email di conferma. La password è opzionale: se assente viene generata e mostrata una sola volta.`),\n note('ok', `Auto-ingresso dal frontend`, `Se un frontend ha già una sessione Accessi valida e l'utente è abilitato alla console, può aprire la console senza rifare il login: \\`POST /api/accessi/console/entry\\` con \\`Authorization: Bearer <jwt>\\` restituisce un ticket monouso; apri poi \\`/api/accessi/console#entry=<ticket>\\`. Il JWT non finisce mai nell'URL; il ticket dura 60 secondi ed è utilizzabile una sola volta (endpoint di scambio protetto da rate limit).`),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'errori',\n group: 'Riferimenti',\n title: 'Contratto errori e codici',\n summary: 'Formato della risposta e codici stabili ACCESSI_* e AUTH_*.',\n blocks: [\n p(`Gli errori HTTP delle API Accessi usano un contratto stabile: severity, status, statusCode, code, error (campo legacy uguale a code), message e details per gli errori di validazione.`),\n code('json', `{\\n \"severity\": \"error\",\\n \"status\": 400,\\n \"statusCode\": 2,\\n \"code\": \"ACCESSI_VALIDATION_ERROR\",\\n \"error\": \"ACCESSI_VALIDATION_ERROR\",\\n \"message\": \"La richiesta contiene dati non validi.\",\\n \"details\": [\"email: deve essere una stringa.\"]\\n}`, 'Errore di validazione'),\n table(\n ['Codice', 'Quando'],\n [\n ['ACCESSI_VALIDATION_ERROR', 'Body o parametri non validi (400).'],\n ['ACCESSI_AUTH_INITIALIZING', 'Bootstrap in corso (503).'],\n ['ACCESSI_AUTH_NOT_INITIALIZED', 'Modulo non inizializzato (500).'],\n ['AUTH_HEADER_MISSING / AUTH_TOKEN_MISSING', 'Header Authorization assente (401).'],\n ['AUTH_TOKEN_INVALID', 'JWT non valido o scaduto (401).'],\n ['AUTH_USER_DISABLED', 'Utente non più autorizzato (401).'],\n ['AUTH_INSUFFICIENT_PERMISSIONS', 'Requisiti non soddisfatti (403).'],\n ['AUTH_REQUIREMENTS_MISCONFIGURED', 'Policy malformata o handler custom mancante (500).'],\n ['ACCESSI_DATABASE_SCHEMA_OUTDATED', 'Schema del database incompatibile.'],\n ['ACCESSI_SERVICE_TOKEN_RATE_LIMITED', 'Troppi tentativi (429).'],\n ],\n ),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'endpoint',\n group: 'Riferimenti',\n title: 'Indice degli endpoint',\n summary: 'Tutte le rotte /api/accessi/* in una tabella.',\n blocks: [\n table(\n ['Area', 'Endpoint principali'],\n [\n ['Auth', 'POST auth/login, auth/get-user-by-token, auth/confirm-reset-password/:token, auth/two-factor/verify, auth/two-factor/resend'],\n ['Email', 'POST email/send-reset-password-email, GET email/reset-password-page/:token'],\n ['Utenti', 'GET user/get-users, POST user/create-managed-user, PUT user/update-user/:codiceUtente, DELETE user/delete-user/:codiceUtente, DELETE user/delete-user-permanent/:codiceUtente, POST user/set-password/:codiceUtente, POST user/set-stato, PATCH user/set-gdpr/:codiceUtente, POST user/register'],\n ['Permessi', 'GET permission/roles, POST permission/create-role, PUT permission/update-role/:codiceRuolo, DELETE permission/delete-role/:codiceRuolo, POST permission/assign-roles/:codiceUtente, POST permission/assign-permissions/:codiceUtente, GET permission/grants/:codiceUtente, GET permission/menus, GET permission/menu-types, GET permission/groups-with-menus'],\n ['Catalogo', 'POST/PUT/DELETE permission/menus[/:codiceMenu], permission/menu-groups[/:codiceGruppo], permission/menu-types[/:codiceTipo]'],\n ['Configurator', 'PATCH configurator/menus/:codiceMenu/enabled, PATCH configurator/groups/:codiceGruppo/enabled'],\n ['SSO', 'GET/POST federated-auth/providers, PATCH/DELETE providers/:provider, GET/POST users/:codiceUtente/identities, POST users, PATCH users/:codiceUtente/password-login'],\n ['Token', 'POST/GET service-token, DELETE service-token/:tokenId, POST service-token/:tokenId/rotate'],\n ['Schema', 'GET schema (DDL completo + entità presenti, solo admin)'],\n ['Console', 'GET console, console/:view, console/:view/:sub, console/assets/:file'],\n ],\n ),\n ],\n },\n // -------------------------------------------------------------------------\n {\n id: 'antipattern',\n group: 'Riferimenti',\n title: 'Antipattern e checklist',\n summary: 'Che cosa non fare, in breve.',\n blocks: [\n list([\n `Non aprire la porta HTTP prima di initializeAccessiModule.`,\n `Non usare authorizeAccessi come unico strato: valida sempre anche l'input applicativo.`,\n `Non dare per scontato che req.userGrants esista se non hai richiesto dei requisiti.`,\n `Non considerare incrementali assignRolesToUser e assignPermissionsToUser: sono sostitutivi.`,\n `Non far validare i token SSO ad Accessi: passagli solo { provider, subject } verificati.`,\n `Non usare un service token come identità utente.`,\n `Non versionare segreti; jwtOptions.secret deve essere unico per ambiente.`,\n `Non eliminare gruppi o tipi in uso: gestisci l'errore 400 e disabilita invece.`,\n `Non usare il challenge 2FA come JWT.`,\n ]),\n note('ok', `Checklist di rilascio`, `Schema verificato (db:check), segreti da un secret manager, mockDemoUser=false, rate limit pubblico configurato, SMTP reale testato, serviceTokens.defaultTtlDays impostato.`),\n ],\n },\n];\n\n/** Elenco ordinato dei gruppi per l'indice. */\nexport function wikiGroups(): Array<{ group: string; sections: WikiSection[] }> {\n const groups: Array<{ group: string; sections: WikiSection[] }> = [];\n for (const section of WIKI_SECTIONS) {\n let bucket = groups.find((item) => item.group === section.group);\n if (!bucket) {\n bucket = { group: section.group, sections: [] };\n groups.push(bucket);\n }\n bucket.sections.push(section);\n }\n return groups;\n}\n\n/**\n * Digest compatto in Markdown per l'IA che costruisce il backend integratore.\n * Deriva dagli stessi dati della wiki, quindi resta sincronizzato.\n */\nexport function buildAiDigest(): string {\n const lines: string[] = [\n `# Accessi (emilsoftware-utilities) - guida compatta per l'integrazione backend`,\n '',\n `Modulo NestJS/Express + Firebird per autenticazione JWT, autorizzazione per menu, utenti, 2FA e passwordless, SSO generico, service token e console.`,\n '',\n ];\n\n for (const { group, sections } of wikiGroups()) {\n lines.push(`## ${group}`);\n for (const section of sections) {\n lines.push(`### ${section.title}`);\n lines.push(section.summary);\n for (const block of section.blocks) {\n switch (block.kind) {\n case 'p':\n lines.push(block.text);\n break;\n case 'h':\n lines.push(`**${block.text}**`);\n break;\n case 'list':\n block.items.forEach((item, index) => lines.push(`${block.ordered ? `${index + 1}.` : '-'} ${item}`));\n break;\n case 'code':\n lines.push('```' + block.language);\n lines.push(block.code);\n lines.push('```');\n break;\n case 'callout':\n lines.push(`[${block.tone.toUpperCase()}] ${block.title ? block.title + ': ' : ''}${block.text}`);\n break;\n case 'table':\n lines.push('| ' + block.head.join(' | ') + ' |');\n lines.push('| ' + block.head.map(() => '---').join(' | ') + ' |');\n block.rows.forEach((row) => lines.push('| ' + row.join(' | ') + ' |'));\n break;\n }\n }\n lines.push('');\n }\n }\n\n return lines.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim();\n}\n"]}
@@ -23,7 +23,11 @@ export declare class PermissionService {
23
23
  private getUserRoles;
24
24
  /** Costruisce l'albero ruoli/menu a partire dalle righe (riusato da lettura singola e batch). */
25
25
  private assembleRoles;
26
- /** Regola di composizione storica: grant diretti prevalgono, i ruoli si sommano per livello massimo. */
26
+ /**
27
+ * Compone i grant effettivi: permessi diretti e ruoli si sommano per codice menu
28
+ * prendendo il livello massimo. Un livello 0/non valido non rimuove un grant del ruolo.
29
+ * I superutenti ricevono tutti i menu attivi al livello massimo.
30
+ */
27
31
  private composeGrants;
28
32
  /**
29
33
  * API legacy per salvare grant dalla struttura storica menu/gruppi.
@@ -46,7 +50,7 @@ export declare class PermissionService {
46
50
  assignRolesToUser(codiceUtente: number, roles: number[]): Promise<void>;
47
51
  /**
48
52
  * Sostituisce tutti i grant diretti dell'utente. I ruoli rimangono invariati e i loro grant sono aggiunti
49
- * solo nel risultato effettivo; un grant diretto dello stesso menu ha precedenza nella composizione.
53
+ * solo nel risultato effettivo; per lo stesso menu vale il livello massimo tra diretto e ruoli.
50
54
  */
51
55
  assignPermissionsToUser(codiceUtente: number, permissions: Permission[]): Promise<void>;
52
56
  /** Elimina fisicamente ruolo, associazioni menu e assegnazioni utente. Operazione amministrativa irreversibile. */
@@ -76,8 +80,8 @@ export declare class PermissionService {
76
80
  /** Elimina menu, grant utente e associazioni ruolo in un'unica transazione. */
77
81
  deleteMenu(codiceMenu: string): Promise<void>;
78
82
  /**
79
- * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu.
80
- * Per compatibilita storica un grant diretto prevale su quello del ruolo; i superutenti ricevono tutti i menu attivi.
83
+ * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu
84
+ * prendendo il livello massimo. I superutenti ricevono tutti i menu attivi.
81
85
  */
82
86
  getUserRolesAndGrants(codiceUtente: number): Promise<{
83
87
  abilitazioni: AbilitazioneMenu[];
@@ -124,8 +124,15 @@ let PermissionService = class PermissionService {
124
124
  const codiceRuolo = Number(row.codiceRuolo);
125
125
  const descrizioneRuolo = typeof row.descrizioneRuolo === 'string' ? row.descrizioneRuolo.trim() : '';
126
126
  const codiceMenu = typeof row.codiceMenu === 'string' ? row.codiceMenu : undefined;
127
- const descrizioneMenu = typeof row.descrizioneMenu === 'string' ? row.descrizioneMenu : undefined;
128
- const tipoAbilitazione = Number(row.tipoAbilitazione);
127
+ // RUOLI_MNU.TIPABI e MENU.DESMNU sono nullable nello schema storico: un livello
128
+ // mancante non deve far sparire il menu dal ruolo. Default prudente: lettura.
129
+ const rawTipoAbilitazione = row.tipoAbilitazione;
130
+ const parsedTipoAbilitazione = rawTipoAbilitazione === undefined || rawTipoAbilitazione === null || rawTipoAbilitazione === ''
131
+ ? Dtos_1.TipoAbilitazione.LETTURA
132
+ : Number(rawTipoAbilitazione);
133
+ const tipoAbilitazione = (Number.isFinite(parsedTipoAbilitazione)
134
+ ? parsedTipoAbilitazione
135
+ : Dtos_1.TipoAbilitazione.LETTURA);
129
136
  if (!ruoliMap.has(codiceRuolo)) {
130
137
  ruoliMap.set(codiceRuolo, {
131
138
  codiceRuolo,
@@ -133,7 +140,7 @@ let PermissionService = class PermissionService {
133
140
  menu: []
134
141
  });
135
142
  }
136
- if (codiceMenu && descrizioneMenu) {
143
+ if (codiceMenu) {
137
144
  ruoliMap.get(codiceRuolo).menu.push({
138
145
  codiceRuolo,
139
146
  codiceMenu,
@@ -143,23 +150,37 @@ let PermissionService = class PermissionService {
143
150
  }
144
151
  return Array.from(ruoliMap.values());
145
152
  }
146
- /** Regola di composizione storica: grant diretti prevalgono, i ruoli si sommano per livello massimo. */
153
+ /**
154
+ * Compone i grant effettivi: permessi diretti e ruoli si sommano per codice menu
155
+ * prendendo il livello massimo. Un livello 0/non valido non rimuove un grant del ruolo.
156
+ * I superutenti ricevono tutti i menu attivi al livello massimo.
157
+ */
147
158
  composeGrants(abilitazioni, ruoli, isSuperAdmin, allActiveMenus) {
159
+ if (isSuperAdmin)
160
+ return allActiveMenus;
161
+ const catalog = new Map(allActiveMenus.map((menu) => [menu.codiceMenu, menu]));
148
162
  const grantsMap = new Map();
149
- for (const abilitazione of abilitazioni) {
150
- grantsMap.set(abilitazione.codiceMenu, abilitazione);
151
- }
152
- const directMenuCodes = new Set(abilitazioni.map(grant => grant.codiceMenu));
163
+ const addGrant = (grant) => {
164
+ var _a, _b;
165
+ const codiceMenu = typeof grant.codiceMenu === 'string' ? grant.codiceMenu.trim() : '';
166
+ const livello = Number((_a = grant.tipoAbilitazione) !== null && _a !== void 0 ? _a : 0);
167
+ if (!codiceMenu || !Number.isFinite(livello) || livello <= 0)
168
+ return;
169
+ const existing = grantsMap.get(codiceMenu);
170
+ if (!existing || livello > Number((_b = existing.tipoAbilitazione) !== null && _b !== void 0 ? _b : 0)) {
171
+ // I ruoli salvano solo codice e livello: completa i metadati di navigazione
172
+ // (pagina, icona, gruppo, tipo) dal catalogo dei menu attivi.
173
+ const metadata = catalog.get(codiceMenu);
174
+ grantsMap.set(codiceMenu, Object.assign(Object.assign(Object.assign({}, (metadata !== null && metadata !== void 0 ? metadata : {})), grant), { codiceMenu, tipoAbilitazione: livello }));
175
+ }
176
+ };
177
+ for (const abilitazione of abilitazioni)
178
+ addGrant(abilitazione);
153
179
  for (const ruolo of ruoli) {
154
- for (const menu of ruolo.menu) {
155
- const existing = grantsMap.get(menu.codiceMenu);
156
- if (!directMenuCodes.has(menu.codiceMenu) &&
157
- (!existing || Number(menu.tipoAbilitazione) > Number(existing.tipoAbilitazione))) {
158
- grantsMap.set(menu.codiceMenu, menu);
159
- }
160
- }
180
+ for (const menu of ruolo.menu)
181
+ addGrant(menu);
161
182
  }
162
- return isSuperAdmin ? allActiveMenus : Array.from(grantsMap.values());
183
+ return Array.from(grantsMap.values());
163
184
  }
164
185
  /**
165
186
  * API legacy per salvare grant dalla struttura storica menu/gruppi.
@@ -308,7 +329,7 @@ let PermissionService = class PermissionService {
308
329
  }
309
330
  /**
310
331
  * Sostituisce tutti i grant diretti dell'utente. I ruoli rimangono invariati e i loro grant sono aggiunti
311
- * solo nel risultato effettivo; un grant diretto dello stesso menu ha precedenza nella composizione.
332
+ * solo nel risultato effettivo; per lo stesso menu vale il livello massimo tra diretto e ruoli.
312
333
  */
313
334
  assignPermissionsToUser(codiceUtente, permissions) {
314
335
  return __awaiter(this, void 0, void 0, function* () {
@@ -650,8 +671,8 @@ let PermissionService = class PermissionService {
650
671
  });
651
672
  }
652
673
  /**
653
- * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu.
654
- * Per compatibilita storica un grant diretto prevale su quello del ruolo; i superutenti ricevono tutti i menu attivi.
674
+ * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu
675
+ * prendendo il livello massimo. I superutenti ricevono tutti i menu attivi.
655
676
  */
656
677
  getUserRolesAndGrants(codiceUtente) {
657
678
  return __awaiter(this, void 0, void 0, function* () {
@@ -663,7 +684,7 @@ let PermissionService = class PermissionService {
663
684
  const isSuperAdmin = this.isSuperFlag((_a = result.map(Utilities_1.RestUtilities.convertKeysToCamelCase)[0]) === null || _a === void 0 ? void 0 : _a.flagSuper);
664
685
  const abilitazioni = yield this.getUserDirectPermissions(codiceUtente);
665
686
  const ruoli = yield this.getUserRoles(codiceUtente);
666
- const grants = this.composeGrants(abilitazioni, ruoli, isSuperAdmin, isSuperAdmin ? yield this.getAllActiveMenusAsGrants() : []);
687
+ const grants = this.composeGrants(abilitazioni, ruoli, isSuperAdmin, yield this.getAllActiveMenusAsGrants());
667
688
  return { abilitazioni, ruoli, grants };
668
689
  });
669
690
  }
@@ -720,7 +741,7 @@ let PermissionService = class PermissionService {
720
741
  LEFT JOIN MENU M ON RM.CODMNU = M.CODMNU AND M.FLGENABLED = 1
721
742
  LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP
722
743
  WHERE RU.CODUTE IN (${placeholders})`, ids)).map(Utilities_1.RestUtilities.convertKeysToCamelCase);
723
- const allActiveMenus = superUsers.size > 0 ? yield this.getAllActiveMenusAsGrants() : [];
744
+ const allActiveMenus = yield this.getAllActiveMenusAsGrants();
724
745
  const permissionsByUser = new Map();
725
746
  for (const row of permissionRows) {
726
747
  const { codiceUtente } = row, grant = __rest(row, ["codiceUtente"]);
@@ -752,7 +773,7 @@ let PermissionService = class PermissionService {
752
773
  result.set(id, {
753
774
  abilitazioni,
754
775
  ruoli,
755
- grants: this.composeGrants(abilitazioni, ruoli, isSuperAdmin, isSuperAdmin ? allActiveMenus : []),
776
+ grants: this.composeGrants(abilitazioni, ruoli, isSuperAdmin, allActiveMenus),
756
777
  });
757
778
  }
758
779
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"PermissionService.js","sourceRoot":"","sources":["../../../../src/accessi-module/Services/PermissionService/PermissionService.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { Orm } from \"../../../Orm\";\nimport { RestUtilities } from \"../../../Utilities\";\nimport type { AccessiOptions } from \"../../AccessiModule\";\nimport { Permission, TipoAbilitazione } from \"../../Dtos\";\nimport { AbilitazioneMenu } from \"../../Dtos/AbilitazioneMenu\";\nimport { GroupWithMenusEntity } from \"../../Dtos/GetGroupsWithMenusResponse\";\nimport { MenuEntity } from \"../../Dtos/GetMenusResponse\";\nimport { Role } from \"../../Dtos/Role\";\nimport {\n CreateMenuGroupRequest,\n CreateMenuRequest,\n CreateMenuTypeRequest,\n MenuTypeEntity,\n UpdateMenuGroupRequest,\n UpdateMenuRequest,\n UpdateMenuTypeRequest,\n} from \"../../Dtos/CatalogDtos\";\nimport { BadRequestException, Inject, Injectable } from \"@nestjs/common\";\n\n/** Struttura storica menu/gruppi accettata dall'API legacy `addAbilitazioni`. */\ntype LegacyMenuGroup = {\n menu?: Array<{\n flgChk?: unknown;\n codiceMenu?: unknown;\n tipoAbilitazione?: unknown;\n }>;\n};\n\n@Injectable()\nexport class PermissionService {\n constructor(\n @Inject('ACCESSI_OPTIONS') private readonly accessiOptions: AccessiOptions\n ) { }\n\n private getCountFromResult(result: ReadonlyArray<Record<string, unknown>>, fieldName = 'COUNT'): number {\n const rawValue = result?.[0]?.[fieldName]\n ?? result?.[0]?.[fieldName.toLowerCase()]\n ?? result?.[0]?.count;\n return typeof rawValue === 'number' ? rawValue : Number.parseInt(`${rawValue ?? '0'}`, 10);\n }\n\n private extractIntegerColumn(result: ReadonlyArray<Record<string, unknown>>, fieldName: string): Set<number> {\n return new Set(\n result.map((row) => Number(row[fieldName] ?? row[fieldName.toLowerCase()])),\n );\n }\n\n private async getAllActiveMenusAsGrants(): Promise<AbilitazioneMenu[]> {\n const query = `\n SELECT\n M.CODMNU AS codice_menu,\n 30 AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM MENU M\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n `;\n\n return await Orm.query(this.accessiOptions.databaseOptions, query, [])\n .then(results => results.map(RestUtilities.convertKeysToCamelCase)) as AbilitazioneMenu[];\n }\n\n private async getUserDirectPermissions(codiceUtente: number): Promise<AbilitazioneMenu[]> {\n const queryAbilitazioni = `\n SELECT\n A.CODMNU AS codice_menu,\n A.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM ABILITAZIONI A\n INNER JOIN MENU M ON A.CODMNU = M.CODMNU\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE A.CODUTE = ? AND M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n `;\n\n return await Orm.query(this.accessiOptions.databaseOptions, queryAbilitazioni, [codiceUtente])\n .then(results => results.map(RestUtilities.convertKeysToCamelCase)) as AbilitazioneMenu[];\n }\n\n private async getUserRoles(codiceUtente: number): Promise<Role[]> {\n const queryRuoli = `\n SELECT\n R.CODRUO AS codice_ruolo,\n R.DESRUO AS descrizione_ruolo,\n CASE WHEN COALESCE(G.FLGENABLED, 1) = 1 THEN M.CODMNU END AS codice_menu,\n RM.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n M.NOTE AS note\n FROM UTENTI_RUOLI RU\n INNER JOIN RUOLI R ON RU.CODRUO = R.CODRUO\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M\n ON RM.CODMNU = M.CODMNU\n AND M.FLGENABLED = 1\n LEFT JOIN MENU_GRP G\n ON G.CODGRP = M.CODGRP\n WHERE RU.CODUTE = ?\n `;\n const ruoliResult = (await Orm.query(this.accessiOptions.databaseOptions, queryRuoli, [codiceUtente]))\n .map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n return this.assembleRoles(ruoliResult);\n }\n\n /** Costruisce l'albero ruoli/menu a partire dalle righe (riusato da lettura singola e batch). */\n private assembleRoles(ruoliResult: Array<Record<string, unknown>>): Role[] {\n const ruoliMap = new Map<number, Role>();\n for (const row of ruoliResult) {\n const codiceRuolo = Number(row.codiceRuolo);\n const descrizioneRuolo = typeof row.descrizioneRuolo === 'string' ? row.descrizioneRuolo.trim() : '';\n const codiceMenu = typeof row.codiceMenu === 'string' ? row.codiceMenu : undefined;\n const descrizioneMenu = typeof row.descrizioneMenu === 'string' ? row.descrizioneMenu : undefined;\n const tipoAbilitazione = Number(row.tipoAbilitazione) as TipoAbilitazione;\n\n if (!ruoliMap.has(codiceRuolo)) {\n ruoliMap.set(codiceRuolo, {\n codiceRuolo,\n descrizioneRuolo,\n menu: []\n });\n }\n\n if (codiceMenu && descrizioneMenu) {\n ruoliMap.get(codiceRuolo)!.menu.push({\n codiceRuolo,\n codiceMenu,\n tipoAbilitazione,\n });\n }\n }\n\n return Array.from(ruoliMap.values());\n }\n\n /** Regola di composizione storica: grant diretti prevalgono, i ruoli si sommano per livello massimo. */\n private composeGrants(\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n isSuperAdmin: boolean,\n allActiveMenus: AbilitazioneMenu[],\n ): AbilitazioneMenu[] {\n const grantsMap = new Map<string, AbilitazioneMenu>();\n\n for (const abilitazione of abilitazioni) {\n grantsMap.set(abilitazione.codiceMenu, abilitazione);\n }\n\n const directMenuCodes = new Set(abilitazioni.map(grant => grant.codiceMenu));\n for (const ruolo of ruoli) {\n for (const menu of ruolo.menu) {\n const existing = grantsMap.get(menu.codiceMenu);\n if (!directMenuCodes.has(menu.codiceMenu) &&\n (!existing || Number(menu.tipoAbilitazione) > Number(existing.tipoAbilitazione))) {\n grantsMap.set(menu.codiceMenu, menu);\n }\n }\n }\n\n return isSuperAdmin ? allActiveMenus : Array.from(grantsMap.values());\n }\n\n\n /**\n * API legacy per salvare grant dalla struttura storica menu/gruppi.\n * Sostituisce tutti i grant diretti dell'utente; per le nuove integrazioni preferire `assignPermissionsToUser`.\n */\n public async addAbilitazioni(codiceUtente: number, menuAbilitazioni: LegacyMenuGroup[]): Promise<void> {\n const abilitazioniToInsert = menuAbilitazioni\n .flatMap(menuGrp => menuGrp.menu ?? [])\n .filter(menu => menu.flgChk)\n .map(menu => [codiceUtente, menu.codiceMenu, menu.tipoAbilitazione]);\n\n const insertQuery = `UPDATE OR INSERT INTO ABILITAZIONI (CODUTE, CODMNU, TIPABI) VALUES (?, ?, ?)`;\n\n // Delete + insert atomici: un errore a metà non lascia l'utente senza abilitazioni.\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM ABILITAZIONI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...abilitazioniToInsert.map(params => ({ query: insertQuery, params })),\n ]);\n }\n\n\n /** Rimuove tutti i grant diretti, senza toccare ruoli o menu ereditati dai ruoli. */\n public async resetAbilitazioni(codiceUtente: number): Promise<void> {\n const query = \"DELETE FROM ABILITAZIONI WHERE CODUTE = ?\";\n await Orm.execute(this.accessiOptions.databaseOptions, query, [codiceUtente]);\n }\n\n /**\n * Crea o aggiorna un ruolo. In aggiornamento la lista `role.menu` sostituisce integralmente i menu del ruolo.\n * Validare i codici menu nel chiamante quando si usano dati non provenienti dalla console o dalle API Accessi.\n */\n public async updateOrInsertRole(role: Role, codiceRuolo: number | null = null): Promise<void> {\n // Ruolo, sostituzione menu e inserimenti in un'unica transazione: niente ruoli senza menu in caso di errore.\n await Orm.withTransaction(this.accessiOptions.databaseOptions, async (transaction) => {\n let resolvedCodiceRuolo = codiceRuolo;\n\n if (resolvedCodiceRuolo == null) {\n const createdRoleResult = await Orm.transactionQuery<unknown>(\n transaction,\n `INSERT INTO RUOLI (DESRUO) VALUES (?) RETURNING CODRUO`,\n [role.descrizioneRuolo],\n );\n const createdRole = (Array.isArray(createdRoleResult) ? createdRoleResult[0] : createdRoleResult) as Record<string, unknown> | undefined;\n const rawCodiceRuolo = createdRole?.CODRUO ?? createdRole?.codruo;\n const parsedCodiceRuolo = typeof rawCodiceRuolo === 'number'\n ? rawCodiceRuolo\n : Number.parseInt(`${rawCodiceRuolo ?? ''}`, 10);\n if (Number.isNaN(parsedCodiceRuolo)) {\n throw new Error('Creazione ruolo non riuscita: impossibile recuperare CODRUO.');\n }\n resolvedCodiceRuolo = parsedCodiceRuolo;\n } else {\n await Orm.transactionQuery(\n transaction,\n `UPDATE RUOLI SET DESRUO = ? WHERE CODRUO = ?`,\n [role.descrizioneRuolo, resolvedCodiceRuolo],\n );\n\n await Orm.transactionQuery(\n transaction,\n `DELETE FROM RUOLI_MNU WHERE CODRUO = ?`,\n [resolvedCodiceRuolo],\n );\n }\n\n if (resolvedCodiceRuolo === null) {\n throw new Error('Operazione ruolo non riuscita: codice ruolo non valorizzato.');\n }\n\n for (const menu of role.menu) {\n await Orm.transactionQuery(\n transaction,\n `INSERT INTO RUOLI_MNU (CODRUO, CODMNU, TIPABI) VALUES (?, ?, ?)`,\n [resolvedCodiceRuolo, menu.codiceMenu, menu.tipoAbilitazione],\n );\n }\n });\n }\n\n\n /** Restituisce il catalogo ruoli con ogni associazione menu e livello, inclusi ruoli senza menu. */\n public async getRolesWithMenus(): Promise<Role[]> {\n const query = `\n SELECT \n R.CODRUO AS codice_ruolo, \n R.DESRUO AS descrizione_ruolo, \n M.CODMNU AS codice_menu, \n M.DESMNU AS descrizione_menu,\n M.NOTE AS note,\n RM.TIPABI AS tipo_abilitazione\n FROM RUOLI R\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M ON RM.CODMNU = M.CODMNU\n ORDER BY R.CODRUO, M.CODMNU\n `;\n\n const result = (await Orm.query(this.accessiOptions.databaseOptions, query, []))\n .map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const ruoliMap = new Map<number, Role>();\n\n for (const row of result) {\n const codiceRuolo = Number(row.codiceRuolo);\n const descrizioneRuolo = typeof row.descrizioneRuolo === 'string' ? row.descrizioneRuolo.trim() : '';\n const codiceMenu = typeof row.codiceMenu === 'string' ? row.codiceMenu : undefined;\n const rawTipoAbilitazione = row.tipoAbilitazione;\n\n if (!ruoliMap.has(codiceRuolo)) {\n ruoliMap.set(codiceRuolo, {\n codiceRuolo,\n descrizioneRuolo,\n menu: []\n });\n }\n\n const abilitationValue = typeof rawTipoAbilitazione === 'number'\n ? rawTipoAbilitazione\n : Number.parseInt(`${rawTipoAbilitazione ?? ''}`, 10);\n\n if (!codiceMenu || Number.isNaN(abilitationValue) || abilitationValue <= TipoAbilitazione.NESSUNA) {\n continue;\n }\n\n ruoliMap.get(codiceRuolo)!.menu.push({\n codiceRuolo,\n codiceMenu,\n tipoAbilitazione: abilitationValue as TipoAbilitazione,\n });\n }\n\n return Array.from(ruoliMap.values());\n }\n\n\n /**\n * Sostituisce l'intera lista di ruoli dell'utente con codici univoci e verificati.\n * Non modifica i grant diretti: il calcolo effettivo li unisce in `getUserRolesAndGrants`.\n */\n public async assignRolesToUser(codiceUtente: number, roles: number[]): Promise<void> {\n\n const userExistsQuery = `SELECT COUNT(*) FROM UTENTI WHERE CODUTE = ?`;\n let result = await Orm.query(this.accessiOptions.databaseOptions, userExistsQuery, [codiceUtente]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`L'utente con codice ${codiceUtente} non esiste.`);\n }\n\n const normalizedRoles = Array.from(\n new Set(\n roles\n .map((role) => Number.parseInt(`${role ?? ''}`, 10))\n .filter((role) => Number.isInteger(role) && role > 0),\n ),\n );\n\n const queriesWithParams = [\n { query: `DELETE FROM UTENTI_RUOLI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...normalizedRoles.map((codiceRuolo) => ({\n query: `INSERT INTO UTENTI_RUOLI (CODUTE, CODRUO) VALUES (?, ?)`,\n params: [codiceUtente, codiceRuolo],\n })),\n ];\n\n if (normalizedRoles.length > 0) {\n // Un'unica query IN invece di N count: stessa validazione, meno round-trip.\n const placeholders = normalizedRoles.map(() => '?').join(', ');\n const existingRolesResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODRUO FROM RUOLI WHERE CODRUO IN (${placeholders})`,\n normalizedRoles,\n );\n const existingRoles = this.extractIntegerColumn(existingRolesResult, 'CODRUO');\n const missingRole = normalizedRoles.find((codiceRuolo) => !existingRoles.has(codiceRuolo));\n if (missingRole !== undefined) {\n throw new Error(`Il ruolo con codice ${missingRole} non esiste.`);\n }\n }\n\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, queriesWithParams);\n\n const assignedRolesResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM UTENTI_RUOLI WHERE CODUTE = ?`,\n [codiceUtente],\n );\n const assignedRolesCount = this.getCountFromResult(assignedRolesResult);\n if (assignedRolesCount !== normalizedRoles.length) {\n throw new Error(\n `Persistenza ruoli non coerente per utente ${codiceUtente}: attesi ${normalizedRoles.length}, trovati ${assignedRolesCount}.`,\n );\n }\n }\n\n\n /**\n * Sostituisce tutti i grant diretti dell'utente. I ruoli rimangono invariati e i loro grant sono aggiunti\n * solo nel risultato effettivo; un grant diretto dello stesso menu ha precedenza nella composizione.\n */\n public async assignPermissionsToUser(codiceUtente: number, permissions: Permission[]): Promise<void> {\n\n const userExistsQuery = `SELECT COUNT(*) FROM UTENTI WHERE CODUTE = ?`;\n let result = await Orm.query(this.accessiOptions.databaseOptions, userExistsQuery, [codiceUtente]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`L'utente con codice ${codiceUtente} non esiste.`);\n }\n\n const normalizedPermissions = Array.from(\n new Map(\n permissions\n .map((permission) => ({\n codiceMenu: `${permission?.codiceMenu ?? ''}`.trim(),\n tipoAbilitazione: Number.parseInt(`${permission?.tipoAbilitazione ?? ''}`, 10),\n }))\n .filter(\n (permission) =>\n permission.codiceMenu.length > 0 && Number.isInteger(permission.tipoAbilitazione),\n )\n .map((permission) => [permission.codiceMenu, permission] as const),\n ).values(),\n );\n\n const queriesWithParams = [\n { query: `DELETE FROM ABILITAZIONI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...normalizedPermissions.map((permission) => ({\n query: `INSERT INTO ABILITAZIONI (CODUTE, CODMNU, TIPABI) VALUES (?, ?, ?)`,\n params: [codiceUtente, permission.codiceMenu, permission.tipoAbilitazione],\n })),\n ];\n\n if (normalizedPermissions.length > 0) {\n // Un'unica query IN invece di N count: stessa validazione, meno round-trip.\n const menuCodes = normalizedPermissions.map((permission) => permission.codiceMenu);\n const placeholders = menuCodes.map(() => '?').join(', ');\n const existingMenusResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODMNU FROM MENU WHERE CODMNU IN (${placeholders})`,\n menuCodes,\n );\n const existingMenus = new Set(\n existingMenusResult.map((row) => String((row as Record<string, unknown>).CODMNU ?? (row as Record<string, unknown>).codmnu ?? '')),\n );\n const missingMenu = menuCodes.find((codiceMenu) => !existingMenus.has(codiceMenu));\n if (missingMenu !== undefined) {\n throw new Error(`Il menu con codice ${missingMenu} non esiste.`);\n }\n }\n\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, queriesWithParams);\n\n const assignedPermissionsResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM ABILITAZIONI WHERE CODUTE = ?`,\n [codiceUtente],\n );\n const assignedPermissionsCount = this.getCountFromResult(assignedPermissionsResult);\n if (assignedPermissionsCount !== normalizedPermissions.length) {\n throw new Error(\n `Persistenza abilitazioni non coerente per utente ${codiceUtente}: attese ${normalizedPermissions.length}, trovate ${assignedPermissionsCount}.`,\n );\n }\n }\n\n\n /** Elimina fisicamente ruolo, associazioni menu e assegnazioni utente. Operazione amministrativa irreversibile. */\n public async deleteRole(codiceRuolo: number): Promise<void> {\n\n const existsQuery = `SELECT COUNT(*) FROM RUOLI WHERE CODRUO = ?`;\n const result = await Orm.query(this.accessiOptions.databaseOptions, existsQuery, [codiceRuolo]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`Il ruolo con codice ${codiceRuolo} non esiste.`);\n }\n\n // Le tre delete sono atomiche: nessun ruolo orfano se una fallisce.\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM RUOLI_MNU WHERE CODRUO = ?`, params: [codiceRuolo] },\n { query: `DELETE FROM UTENTI_RUOLI WHERE CODRUO = ?`, params: [codiceRuolo] },\n { query: `DELETE FROM RUOLI WHERE CODRUO = ?`, params: [codiceRuolo] },\n ]);\n\n }\n\n\n /** Restituisce solo menu abilitati; per il catalogo amministrativo usare `getGroupsWithMenus(true)`. */\n public async getMenus(): Promise<MenuEntity[]> {\n const query = `\n SELECT \n M.CODMNU AS codiceMenu, \n M.DESMNU AS descrizioneMenu,\n M.CODGRP AS codiceGruppo,\n G.DESGRP AS descrizioneGruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM MENU M\n LEFT JOIN MENU_GRP G ON M.CODGRP = G.CODGRP\n WHERE M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n ORDER BY G.CODGRP, M.CODMNU\n `;\n\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n return result.map(RestUtilities.convertKeysToCamelCase) as unknown as MenuEntity[];\n }\n\n\n /**\n * Restituisce il catalogo ordinato per gruppo e menu. `includeDisabled` e riservato a console e amministrazione:\n * i flussi applicativi di autorizzazione devono usare il comportamento predefinito, che nasconde voci disabilitate.\n */\n public async getGroupsWithMenus(includeDisabled = false): Promise<GroupWithMenusEntity[]> {\n const filtersClause = includeDisabled\n ? ''\n : 'WHERE M.FLGENABLED = 1 AND (G.FLGENABLED IS NULL OR G.FLGENABLED = 1)';\n\n const query = `\n SELECT\n M.CODMNU AS codice_menu,\n M.DESMNU AS descrizione_menu,\n M.CODGRP AS codice_gruppo,\n G.DESGRP AS descrizione_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note,\n G.ORDINE AS ordine_gruppo,\n M.ORDINE as ordine_menu,\n M.FLGENABLED AS menu_enabled,\n G.FLGENABLED AS group_enabled\n FROM MENU M\n LEFT JOIN MENU_GRP G ON M.CODGRP = G.CODGRP\n ${filtersClause}\n ORDER BY G.CODGRP, M.CODMNU\n `;\n\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n\n const groupMap = new Map<\n string,\n GroupWithMenusEntity & { menus: (MenuEntity & { enabled?: boolean })[] }\n >();\n\n result.forEach((row: unknown) => {\n const converted = RestUtilities.convertKeysToCamelCase(row) as MenuEntity & {\n menuEnabled?: number | boolean;\n groupEnabled?: number | boolean;\n };\n\n const { menuEnabled, groupEnabled, ...menuBase } = converted;\n const normalizedGroupKey = menuBase.codiceGruppo ?? '__UNGROUPED__';\n const groupEnabledFlag =\n groupEnabled == null ? true : Number(groupEnabled) === 1 || groupEnabled === true;\n const menuEnabledFlag =\n menuEnabled === undefined ? true : Number(menuEnabled) === 1 || menuEnabled === true;\n\n if (!groupMap.has(normalizedGroupKey)) {\n groupMap.set(normalizedGroupKey, {\n codiceGruppo: menuBase.codiceGruppo ?? normalizedGroupKey,\n descrizioneGruppo: menuBase.descrizioneGruppo ?? '',\n ordineGruppo: menuBase.ordineGruppo,\n enabled: groupEnabledFlag,\n menus: [],\n });\n }\n\n if (menuBase.codiceMenu) {\n groupMap.get(normalizedGroupKey)!.menus.push({\n ...menuBase,\n enabled: menuEnabledFlag,\n });\n }\n });\n\n const groupsArray = Array.from(groupMap.values())\n .map(group => ({\n ...group,\n menus: (group.menus ?? []).sort(\n (a, b) =>\n (a.ordineMenu ?? Number.MAX_SAFE_INTEGER) - (b.ordineMenu ?? Number.MAX_SAFE_INTEGER),\n ),\n }))\n .sort(\n (a, b) =>\n (a.ordineGruppo ?? Number.MAX_SAFE_INTEGER) - (b.ordineGruppo ?? Number.MAX_SAFE_INTEGER),\n );\n\n return groupsArray;\n }\n\n // -----------------------------------------------------------------------\n // Cataloghi di configurazione: CRUD di menu, gruppi, tipi menu.\n // `table` e `column` sono sempre costanti interne, mai input utente: i\n // valori sono parametrizzati, quindi non esiste superficie di SQL injection.\n // -----------------------------------------------------------------------\n\n private async countWhere(table: string, column: string, value: unknown): Promise<number> {\n const result = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM ${table} WHERE ${column} = ?`,\n [value],\n );\n return this.getCountFromResult(result);\n }\n\n private async assertMenuGroupExists(codiceGruppo: string): Promise<void> {\n if ((await this.countWhere('MENU_GRP', 'CODGRP', codiceGruppo)) === 0) {\n throw new BadRequestException(`Il gruppo menu ${codiceGruppo} non esiste.`);\n }\n }\n\n private async assertMenuTypeExists(codiceTipo: string): Promise<void> {\n if ((await this.countWhere('MENU_TIPI', 'CODTIP', codiceTipo)) === 0) {\n throw new BadRequestException(`Il tipo menu ${codiceTipo} non esiste.`);\n }\n }\n\n /** Catalogo completo dei tipi menu, usato dagli editor di configurazione. */\n public async getMenuTypes(): Promise<MenuTypeEntity[]> {\n const query = `SELECT CODTIP AS codice_tipo, DESTIP AS descrizione_tipo FROM MENU_TIPI ORDER BY CODTIP`;\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n return result.map(RestUtilities.convertKeysToCamelCase) as unknown as MenuTypeEntity[];\n }\n\n public async createMenuType(input: CreateMenuTypeRequest): Promise<void> {\n if ((await this.countWhere('MENU_TIPI', 'CODTIP', input.codiceTipo)) > 0) {\n throw new BadRequestException(`Il tipo menu ${input.codiceTipo} esiste gia.`);\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU_TIPI (CODTIP, DESTIP) VALUES (?, ?)`,\n [input.codiceTipo, input.descrizioneTipo ?? null],\n );\n }\n\n public async updateMenuType(codiceTipo: string, input: UpdateMenuTypeRequest): Promise<void> {\n await this.assertMenuTypeExists(codiceTipo);\n if (input.descrizioneTipo === undefined) return;\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU_TIPI SET DESTIP = ? WHERE CODTIP = ?`,\n [input.descrizioneTipo, codiceTipo],\n );\n }\n\n /** Elimina un tipo menu solo se nessun menu lo utilizza (la FK lo impedirebbe comunque). */\n public async deleteMenuType(codiceTipo: string): Promise<void> {\n await this.assertMenuTypeExists(codiceTipo);\n const used = await this.countWhere('MENU', 'CODTIP', codiceTipo);\n if (used > 0) {\n throw new BadRequestException(\n `Impossibile eliminare il tipo ${codiceTipo}: ${used} menu lo utilizzano.`,\n );\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `DELETE FROM MENU_TIPI WHERE CODTIP = ?`,\n [codiceTipo],\n );\n }\n\n public async createMenuGroup(input: CreateMenuGroupRequest): Promise<void> {\n if ((await this.countWhere('MENU_GRP', 'CODGRP', input.codiceGruppo)) > 0) {\n throw new BadRequestException(`Il gruppo menu ${input.codiceGruppo} esiste gia.`);\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU_GRP (CODGRP, DESGRP, FLGENABLED, ORDINE) VALUES (?, ?, ?, ?)`,\n [\n input.codiceGruppo,\n input.descrizioneGruppo ?? null,\n input.enabled === false ? 0 : 1,\n input.ordineGruppo ?? 0,\n ],\n );\n }\n\n public async updateMenuGroup(codiceGruppo: string, input: UpdateMenuGroupRequest): Promise<void> {\n await this.assertMenuGroupExists(codiceGruppo);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.descrizioneGruppo !== undefined) {\n sets.push('DESGRP = ?');\n params.push(input.descrizioneGruppo);\n }\n if (input.ordineGruppo !== undefined) {\n sets.push('ORDINE = ?');\n params.push(input.ordineGruppo);\n }\n if (input.enabled !== undefined) {\n sets.push('FLGENABLED = ?');\n params.push(input.enabled ? 1 : 0);\n }\n if (sets.length === 0) return;\n\n params.push(codiceGruppo);\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU_GRP SET ${sets.join(', ')} WHERE CODGRP = ?`,\n params,\n );\n }\n\n /** Elimina un gruppo solo se non contiene menu, per non lasciare menu orfani. */\n public async deleteMenuGroup(codiceGruppo: string): Promise<void> {\n await this.assertMenuGroupExists(codiceGruppo);\n const used = await this.countWhere('MENU', 'CODGRP', codiceGruppo);\n if (used > 0) {\n throw new BadRequestException(\n `Impossibile eliminare il gruppo ${codiceGruppo}: contiene ${used} menu.`,\n );\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `DELETE FROM MENU_GRP WHERE CODGRP = ?`,\n [codiceGruppo],\n );\n }\n\n public async createMenu(input: CreateMenuRequest): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', input.codiceMenu)) > 0) {\n throw new BadRequestException(`Il menu ${input.codiceMenu} esiste gia.`);\n }\n await this.assertMenuGroupExists(input.codiceGruppo);\n if (input.tipo) await this.assertMenuTypeExists(input.tipo);\n\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU (CODMNU, DESMNU, CODGRP, FLGENABLED, ICON, ORDINE, CODTIP, PAGINA, RIFMENU, NOTE)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n input.codiceMenu,\n input.descrizioneMenu ?? null,\n input.codiceGruppo,\n input.enabled === false ? 0 : 1,\n input.icona ?? null,\n input.ordineMenu ?? null,\n input.tipo ?? null,\n input.pagina ?? null,\n input.rifMenu ?? null,\n input.note ?? null,\n ],\n );\n }\n\n public async updateMenu(codiceMenu: string, input: UpdateMenuRequest): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', codiceMenu)) === 0) {\n throw new BadRequestException(`Il menu ${codiceMenu} non esiste.`);\n }\n if (input.codiceGruppo !== undefined) await this.assertMenuGroupExists(input.codiceGruppo);\n if (input.tipo) await this.assertMenuTypeExists(input.tipo);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.descrizioneMenu !== undefined) {\n sets.push('DESMNU = ?');\n params.push(input.descrizioneMenu);\n }\n if (input.codiceGruppo !== undefined) {\n sets.push('CODGRP = ?');\n params.push(input.codiceGruppo);\n }\n if (input.tipo !== undefined) {\n sets.push('CODTIP = ?');\n params.push(input.tipo || null);\n }\n if (input.icona !== undefined) {\n sets.push('ICON = ?');\n params.push(input.icona);\n }\n if (input.pagina !== undefined) {\n sets.push('PAGINA = ?');\n params.push(input.pagina);\n }\n if (input.ordineMenu !== undefined) {\n sets.push('ORDINE = ?');\n params.push(input.ordineMenu);\n }\n if (input.enabled !== undefined) {\n sets.push('FLGENABLED = ?');\n params.push(input.enabled ? 1 : 0);\n }\n if (input.rifMenu !== undefined) {\n sets.push('RIFMENU = ?');\n params.push(input.rifMenu);\n }\n if (input.note !== undefined) {\n sets.push('NOTE = ?');\n params.push(input.note);\n }\n if (sets.length === 0) return;\n\n params.push(codiceMenu);\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU SET ${sets.join(', ')} WHERE CODMNU = ?`,\n params,\n );\n }\n\n /** Elimina menu, grant utente e associazioni ruolo in un'unica transazione. */\n public async deleteMenu(codiceMenu: string): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', codiceMenu)) === 0) {\n throw new BadRequestException(`Il menu ${codiceMenu} non esiste.`);\n }\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM ABILITAZIONI WHERE CODMNU = ?`, params: [codiceMenu] },\n { query: `DELETE FROM RUOLI_MNU WHERE CODMNU = ?`, params: [codiceMenu] },\n { query: `DELETE FROM MENU WHERE CODMNU = ?`, params: [codiceMenu] },\n ]);\n }\n\n\n /**\n * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu.\n * Per compatibilita storica un grant diretto prevale su quello del ruolo; i superutenti ricevono tutti i menu attivi.\n */\n public async getUserRolesAndGrants(codiceUtente: number): Promise<{\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n grants: AbilitazioneMenu[]\n }> {\n const codiceUtenteQuery = \"SELECT FLGSUPER as flag_super FROM UTENTI_CONFIG WHERE CODUTE = ?\";\n const result = await Orm.query(this.accessiOptions.databaseOptions, codiceUtenteQuery, [codiceUtente]);\n if (!result || result.length == 0) throw new Error(\"Nessun utente trovato con il codice utente \" + codiceUtente);\n\n const isSuperAdmin = this.isSuperFlag(\n (result.map(RestUtilities.convertKeysToCamelCase)[0] as { flagSuper?: unknown } | undefined)?.flagSuper,\n );\n\n const abilitazioni = await this.getUserDirectPermissions(codiceUtente);\n const ruoli = await this.getUserRoles(codiceUtente);\n const grants = this.composeGrants(\n abilitazioni,\n ruoli,\n isSuperAdmin,\n isSuperAdmin ? await this.getAllActiveMenusAsGrants() : [],\n );\n\n return { abilitazioni, ruoli, grants };\n }\n\n private isSuperFlag(value: unknown): boolean {\n return value === true || value === 1 || value === '1';\n }\n\n /**\n * Versione batch di `getUserRolesAndGrants`: esegue un numero costante di query per N utenti\n * (invece di N set di query) e restituisce mappa per codice utente. Stessa composizione della\n * lettura singola.\n */\n public async getUsersRolesAndGrants(codiciUtente: number[]): Promise<Map<number, {\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n grants: AbilitazioneMenu[]\n }>> {\n const result = new Map<number, { abilitazioni: AbilitazioneMenu[], ruoli: Role[], grants: AbilitazioneMenu[] }>();\n const ids = Array.from(new Set(codiciUtente.filter((id) => Number.isInteger(id) && id > 0)));\n if (ids.length === 0) {\n return result;\n }\n\n const placeholders = ids.map(() => '?').join(', ');\n\n const superRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODUTE AS codice_utente, FLGSUPER AS flag_super FROM UTENTI_CONFIG WHERE CODUTE IN (${placeholders})`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n const superUsers = new Set<number>();\n for (const row of superRows) {\n if (this.isSuperFlag(row.flagSuper)) {\n superUsers.add(Number(row.codiceUtente));\n }\n }\n\n const permissionRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT\n A.CODUTE AS codice_utente,\n A.CODMNU AS codice_menu,\n A.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM ABILITAZIONI A\n INNER JOIN MENU M ON A.CODMNU = M.CODMNU\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE A.CODUTE IN (${placeholders}) AND M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const roleRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT\n RU.CODUTE AS codice_utente,\n R.CODRUO AS codice_ruolo,\n R.DESRUO AS descrizione_ruolo,\n CASE WHEN COALESCE(G.FLGENABLED, 1) = 1 THEN M.CODMNU END AS codice_menu,\n RM.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n M.NOTE AS note\n FROM UTENTI_RUOLI RU\n INNER JOIN RUOLI R ON RU.CODRUO = R.CODRUO\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M ON RM.CODMNU = M.CODMNU AND M.FLGENABLED = 1\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE RU.CODUTE IN (${placeholders})`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const allActiveMenus = superUsers.size > 0 ? await this.getAllActiveMenusAsGrants() : [];\n\n const permissionsByUser = new Map<number, AbilitazioneMenu[]>();\n for (const row of permissionRows) {\n const { codiceUtente, ...grant } = row;\n const key = Number(codiceUtente);\n const bucket = permissionsByUser.get(key);\n if (bucket) {\n bucket.push(grant as unknown as AbilitazioneMenu);\n } else {\n permissionsByUser.set(key, [grant as unknown as AbilitazioneMenu]);\n }\n }\n\n const rolesByUser = new Map<number, Array<Record<string, unknown>>>();\n for (const row of roleRows) {\n const { codiceUtente, ...roleRow } = row;\n const key = Number(codiceUtente);\n const bucket = rolesByUser.get(key);\n if (bucket) {\n bucket.push(roleRow);\n } else {\n rolesByUser.set(key, [roleRow]);\n }\n }\n\n for (const id of ids) {\n const abilitazioni = permissionsByUser.get(id) ?? [];\n const ruoli = this.assembleRoles(rolesByUser.get(id) ?? []);\n const isSuperAdmin = superUsers.has(id);\n result.set(id, {\n abilitazioni,\n ruoli,\n grants: this.composeGrants(abilitazioni, ruoli, isSuperAdmin, isSuperAdmin ? allActiveMenus : []),\n });\n }\n\n return result;\n }\n\n\n\n}\n\n\n"]}
1
+ {"version":3,"file":"PermissionService.js","sourceRoot":"","sources":["../../../../src/accessi-module/Services/PermissionService/PermissionService.ts"],"names":[],"mappings":"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","sourcesContent":["\nimport { Orm } from \"../../../Orm\";\nimport { RestUtilities } from \"../../../Utilities\";\nimport type { AccessiOptions } from \"../../AccessiModule\";\nimport { Permission, TipoAbilitazione } from \"../../Dtos\";\nimport { AbilitazioneMenu } from \"../../Dtos/AbilitazioneMenu\";\nimport { GroupWithMenusEntity } from \"../../Dtos/GetGroupsWithMenusResponse\";\nimport { MenuEntity } from \"../../Dtos/GetMenusResponse\";\nimport { Role } from \"../../Dtos/Role\";\nimport {\n CreateMenuGroupRequest,\n CreateMenuRequest,\n CreateMenuTypeRequest,\n MenuTypeEntity,\n UpdateMenuGroupRequest,\n UpdateMenuRequest,\n UpdateMenuTypeRequest,\n} from \"../../Dtos/CatalogDtos\";\nimport { BadRequestException, Inject, Injectable } from \"@nestjs/common\";\n\n/** Struttura storica menu/gruppi accettata dall'API legacy `addAbilitazioni`. */\ntype LegacyMenuGroup = {\n menu?: Array<{\n flgChk?: unknown;\n codiceMenu?: unknown;\n tipoAbilitazione?: unknown;\n }>;\n};\n\n@Injectable()\nexport class PermissionService {\n constructor(\n @Inject('ACCESSI_OPTIONS') private readonly accessiOptions: AccessiOptions\n ) { }\n\n private getCountFromResult(result: ReadonlyArray<Record<string, unknown>>, fieldName = 'COUNT'): number {\n const rawValue = result?.[0]?.[fieldName]\n ?? result?.[0]?.[fieldName.toLowerCase()]\n ?? result?.[0]?.count;\n return typeof rawValue === 'number' ? rawValue : Number.parseInt(`${rawValue ?? '0'}`, 10);\n }\n\n private extractIntegerColumn(result: ReadonlyArray<Record<string, unknown>>, fieldName: string): Set<number> {\n return new Set(\n result.map((row) => Number(row[fieldName] ?? row[fieldName.toLowerCase()])),\n );\n }\n\n private async getAllActiveMenusAsGrants(): Promise<AbilitazioneMenu[]> {\n const query = `\n SELECT\n M.CODMNU AS codice_menu,\n 30 AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM MENU M\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n `;\n\n return await Orm.query(this.accessiOptions.databaseOptions, query, [])\n .then(results => results.map(RestUtilities.convertKeysToCamelCase)) as AbilitazioneMenu[];\n }\n\n private async getUserDirectPermissions(codiceUtente: number): Promise<AbilitazioneMenu[]> {\n const queryAbilitazioni = `\n SELECT\n A.CODMNU AS codice_menu,\n A.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM ABILITAZIONI A\n INNER JOIN MENU M ON A.CODMNU = M.CODMNU\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE A.CODUTE = ? AND M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n `;\n\n return await Orm.query(this.accessiOptions.databaseOptions, queryAbilitazioni, [codiceUtente])\n .then(results => results.map(RestUtilities.convertKeysToCamelCase)) as AbilitazioneMenu[];\n }\n\n private async getUserRoles(codiceUtente: number): Promise<Role[]> {\n const queryRuoli = `\n SELECT\n R.CODRUO AS codice_ruolo,\n R.DESRUO AS descrizione_ruolo,\n CASE WHEN COALESCE(G.FLGENABLED, 1) = 1 THEN M.CODMNU END AS codice_menu,\n RM.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n M.NOTE AS note\n FROM UTENTI_RUOLI RU\n INNER JOIN RUOLI R ON RU.CODRUO = R.CODRUO\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M\n ON RM.CODMNU = M.CODMNU\n AND M.FLGENABLED = 1\n LEFT JOIN MENU_GRP G\n ON G.CODGRP = M.CODGRP\n WHERE RU.CODUTE = ?\n `;\n const ruoliResult = (await Orm.query(this.accessiOptions.databaseOptions, queryRuoli, [codiceUtente]))\n .map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n return this.assembleRoles(ruoliResult);\n }\n\n /** Costruisce l'albero ruoli/menu a partire dalle righe (riusato da lettura singola e batch). */\n private assembleRoles(ruoliResult: Array<Record<string, unknown>>): Role[] {\n const ruoliMap = new Map<number, Role>();\n for (const row of ruoliResult) {\n const codiceRuolo = Number(row.codiceRuolo);\n const descrizioneRuolo = typeof row.descrizioneRuolo === 'string' ? row.descrizioneRuolo.trim() : '';\n const codiceMenu = typeof row.codiceMenu === 'string' ? row.codiceMenu : undefined;\n // RUOLI_MNU.TIPABI e MENU.DESMNU sono nullable nello schema storico: un livello\n // mancante non deve far sparire il menu dal ruolo. Default prudente: lettura.\n const rawTipoAbilitazione = row.tipoAbilitazione;\n const parsedTipoAbilitazione = rawTipoAbilitazione === undefined || rawTipoAbilitazione === null || rawTipoAbilitazione === ''\n ? TipoAbilitazione.LETTURA\n : Number(rawTipoAbilitazione);\n const tipoAbilitazione = (Number.isFinite(parsedTipoAbilitazione)\n ? parsedTipoAbilitazione\n : TipoAbilitazione.LETTURA) as TipoAbilitazione;\n\n if (!ruoliMap.has(codiceRuolo)) {\n ruoliMap.set(codiceRuolo, {\n codiceRuolo,\n descrizioneRuolo,\n menu: []\n });\n }\n\n if (codiceMenu) {\n ruoliMap.get(codiceRuolo)!.menu.push({\n codiceRuolo,\n codiceMenu,\n tipoAbilitazione,\n });\n }\n }\n\n return Array.from(ruoliMap.values());\n }\n\n /**\n * Compone i grant effettivi: permessi diretti e ruoli si sommano per codice menu\n * prendendo il livello massimo. Un livello 0/non valido non rimuove un grant del ruolo.\n * I superutenti ricevono tutti i menu attivi al livello massimo.\n */\n private composeGrants(\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n isSuperAdmin: boolean,\n allActiveMenus: AbilitazioneMenu[],\n ): AbilitazioneMenu[] {\n if (isSuperAdmin) return allActiveMenus;\n\n const catalog = new Map(allActiveMenus.map((menu) => [menu.codiceMenu, menu]));\n const grantsMap = new Map<string, AbilitazioneMenu>();\n const addGrant = (grant: AbilitazioneMenu | { codiceMenu?: string; tipoAbilitazione?: unknown }) => {\n const codiceMenu = typeof grant.codiceMenu === 'string' ? grant.codiceMenu.trim() : '';\n const livello = Number(grant.tipoAbilitazione ?? 0);\n if (!codiceMenu || !Number.isFinite(livello) || livello <= 0) return;\n const existing = grantsMap.get(codiceMenu);\n if (!existing || livello > Number(existing.tipoAbilitazione ?? 0)) {\n // I ruoli salvano solo codice e livello: completa i metadati di navigazione\n // (pagina, icona, gruppo, tipo) dal catalogo dei menu attivi.\n const metadata = catalog.get(codiceMenu);\n grantsMap.set(codiceMenu, {\n ...(metadata ?? {}),\n ...(grant as AbilitazioneMenu),\n codiceMenu,\n tipoAbilitazione: livello as TipoAbilitazione,\n });\n }\n };\n\n for (const abilitazione of abilitazioni) addGrant(abilitazione);\n for (const ruolo of ruoli) {\n for (const menu of ruolo.menu) addGrant(menu);\n }\n\n return Array.from(grantsMap.values());\n }\n\n\n /**\n * API legacy per salvare grant dalla struttura storica menu/gruppi.\n * Sostituisce tutti i grant diretti dell'utente; per le nuove integrazioni preferire `assignPermissionsToUser`.\n */\n public async addAbilitazioni(codiceUtente: number, menuAbilitazioni: LegacyMenuGroup[]): Promise<void> {\n const abilitazioniToInsert = menuAbilitazioni\n .flatMap(menuGrp => menuGrp.menu ?? [])\n .filter(menu => menu.flgChk)\n .map(menu => [codiceUtente, menu.codiceMenu, menu.tipoAbilitazione]);\n\n const insertQuery = `UPDATE OR INSERT INTO ABILITAZIONI (CODUTE, CODMNU, TIPABI) VALUES (?, ?, ?)`;\n\n // Delete + insert atomici: un errore a metà non lascia l'utente senza abilitazioni.\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM ABILITAZIONI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...abilitazioniToInsert.map(params => ({ query: insertQuery, params })),\n ]);\n }\n\n\n /** Rimuove tutti i grant diretti, senza toccare ruoli o menu ereditati dai ruoli. */\n public async resetAbilitazioni(codiceUtente: number): Promise<void> {\n const query = \"DELETE FROM ABILITAZIONI WHERE CODUTE = ?\";\n await Orm.execute(this.accessiOptions.databaseOptions, query, [codiceUtente]);\n }\n\n /**\n * Crea o aggiorna un ruolo. In aggiornamento la lista `role.menu` sostituisce integralmente i menu del ruolo.\n * Validare i codici menu nel chiamante quando si usano dati non provenienti dalla console o dalle API Accessi.\n */\n public async updateOrInsertRole(role: Role, codiceRuolo: number | null = null): Promise<void> {\n // Ruolo, sostituzione menu e inserimenti in un'unica transazione: niente ruoli senza menu in caso di errore.\n await Orm.withTransaction(this.accessiOptions.databaseOptions, async (transaction) => {\n let resolvedCodiceRuolo = codiceRuolo;\n\n if (resolvedCodiceRuolo == null) {\n const createdRoleResult = await Orm.transactionQuery<unknown>(\n transaction,\n `INSERT INTO RUOLI (DESRUO) VALUES (?) RETURNING CODRUO`,\n [role.descrizioneRuolo],\n );\n const createdRole = (Array.isArray(createdRoleResult) ? createdRoleResult[0] : createdRoleResult) as Record<string, unknown> | undefined;\n const rawCodiceRuolo = createdRole?.CODRUO ?? createdRole?.codruo;\n const parsedCodiceRuolo = typeof rawCodiceRuolo === 'number'\n ? rawCodiceRuolo\n : Number.parseInt(`${rawCodiceRuolo ?? ''}`, 10);\n if (Number.isNaN(parsedCodiceRuolo)) {\n throw new Error('Creazione ruolo non riuscita: impossibile recuperare CODRUO.');\n }\n resolvedCodiceRuolo = parsedCodiceRuolo;\n } else {\n await Orm.transactionQuery(\n transaction,\n `UPDATE RUOLI SET DESRUO = ? WHERE CODRUO = ?`,\n [role.descrizioneRuolo, resolvedCodiceRuolo],\n );\n\n await Orm.transactionQuery(\n transaction,\n `DELETE FROM RUOLI_MNU WHERE CODRUO = ?`,\n [resolvedCodiceRuolo],\n );\n }\n\n if (resolvedCodiceRuolo === null) {\n throw new Error('Operazione ruolo non riuscita: codice ruolo non valorizzato.');\n }\n\n for (const menu of role.menu) {\n await Orm.transactionQuery(\n transaction,\n `INSERT INTO RUOLI_MNU (CODRUO, CODMNU, TIPABI) VALUES (?, ?, ?)`,\n [resolvedCodiceRuolo, menu.codiceMenu, menu.tipoAbilitazione],\n );\n }\n });\n }\n\n\n /** Restituisce il catalogo ruoli con ogni associazione menu e livello, inclusi ruoli senza menu. */\n public async getRolesWithMenus(): Promise<Role[]> {\n const query = `\n SELECT \n R.CODRUO AS codice_ruolo, \n R.DESRUO AS descrizione_ruolo, \n M.CODMNU AS codice_menu, \n M.DESMNU AS descrizione_menu,\n M.NOTE AS note,\n RM.TIPABI AS tipo_abilitazione\n FROM RUOLI R\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M ON RM.CODMNU = M.CODMNU\n ORDER BY R.CODRUO, M.CODMNU\n `;\n\n const result = (await Orm.query(this.accessiOptions.databaseOptions, query, []))\n .map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const ruoliMap = new Map<number, Role>();\n\n for (const row of result) {\n const codiceRuolo = Number(row.codiceRuolo);\n const descrizioneRuolo = typeof row.descrizioneRuolo === 'string' ? row.descrizioneRuolo.trim() : '';\n const codiceMenu = typeof row.codiceMenu === 'string' ? row.codiceMenu : undefined;\n const rawTipoAbilitazione = row.tipoAbilitazione;\n\n if (!ruoliMap.has(codiceRuolo)) {\n ruoliMap.set(codiceRuolo, {\n codiceRuolo,\n descrizioneRuolo,\n menu: []\n });\n }\n\n const abilitationValue = typeof rawTipoAbilitazione === 'number'\n ? rawTipoAbilitazione\n : Number.parseInt(`${rawTipoAbilitazione ?? ''}`, 10);\n\n if (!codiceMenu || Number.isNaN(abilitationValue) || abilitationValue <= TipoAbilitazione.NESSUNA) {\n continue;\n }\n\n ruoliMap.get(codiceRuolo)!.menu.push({\n codiceRuolo,\n codiceMenu,\n tipoAbilitazione: abilitationValue as TipoAbilitazione,\n });\n }\n\n return Array.from(ruoliMap.values());\n }\n\n\n /**\n * Sostituisce l'intera lista di ruoli dell'utente con codici univoci e verificati.\n * Non modifica i grant diretti: il calcolo effettivo li unisce in `getUserRolesAndGrants`.\n */\n public async assignRolesToUser(codiceUtente: number, roles: number[]): Promise<void> {\n\n const userExistsQuery = `SELECT COUNT(*) FROM UTENTI WHERE CODUTE = ?`;\n let result = await Orm.query(this.accessiOptions.databaseOptions, userExistsQuery, [codiceUtente]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`L'utente con codice ${codiceUtente} non esiste.`);\n }\n\n const normalizedRoles = Array.from(\n new Set(\n roles\n .map((role) => Number.parseInt(`${role ?? ''}`, 10))\n .filter((role) => Number.isInteger(role) && role > 0),\n ),\n );\n\n const queriesWithParams = [\n { query: `DELETE FROM UTENTI_RUOLI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...normalizedRoles.map((codiceRuolo) => ({\n query: `INSERT INTO UTENTI_RUOLI (CODUTE, CODRUO) VALUES (?, ?)`,\n params: [codiceUtente, codiceRuolo],\n })),\n ];\n\n if (normalizedRoles.length > 0) {\n // Un'unica query IN invece di N count: stessa validazione, meno round-trip.\n const placeholders = normalizedRoles.map(() => '?').join(', ');\n const existingRolesResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODRUO FROM RUOLI WHERE CODRUO IN (${placeholders})`,\n normalizedRoles,\n );\n const existingRoles = this.extractIntegerColumn(existingRolesResult, 'CODRUO');\n const missingRole = normalizedRoles.find((codiceRuolo) => !existingRoles.has(codiceRuolo));\n if (missingRole !== undefined) {\n throw new Error(`Il ruolo con codice ${missingRole} non esiste.`);\n }\n }\n\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, queriesWithParams);\n\n const assignedRolesResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM UTENTI_RUOLI WHERE CODUTE = ?`,\n [codiceUtente],\n );\n const assignedRolesCount = this.getCountFromResult(assignedRolesResult);\n if (assignedRolesCount !== normalizedRoles.length) {\n throw new Error(\n `Persistenza ruoli non coerente per utente ${codiceUtente}: attesi ${normalizedRoles.length}, trovati ${assignedRolesCount}.`,\n );\n }\n }\n\n\n /**\n * Sostituisce tutti i grant diretti dell'utente. I ruoli rimangono invariati e i loro grant sono aggiunti\n * solo nel risultato effettivo; per lo stesso menu vale il livello massimo tra diretto e ruoli.\n */\n public async assignPermissionsToUser(codiceUtente: number, permissions: Permission[]): Promise<void> {\n\n const userExistsQuery = `SELECT COUNT(*) FROM UTENTI WHERE CODUTE = ?`;\n let result = await Orm.query(this.accessiOptions.databaseOptions, userExistsQuery, [codiceUtente]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`L'utente con codice ${codiceUtente} non esiste.`);\n }\n\n const normalizedPermissions = Array.from(\n new Map(\n permissions\n .map((permission) => ({\n codiceMenu: `${permission?.codiceMenu ?? ''}`.trim(),\n tipoAbilitazione: Number.parseInt(`${permission?.tipoAbilitazione ?? ''}`, 10),\n }))\n .filter(\n (permission) =>\n permission.codiceMenu.length > 0 && Number.isInteger(permission.tipoAbilitazione),\n )\n .map((permission) => [permission.codiceMenu, permission] as const),\n ).values(),\n );\n\n const queriesWithParams = [\n { query: `DELETE FROM ABILITAZIONI WHERE CODUTE = ?`, params: [codiceUtente] },\n ...normalizedPermissions.map((permission) => ({\n query: `INSERT INTO ABILITAZIONI (CODUTE, CODMNU, TIPABI) VALUES (?, ?, ?)`,\n params: [codiceUtente, permission.codiceMenu, permission.tipoAbilitazione],\n })),\n ];\n\n if (normalizedPermissions.length > 0) {\n // Un'unica query IN invece di N count: stessa validazione, meno round-trip.\n const menuCodes = normalizedPermissions.map((permission) => permission.codiceMenu);\n const placeholders = menuCodes.map(() => '?').join(', ');\n const existingMenusResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODMNU FROM MENU WHERE CODMNU IN (${placeholders})`,\n menuCodes,\n );\n const existingMenus = new Set(\n existingMenusResult.map((row) => String((row as Record<string, unknown>).CODMNU ?? (row as Record<string, unknown>).codmnu ?? '')),\n );\n const missingMenu = menuCodes.find((codiceMenu) => !existingMenus.has(codiceMenu));\n if (missingMenu !== undefined) {\n throw new Error(`Il menu con codice ${missingMenu} non esiste.`);\n }\n }\n\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, queriesWithParams);\n\n const assignedPermissionsResult = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM ABILITAZIONI WHERE CODUTE = ?`,\n [codiceUtente],\n );\n const assignedPermissionsCount = this.getCountFromResult(assignedPermissionsResult);\n if (assignedPermissionsCount !== normalizedPermissions.length) {\n throw new Error(\n `Persistenza abilitazioni non coerente per utente ${codiceUtente}: attese ${normalizedPermissions.length}, trovate ${assignedPermissionsCount}.`,\n );\n }\n }\n\n\n /** Elimina fisicamente ruolo, associazioni menu e assegnazioni utente. Operazione amministrativa irreversibile. */\n public async deleteRole(codiceRuolo: number): Promise<void> {\n\n const existsQuery = `SELECT COUNT(*) FROM RUOLI WHERE CODRUO = ?`;\n const result = await Orm.query(this.accessiOptions.databaseOptions, existsQuery, [codiceRuolo]);\n\n if (this.getCountFromResult(result) === 0) {\n throw new Error(`Il ruolo con codice ${codiceRuolo} non esiste.`);\n }\n\n // Le tre delete sono atomiche: nessun ruolo orfano se una fallisce.\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM RUOLI_MNU WHERE CODRUO = ?`, params: [codiceRuolo] },\n { query: `DELETE FROM UTENTI_RUOLI WHERE CODRUO = ?`, params: [codiceRuolo] },\n { query: `DELETE FROM RUOLI WHERE CODRUO = ?`, params: [codiceRuolo] },\n ]);\n\n }\n\n\n /** Restituisce solo menu abilitati; per il catalogo amministrativo usare `getGroupsWithMenus(true)`. */\n public async getMenus(): Promise<MenuEntity[]> {\n const query = `\n SELECT \n M.CODMNU AS codiceMenu, \n M.DESMNU AS descrizioneMenu,\n M.CODGRP AS codiceGruppo,\n G.DESGRP AS descrizioneGruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM MENU M\n LEFT JOIN MENU_GRP G ON M.CODGRP = G.CODGRP\n WHERE M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1\n ORDER BY G.CODGRP, M.CODMNU\n `;\n\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n return result.map(RestUtilities.convertKeysToCamelCase) as unknown as MenuEntity[];\n }\n\n\n /**\n * Restituisce il catalogo ordinato per gruppo e menu. `includeDisabled` e riservato a console e amministrazione:\n * i flussi applicativi di autorizzazione devono usare il comportamento predefinito, che nasconde voci disabilitate.\n */\n public async getGroupsWithMenus(includeDisabled = false): Promise<GroupWithMenusEntity[]> {\n const filtersClause = includeDisabled\n ? ''\n : 'WHERE M.FLGENABLED = 1 AND (G.FLGENABLED IS NULL OR G.FLGENABLED = 1)';\n\n const query = `\n SELECT\n M.CODMNU AS codice_menu,\n M.DESMNU AS descrizione_menu,\n M.CODGRP AS codice_gruppo,\n G.DESGRP AS descrizione_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note,\n G.ORDINE AS ordine_gruppo,\n M.ORDINE as ordine_menu,\n M.FLGENABLED AS menu_enabled,\n G.FLGENABLED AS group_enabled\n FROM MENU M\n LEFT JOIN MENU_GRP G ON M.CODGRP = G.CODGRP\n ${filtersClause}\n ORDER BY G.CODGRP, M.CODMNU\n `;\n\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n\n const groupMap = new Map<\n string,\n GroupWithMenusEntity & { menus: (MenuEntity & { enabled?: boolean })[] }\n >();\n\n result.forEach((row: unknown) => {\n const converted = RestUtilities.convertKeysToCamelCase(row) as MenuEntity & {\n menuEnabled?: number | boolean;\n groupEnabled?: number | boolean;\n };\n\n const { menuEnabled, groupEnabled, ...menuBase } = converted;\n const normalizedGroupKey = menuBase.codiceGruppo ?? '__UNGROUPED__';\n const groupEnabledFlag =\n groupEnabled == null ? true : Number(groupEnabled) === 1 || groupEnabled === true;\n const menuEnabledFlag =\n menuEnabled === undefined ? true : Number(menuEnabled) === 1 || menuEnabled === true;\n\n if (!groupMap.has(normalizedGroupKey)) {\n groupMap.set(normalizedGroupKey, {\n codiceGruppo: menuBase.codiceGruppo ?? normalizedGroupKey,\n descrizioneGruppo: menuBase.descrizioneGruppo ?? '',\n ordineGruppo: menuBase.ordineGruppo,\n enabled: groupEnabledFlag,\n menus: [],\n });\n }\n\n if (menuBase.codiceMenu) {\n groupMap.get(normalizedGroupKey)!.menus.push({\n ...menuBase,\n enabled: menuEnabledFlag,\n });\n }\n });\n\n const groupsArray = Array.from(groupMap.values())\n .map(group => ({\n ...group,\n menus: (group.menus ?? []).sort(\n (a, b) =>\n (a.ordineMenu ?? Number.MAX_SAFE_INTEGER) - (b.ordineMenu ?? Number.MAX_SAFE_INTEGER),\n ),\n }))\n .sort(\n (a, b) =>\n (a.ordineGruppo ?? Number.MAX_SAFE_INTEGER) - (b.ordineGruppo ?? Number.MAX_SAFE_INTEGER),\n );\n\n return groupsArray;\n }\n\n // -----------------------------------------------------------------------\n // Cataloghi di configurazione: CRUD di menu, gruppi, tipi menu.\n // `table` e `column` sono sempre costanti interne, mai input utente: i\n // valori sono parametrizzati, quindi non esiste superficie di SQL injection.\n // -----------------------------------------------------------------------\n\n private async countWhere(table: string, column: string, value: unknown): Promise<number> {\n const result = await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT COUNT(*) FROM ${table} WHERE ${column} = ?`,\n [value],\n );\n return this.getCountFromResult(result);\n }\n\n private async assertMenuGroupExists(codiceGruppo: string): Promise<void> {\n if ((await this.countWhere('MENU_GRP', 'CODGRP', codiceGruppo)) === 0) {\n throw new BadRequestException(`Il gruppo menu ${codiceGruppo} non esiste.`);\n }\n }\n\n private async assertMenuTypeExists(codiceTipo: string): Promise<void> {\n if ((await this.countWhere('MENU_TIPI', 'CODTIP', codiceTipo)) === 0) {\n throw new BadRequestException(`Il tipo menu ${codiceTipo} non esiste.`);\n }\n }\n\n /** Catalogo completo dei tipi menu, usato dagli editor di configurazione. */\n public async getMenuTypes(): Promise<MenuTypeEntity[]> {\n const query = `SELECT CODTIP AS codice_tipo, DESTIP AS descrizione_tipo FROM MENU_TIPI ORDER BY CODTIP`;\n const result = await Orm.query(this.accessiOptions.databaseOptions, query, []);\n return result.map(RestUtilities.convertKeysToCamelCase) as unknown as MenuTypeEntity[];\n }\n\n public async createMenuType(input: CreateMenuTypeRequest): Promise<void> {\n if ((await this.countWhere('MENU_TIPI', 'CODTIP', input.codiceTipo)) > 0) {\n throw new BadRequestException(`Il tipo menu ${input.codiceTipo} esiste gia.`);\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU_TIPI (CODTIP, DESTIP) VALUES (?, ?)`,\n [input.codiceTipo, input.descrizioneTipo ?? null],\n );\n }\n\n public async updateMenuType(codiceTipo: string, input: UpdateMenuTypeRequest): Promise<void> {\n await this.assertMenuTypeExists(codiceTipo);\n if (input.descrizioneTipo === undefined) return;\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU_TIPI SET DESTIP = ? WHERE CODTIP = ?`,\n [input.descrizioneTipo, codiceTipo],\n );\n }\n\n /** Elimina un tipo menu solo se nessun menu lo utilizza (la FK lo impedirebbe comunque). */\n public async deleteMenuType(codiceTipo: string): Promise<void> {\n await this.assertMenuTypeExists(codiceTipo);\n const used = await this.countWhere('MENU', 'CODTIP', codiceTipo);\n if (used > 0) {\n throw new BadRequestException(\n `Impossibile eliminare il tipo ${codiceTipo}: ${used} menu lo utilizzano.`,\n );\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `DELETE FROM MENU_TIPI WHERE CODTIP = ?`,\n [codiceTipo],\n );\n }\n\n public async createMenuGroup(input: CreateMenuGroupRequest): Promise<void> {\n if ((await this.countWhere('MENU_GRP', 'CODGRP', input.codiceGruppo)) > 0) {\n throw new BadRequestException(`Il gruppo menu ${input.codiceGruppo} esiste gia.`);\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU_GRP (CODGRP, DESGRP, FLGENABLED, ORDINE) VALUES (?, ?, ?, ?)`,\n [\n input.codiceGruppo,\n input.descrizioneGruppo ?? null,\n input.enabled === false ? 0 : 1,\n input.ordineGruppo ?? 0,\n ],\n );\n }\n\n public async updateMenuGroup(codiceGruppo: string, input: UpdateMenuGroupRequest): Promise<void> {\n await this.assertMenuGroupExists(codiceGruppo);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.descrizioneGruppo !== undefined) {\n sets.push('DESGRP = ?');\n params.push(input.descrizioneGruppo);\n }\n if (input.ordineGruppo !== undefined) {\n sets.push('ORDINE = ?');\n params.push(input.ordineGruppo);\n }\n if (input.enabled !== undefined) {\n sets.push('FLGENABLED = ?');\n params.push(input.enabled ? 1 : 0);\n }\n if (sets.length === 0) return;\n\n params.push(codiceGruppo);\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU_GRP SET ${sets.join(', ')} WHERE CODGRP = ?`,\n params,\n );\n }\n\n /** Elimina un gruppo solo se non contiene menu, per non lasciare menu orfani. */\n public async deleteMenuGroup(codiceGruppo: string): Promise<void> {\n await this.assertMenuGroupExists(codiceGruppo);\n const used = await this.countWhere('MENU', 'CODGRP', codiceGruppo);\n if (used > 0) {\n throw new BadRequestException(\n `Impossibile eliminare il gruppo ${codiceGruppo}: contiene ${used} menu.`,\n );\n }\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `DELETE FROM MENU_GRP WHERE CODGRP = ?`,\n [codiceGruppo],\n );\n }\n\n public async createMenu(input: CreateMenuRequest): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', input.codiceMenu)) > 0) {\n throw new BadRequestException(`Il menu ${input.codiceMenu} esiste gia.`);\n }\n await this.assertMenuGroupExists(input.codiceGruppo);\n if (input.tipo) await this.assertMenuTypeExists(input.tipo);\n\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `INSERT INTO MENU (CODMNU, DESMNU, CODGRP, FLGENABLED, ICON, ORDINE, CODTIP, PAGINA, RIFMENU, NOTE)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n input.codiceMenu,\n input.descrizioneMenu ?? null,\n input.codiceGruppo,\n input.enabled === false ? 0 : 1,\n input.icona ?? null,\n input.ordineMenu ?? null,\n input.tipo ?? null,\n input.pagina ?? null,\n input.rifMenu ?? null,\n input.note ?? null,\n ],\n );\n }\n\n public async updateMenu(codiceMenu: string, input: UpdateMenuRequest): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', codiceMenu)) === 0) {\n throw new BadRequestException(`Il menu ${codiceMenu} non esiste.`);\n }\n if (input.codiceGruppo !== undefined) await this.assertMenuGroupExists(input.codiceGruppo);\n if (input.tipo) await this.assertMenuTypeExists(input.tipo);\n\n const sets: string[] = [];\n const params: unknown[] = [];\n if (input.descrizioneMenu !== undefined) {\n sets.push('DESMNU = ?');\n params.push(input.descrizioneMenu);\n }\n if (input.codiceGruppo !== undefined) {\n sets.push('CODGRP = ?');\n params.push(input.codiceGruppo);\n }\n if (input.tipo !== undefined) {\n sets.push('CODTIP = ?');\n params.push(input.tipo || null);\n }\n if (input.icona !== undefined) {\n sets.push('ICON = ?');\n params.push(input.icona);\n }\n if (input.pagina !== undefined) {\n sets.push('PAGINA = ?');\n params.push(input.pagina);\n }\n if (input.ordineMenu !== undefined) {\n sets.push('ORDINE = ?');\n params.push(input.ordineMenu);\n }\n if (input.enabled !== undefined) {\n sets.push('FLGENABLED = ?');\n params.push(input.enabled ? 1 : 0);\n }\n if (input.rifMenu !== undefined) {\n sets.push('RIFMENU = ?');\n params.push(input.rifMenu);\n }\n if (input.note !== undefined) {\n sets.push('NOTE = ?');\n params.push(input.note);\n }\n if (sets.length === 0) return;\n\n params.push(codiceMenu);\n await Orm.execute(\n this.accessiOptions.databaseOptions,\n `UPDATE MENU SET ${sets.join(', ')} WHERE CODMNU = ?`,\n params,\n );\n }\n\n /** Elimina menu, grant utente e associazioni ruolo in un'unica transazione. */\n public async deleteMenu(codiceMenu: string): Promise<void> {\n if ((await this.countWhere('MENU', 'CODMNU', codiceMenu)) === 0) {\n throw new BadRequestException(`Il menu ${codiceMenu} non esiste.`);\n }\n await Orm.executeMultiple(this.accessiOptions.databaseOptions, [\n { query: `DELETE FROM ABILITAZIONI WHERE CODMNU = ?`, params: [codiceMenu] },\n { query: `DELETE FROM RUOLI_MNU WHERE CODMNU = ?`, params: [codiceMenu] },\n { query: `DELETE FROM MENU WHERE CODMNU = ?`, params: [codiceMenu] },\n ]);\n }\n\n\n /**\n * Calcola l'autorizzazione effettiva: grant diretti e grant dei ruoli sono uniti per codice menu\n * prendendo il livello massimo. I superutenti ricevono tutti i menu attivi.\n */\n public async getUserRolesAndGrants(codiceUtente: number): Promise<{\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n grants: AbilitazioneMenu[]\n }> {\n const codiceUtenteQuery = \"SELECT FLGSUPER as flag_super FROM UTENTI_CONFIG WHERE CODUTE = ?\";\n const result = await Orm.query(this.accessiOptions.databaseOptions, codiceUtenteQuery, [codiceUtente]);\n if (!result || result.length == 0) throw new Error(\"Nessun utente trovato con il codice utente \" + codiceUtente);\n\n const isSuperAdmin = this.isSuperFlag(\n (result.map(RestUtilities.convertKeysToCamelCase)[0] as { flagSuper?: unknown } | undefined)?.flagSuper,\n );\n\n const abilitazioni = await this.getUserDirectPermissions(codiceUtente);\n const ruoli = await this.getUserRoles(codiceUtente);\n const grants = this.composeGrants(\n abilitazioni,\n ruoli,\n isSuperAdmin,\n await this.getAllActiveMenusAsGrants(),\n );\n\n return { abilitazioni, ruoli, grants };\n }\n\n private isSuperFlag(value: unknown): boolean {\n return value === true || value === 1 || value === '1';\n }\n\n /**\n * Versione batch di `getUserRolesAndGrants`: esegue un numero costante di query per N utenti\n * (invece di N set di query) e restituisce mappa per codice utente. Stessa composizione della\n * lettura singola.\n */\n public async getUsersRolesAndGrants(codiciUtente: number[]): Promise<Map<number, {\n abilitazioni: AbilitazioneMenu[],\n ruoli: Role[],\n grants: AbilitazioneMenu[]\n }>> {\n const result = new Map<number, { abilitazioni: AbilitazioneMenu[], ruoli: Role[], grants: AbilitazioneMenu[] }>();\n const ids = Array.from(new Set(codiciUtente.filter((id) => Number.isInteger(id) && id > 0)));\n if (ids.length === 0) {\n return result;\n }\n\n const placeholders = ids.map(() => '?').join(', ');\n\n const superRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT CODUTE AS codice_utente, FLGSUPER AS flag_super FROM UTENTI_CONFIG WHERE CODUTE IN (${placeholders})`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n const superUsers = new Set<number>();\n for (const row of superRows) {\n if (this.isSuperFlag(row.flagSuper)) {\n superUsers.add(Number(row.codiceUtente));\n }\n }\n\n const permissionRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT\n A.CODUTE AS codice_utente,\n A.CODMNU AS codice_menu,\n A.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n G.DESGRP AS descrizione_gruppo,\n G.CODGRP AS codice_gruppo,\n M.ICON AS icona,\n M.CODTIP AS tipo,\n M.PAGINA AS pagina,\n M.NOTE AS note\n FROM ABILITAZIONI A\n INNER JOIN MENU M ON A.CODMNU = M.CODMNU\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE A.CODUTE IN (${placeholders}) AND M.FLGENABLED = 1 AND COALESCE(G.FLGENABLED, 1) = 1`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const roleRows = (await Orm.query(\n this.accessiOptions.databaseOptions,\n `SELECT\n RU.CODUTE AS codice_utente,\n R.CODRUO AS codice_ruolo,\n R.DESRUO AS descrizione_ruolo,\n CASE WHEN COALESCE(G.FLGENABLED, 1) = 1 THEN M.CODMNU END AS codice_menu,\n RM.TIPABI AS tipo_abilitazione,\n M.DESMNU AS descrizione_menu,\n M.NOTE AS note\n FROM UTENTI_RUOLI RU\n INNER JOIN RUOLI R ON RU.CODRUO = R.CODRUO\n LEFT JOIN RUOLI_MNU RM ON R.CODRUO = RM.CODRUO\n LEFT JOIN MENU M ON RM.CODMNU = M.CODMNU AND M.FLGENABLED = 1\n LEFT JOIN MENU_GRP G ON G.CODGRP = M.CODGRP\n WHERE RU.CODUTE IN (${placeholders})`,\n ids,\n )).map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>;\n\n const allActiveMenus = await this.getAllActiveMenusAsGrants();\n\n const permissionsByUser = new Map<number, AbilitazioneMenu[]>();\n for (const row of permissionRows) {\n const { codiceUtente, ...grant } = row;\n const key = Number(codiceUtente);\n const bucket = permissionsByUser.get(key);\n if (bucket) {\n bucket.push(grant as unknown as AbilitazioneMenu);\n } else {\n permissionsByUser.set(key, [grant as unknown as AbilitazioneMenu]);\n }\n }\n\n const rolesByUser = new Map<number, Array<Record<string, unknown>>>();\n for (const row of roleRows) {\n const { codiceUtente, ...roleRow } = row;\n const key = Number(codiceUtente);\n const bucket = rolesByUser.get(key);\n if (bucket) {\n bucket.push(roleRow);\n } else {\n rolesByUser.set(key, [roleRow]);\n }\n }\n\n for (const id of ids) {\n const abilitazioni = permissionsByUser.get(id) ?? [];\n const ruoli = this.assembleRoles(rolesByUser.get(id) ?? []);\n const isSuperAdmin = superUsers.has(id);\n result.set(id, {\n abilitazioni,\n ruoli,\n grants: this.composeGrants(abilitazioni, ruoli, isSuperAdmin, allActiveMenus),\n });\n }\n\n return result;\n }\n\n\n\n}\n\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emilsoftware-utilities",
3
- "version": "2.0.0-dev.53",
3
+ "version": "2.0.0-dev.54",
4
4
  "description": "Utilities for EmilSoftware",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",