emilsoftware-utilities 2.0.0-dev.40 → 2.0.0-dev.42
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.
- package/dist/accessi-module/Console/accessiFetch.d.ts +9 -0
- package/dist/accessi-module/Console/accessiFetch.js +31 -3
- package/dist/accessi-module/Console/accessiFetch.js.map +1 -1
- package/dist/accessi-module/Console/console.css +8 -3
- package/dist/accessi-module/Console/console.js +380 -46
- package/dist/accessi-module/Console/console.js.map +1 -1
- package/dist/accessi-module/Console/generated/accessiApi.d.ts +21 -1
- package/dist/accessi-module/Console/generated/accessiApi.js +18 -2
- package/dist/accessi-module/Console/generated/accessiApi.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/confirmResetPasswordRequest.d.ts +1 -1
- package/dist/accessi-module/Console/generated/model/confirmResetPasswordRequest.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/index.d.ts +1 -0
- package/dist/accessi-module/Console/generated/model/index.js +1 -0
- package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
- package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.d.ts +14 -0
- package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.js +9 -0
- package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.js.map +1 -0
- package/dist/accessi-module/Console/help.js +115 -0
- package/dist/accessi-module/Console/help.js.map +1 -1
- package/dist/accessi-module/Console/wiki.js +9 -2
- package/dist/accessi-module/Console/wiki.js.map +1 -1
- package/dist/accessi-module/Controllers/UserController.d.ts +6 -1
- package/dist/accessi-module/Controllers/UserController.js +91 -3
- package/dist/accessi-module/Controllers/UserController.js.map +1 -1
- package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.js +1 -1
- package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.js.map +1 -1
- package/dist/accessi-module/Dtos/SetUserPasswordRequest.d.ts +4 -0
- package/dist/accessi-module/Dtos/SetUserPasswordRequest.js +32 -0
- package/dist/accessi-module/Dtos/SetUserPasswordRequest.js.map +1 -0
- package/dist/accessi-module/Services/AuthService/AuthService.d.ts +8 -2
- package/dist/accessi-module/Services/AuthService/AuthService.js +13 -9
- package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
- package/dist/accessi-module/Services/UserService/UserService.d.ts +5 -0
- package/dist/accessi-module/Services/UserService/UserService.js +71 -55
- package/dist/accessi-module/Services/UserService/UserService.js.map +1 -1
- package/dist/accessi-module/Views/reset-password.html +23 -7
- package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.d.ts +2 -1
- package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js +35 -4
- package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js.map +1 -1
- package/dist/accessi-module/security/accessControl.d.ts +9 -0
- package/dist/accessi-module/security/accessControl.js +14 -0
- package/dist/accessi-module/security/accessControl.js.map +1 -1
- package/dist/accessi-module/security/passwordPolicy.d.ts +21 -0
- package/dist/accessi-module/security/passwordPolicy.js +71 -0
- package/dist/accessi-module/security/passwordPolicy.js.map +1 -0
- package/dist/emilsoftware-utilities-2.0.0-dev.42.tgz +0 -0
- package/package.json +1 -1
- package/dist/emilsoftware-utilities-2.0.0-dev.40.tgz +0 -0
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// src/accessi-module/Console/accessiFetch.ts
|
|
2
2
|
var accessToken = null;
|
|
3
3
|
var loadingEventName = "accessi-console-network";
|
|
4
|
+
var ACCESSI_UNAUTHORIZED_EVENT = "accessi-console-unauthorized";
|
|
5
|
+
var AccessiRequestError = class extends Error {
|
|
6
|
+
constructor(message, status, code3, details) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.code = code3;
|
|
10
|
+
this.details = details;
|
|
11
|
+
this.name = "AccessiRequestError";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
4
14
|
function notifyNetworkActivity(active) {
|
|
5
15
|
if (typeof window !== "undefined") {
|
|
6
16
|
window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));
|
|
@@ -9,20 +19,31 @@ function notifyNetworkActivity(active) {
|
|
|
9
19
|
function setAccessiConsoleToken(token2) {
|
|
10
20
|
accessToken = token2;
|
|
11
21
|
}
|
|
22
|
+
function payloadString(payload, key) {
|
|
23
|
+
if (!payload || typeof payload !== "object") return void 0;
|
|
24
|
+
const value = payload[key];
|
|
25
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
26
|
+
}
|
|
12
27
|
async function accessiFetch(url, options) {
|
|
13
28
|
const headers = new Headers(options.headers);
|
|
14
29
|
headers.set("Accept", "application/json");
|
|
15
30
|
if (options.body !== void 0 && !headers.has("Content-Type")) {
|
|
16
31
|
headers.set("Content-Type", "application/json");
|
|
17
32
|
}
|
|
33
|
+
const authenticatedRequest = accessToken !== null;
|
|
18
34
|
if (accessToken) headers.set("Authorization", `Bearer ${accessToken}`);
|
|
19
35
|
notifyNetworkActivity(true);
|
|
20
36
|
try {
|
|
21
37
|
const response = await fetch(url, { ...options, headers });
|
|
22
38
|
const payload = await response.json().catch(() => void 0);
|
|
23
39
|
if (!response.ok) {
|
|
24
|
-
const message = payload
|
|
25
|
-
|
|
40
|
+
const message = payloadString(payload, "message") ?? payloadString(payload, "error") ?? `Errore HTTP ${response.status}`;
|
|
41
|
+
const rawDetails = payload && typeof payload === "object" ? payload.details : void 0;
|
|
42
|
+
const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : void 0;
|
|
43
|
+
if (response.status === 401 && authenticatedRequest && typeof window !== "undefined") {
|
|
44
|
+
window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));
|
|
45
|
+
}
|
|
46
|
+
throw new AccessiRequestError(message, response.status, payloadString(payload, "code") ?? payloadString(payload, "error"), details);
|
|
26
47
|
}
|
|
27
48
|
return {
|
|
28
49
|
data: payload,
|
|
@@ -396,6 +417,33 @@ var deleteUser = async (codiceUtente, options) => {
|
|
|
396
417
|
}
|
|
397
418
|
);
|
|
398
419
|
};
|
|
420
|
+
var getDeleteUserPermanentlyUrl = (codiceUtente) => {
|
|
421
|
+
return `/api/accessi/user/delete-user-permanent/${codiceUtente}`;
|
|
422
|
+
};
|
|
423
|
+
var deleteUserPermanently = async (codiceUtente, options) => {
|
|
424
|
+
return accessiFetch(
|
|
425
|
+
getDeleteUserPermanentlyUrl(codiceUtente),
|
|
426
|
+
{
|
|
427
|
+
...options,
|
|
428
|
+
method: "DELETE"
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
var getSetUserPasswordUrl = (codiceUtente) => {
|
|
433
|
+
return `/api/accessi/user/set-password/${codiceUtente}`;
|
|
434
|
+
};
|
|
435
|
+
var setUserPassword = async (codiceUtente, setUserPasswordRequest, options) => {
|
|
436
|
+
return accessiFetch(
|
|
437
|
+
getSetUserPasswordUrl(codiceUtente),
|
|
438
|
+
{
|
|
439
|
+
...options,
|
|
440
|
+
method: "POST",
|
|
441
|
+
body: JSON.stringify(
|
|
442
|
+
setUserPasswordRequest
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
};
|
|
399
447
|
var getSetStatoRegistrazioneUrl = () => {
|
|
400
448
|
return `/api/accessi/user/set-stato`;
|
|
401
449
|
};
|
|
@@ -959,7 +1007,8 @@ var WIKI_SECTIONS = [
|
|
|
959
1007
|
]),
|
|
960
1008
|
code("bash", `npm run db:update:accessi # riconcilia lo schema
|
|
961
1009
|
npm run db:check:accessi # verifica senza modificare`, "CLI schema"),
|
|
962
|
-
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.`)
|
|
1010
|
+
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.`),
|
|
1011
|
+
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.`)
|
|
963
1012
|
]
|
|
964
1013
|
},
|
|
965
1014
|
// -------------------------------------------------------------------------
|
|
@@ -1083,6 +1132,7 @@ POST /api/accessi/auth/two-factor/resend
|
|
|
1083
1132
|
POST /api/accessi/auth/confirm-reset-password/{token}
|
|
1084
1133
|
{ "newPassword": "NuovaSegreta123!" }`, "Reset in due passi"),
|
|
1085
1134
|
p(`L'invio non rivela se l'email esiste (anti-enumerazione). Il token di reset \xE8 monouso ed \xE8 firmato con una chiave dedicata.`),
|
|
1135
|
+
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.`),
|
|
1086
1136
|
note("info", `Pagina personalizzata`, `Imposta customResetPage per usare la tua pagina di reset invece di quella inclusa. confirmationEmailReturnUrl e confirmationEmailPrefix controllano il ritorno.`)
|
|
1087
1137
|
]
|
|
1088
1138
|
},
|
|
@@ -1179,12 +1229,16 @@ POST /api/accessi/auth/confirm-reset-password/{token}
|
|
|
1179
1229
|
["GET", "/api/accessi/user/get-users", "Elenco con paginazione (limit e offset, header X-Total-Count)."],
|
|
1180
1230
|
["POST", "/api/accessi/user/create-managed-user", "Crea un utente locale e invia il reset password."],
|
|
1181
1231
|
["PUT", "/api/accessi/user/update-user/:codiceUtente", "Aggiorna profilo e policy (campi privilegiati solo superutente)."],
|
|
1182
|
-
["DELETE", "/api/accessi/user/delete-user/:codiceUtente", "Soft delete: STAREG=DELETE."],
|
|
1232
|
+
["DELETE", "/api/accessi/user/delete-user/:codiceUtente", "Soft delete: STAREG=DELETE. Admin o superutente."],
|
|
1233
|
+
["DELETE", "/api/accessi/user/delete-user-permanent/:codiceUtente", "Eliminazione definitiva: rimuove utente e record collegati. Solo admin."],
|
|
1234
|
+
["POST", "/api/accessi/user/set-password/:codiceUtente", "Imposta direttamente la password: hash lato backend e scadenza secondo policy, nessuna email."],
|
|
1183
1235
|
["POST", "/api/accessi/user/set-stato", "Imposta lo stato di registrazione."],
|
|
1184
1236
|
["PATCH", "/api/accessi/user/set-gdpr/:codiceUtente", "Registra il consenso GDPR."],
|
|
1185
1237
|
["POST", "/api/accessi/user/register", "Registrazione pubblica, solo se publicRegistration.enabled."]
|
|
1186
1238
|
]
|
|
1187
1239
|
),
|
|
1240
|
+
note("warn", `Eliminazione: soft o definitiva`, `Il soft delete (STAREG=DELETE) \xE8 disponibile per admin e superutenti ed \xE8 reversibile. L'eliminazione definitiva rimuove utente e record collegati in transazione ed \xE8 riservata agli admin; il superutente senza flag Admin pu\xF2 solo flaggare l'utente come eliminato.`),
|
|
1241
|
+
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, scadenza password e filtri (tabella FILTRI). 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.`),
|
|
1188
1242
|
good("ts", [
|
|
1189
1243
|
"await fetch('/api/accessi/user/create-managed-user', {",
|
|
1190
1244
|
" method: 'POST',",
|
|
@@ -1392,6 +1446,7 @@ POST /api/accessi/auth/confirm-reset-password/{token}
|
|
|
1392
1446
|
]),
|
|
1393
1447
|
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.`),
|
|
1394
1448
|
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.`),
|
|
1449
|
+
note("info", `Gestione dei flag`, `Dalla scheda utente i flag sono mostrati in base al ruolo di chi \xE8 connesso: un admin pu\xF2 assegnare o togliere sia il flag Admin sia il flag Superutente; un superutente senza flag Admin pu\xF2 gestire solo il flag Superutente. I flag si impostano anche in creazione (utente locale o SSO). La regola \xE8 imposta anche dal backend: inviare un flag non consentito viene rifiutato.`),
|
|
1395
1450
|
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).`),
|
|
1396
1451
|
note("info", `Bootstrap admin`, `Con adminBootstrap.enabled il backend stampa a ogni avvio un token casuale (monouso, valido fino al riavvio). Nella schermata di login della console trovi "Crea utente admin": incolla il token e crea il primo admin senza email di conferma. La password \xE8 opzionale: se assente viene generata e mostrata una sola volta.`)
|
|
1397
1452
|
]
|
|
@@ -1442,7 +1497,7 @@ POST /api/accessi/auth/confirm-reset-password/{token}
|
|
|
1442
1497
|
[
|
|
1443
1498
|
["Auth", "POST auth/login, auth/get-user-by-token, auth/confirm-reset-password/:token, auth/two-factor/verify, auth/two-factor/resend"],
|
|
1444
1499
|
["Email", "POST email/send-reset-password-email, GET email/reset-password-page/:token"],
|
|
1445
|
-
["Utenti", "GET user/get-users, POST user/create-managed-user, PUT user/update-user/:codiceUtente, DELETE user/delete-user/:codiceUtente, POST user/set-stato, PATCH user/set-gdpr/:codiceUtente, POST user/register"],
|
|
1500
|
+
["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"],
|
|
1446
1501
|
["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"],
|
|
1447
1502
|
["Catalogo", "POST/PUT/DELETE permission/menus[/:codiceMenu], permission/menu-groups[/:codiceGruppo], permission/menu-types[/:codiceTipo]"],
|
|
1448
1503
|
["Configurator", "PATCH configurator/menus/:codiceMenu/enabled, PATCH configurator/groups/:codiceGruppo/enabled"],
|
|
@@ -1534,7 +1589,7 @@ function buildAiDigest() {
|
|
|
1534
1589
|
}
|
|
1535
1590
|
|
|
1536
1591
|
// src/accessi-module/Console/changelog.md
|
|
1537
|
-
var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\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";
|
|
1592
|
+
var changelog_default = "# Changelog Accessi\n\nNovit\xE0 e modifiche rilevanti della libreria `emilsoftware-utilities` (modulo Accessi).\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";
|
|
1538
1593
|
|
|
1539
1594
|
// src/accessi-module/Console/help.ts
|
|
1540
1595
|
var p2 = (text) => ({ kind: "p", text });
|
|
@@ -1692,6 +1747,112 @@ var HELP = {
|
|
|
1692
1747
|
p2(`E una stringa libera (massimo 50 caratteri): la libreria la conserva e la restituisce nel profilo; l'instradamento effettivo lo decide il frontend.`)
|
|
1693
1748
|
]
|
|
1694
1749
|
},
|
|
1750
|
+
"user-cellulare": {
|
|
1751
|
+
title: "Cellulare",
|
|
1752
|
+
blocks: [
|
|
1753
|
+
p2(`Numero di cellulare dell'utente (UTENTI_CONFIG.CELLULARE). \xC8 il recapito canonico per SMS o notifiche applicative; Accessi non lo invia da solo.`)
|
|
1754
|
+
]
|
|
1755
|
+
},
|
|
1756
|
+
"user-lingua": {
|
|
1757
|
+
title: "Codice lingua",
|
|
1758
|
+
blocks: [
|
|
1759
|
+
p2(`Lingua preferita dell'utente (UTENTI_CONFIG.CODLINGUA). Stringa breve come it, en, sl; viene restituita nel profilo e il frontend decide come usarla.`)
|
|
1760
|
+
]
|
|
1761
|
+
},
|
|
1762
|
+
"user-avatar": {
|
|
1763
|
+
title: "Avatar",
|
|
1764
|
+
blocks: [
|
|
1765
|
+
p2(`Riferimento all'avatar (UTENTI_CONFIG.AVATAR, massimo 30 caratteri): tipicamente un nome file o una chiave, non un base64.`)
|
|
1766
|
+
]
|
|
1767
|
+
},
|
|
1768
|
+
"user-scadenza-password": {
|
|
1769
|
+
title: "Scadenza password",
|
|
1770
|
+
blocks: [
|
|
1771
|
+
p2(`Data di scadenza della password (UTENTI.DATSCAPWD). Lascia vuoto per non forzare una scadenza; usa una data passata per richiedere il cambio al prossimo login, se la policy \xE8 attiva.`)
|
|
1772
|
+
]
|
|
1773
|
+
},
|
|
1774
|
+
"user-gdpr": {
|
|
1775
|
+
title: "GDPR accettato",
|
|
1776
|
+
blocks: [
|
|
1777
|
+
p2(`Imposta il flag di accettazione GDPR (UTENTI.FLGGDPR). Il consenso completo con tracciamento si registra dal flusso dedicato (endpoint set-gdpr).`)
|
|
1778
|
+
]
|
|
1779
|
+
},
|
|
1780
|
+
"user-json": {
|
|
1781
|
+
title: "Metadata JSON",
|
|
1782
|
+
blocks: [
|
|
1783
|
+
p2(`Campo libero JSON (UTENTI_CONFIG.JSON_METADATA) per dati applicativi del backend. Deve essere JSON valido se il backend lo interpreta.`)
|
|
1784
|
+
]
|
|
1785
|
+
},
|
|
1786
|
+
"user-set-password": {
|
|
1787
|
+
title: "Imposta password",
|
|
1788
|
+
blocks: [
|
|
1789
|
+
p2(`Imposta direttamente una nuova password locale: viene hashata dal backend e la scadenza segue la policy. Nessuna email viene inviata.`),
|
|
1790
|
+
p2(`Requisiti di sicurezza:`),
|
|
1791
|
+
list2([
|
|
1792
|
+
`Almeno 8 caratteri (massimo 100).`,
|
|
1793
|
+
`Almeno una lettera maiuscola e una minuscola.`,
|
|
1794
|
+
`Almeno una cifra e un carattere speciale (es. ! ? @ # $ %).`,
|
|
1795
|
+
`Nessuno spazio e non deve essere una password comune.`
|
|
1796
|
+
]),
|
|
1797
|
+
note2("danger", "Password non conforme", `Se non rispetta i requisiti l API risponde 400 con codice ACCESSI_WEAK_PASSWORD e l elenco dei requisiti mancanti; la password non viene salvata.`),
|
|
1798
|
+
note2("warn", "Sostituisce la password attuale", `L'utente dovr\xE0 usare subito la nuova password. Per fargliela scegliere, usa "Invia reset password".`)
|
|
1799
|
+
]
|
|
1800
|
+
},
|
|
1801
|
+
"user-filter-numrep": {
|
|
1802
|
+
title: "Numero reparto (NUMREP)",
|
|
1803
|
+
blocks: [p2(`Reparto associato all'utente nella tabella FILTRI. Vuoto rimuove il valore.`)]
|
|
1804
|
+
},
|
|
1805
|
+
"user-filter-idxpers": {
|
|
1806
|
+
title: "Indice personale (IDXPERS)",
|
|
1807
|
+
blocks: [p2(`Indice del personale nella tabella FILTRI. Vuoto rimuove il valore.`)]
|
|
1808
|
+
},
|
|
1809
|
+
"user-filter-codclisuper": {
|
|
1810
|
+
title: "Cliente padre (CODCLISUPER)",
|
|
1811
|
+
blocks: [p2(`Codice del cliente padre. Vuoto rimuove il valore.`)]
|
|
1812
|
+
},
|
|
1813
|
+
"user-filter-codage": {
|
|
1814
|
+
title: "Agente (CODAGE)",
|
|
1815
|
+
blocks: [p2(`Codice dell'agente associato. Vuoto rimuove il valore.`)]
|
|
1816
|
+
},
|
|
1817
|
+
"user-filter-codclicol": {
|
|
1818
|
+
title: "Cliente collegato (CODCLICOL)",
|
|
1819
|
+
blocks: [p2(`Codice del cliente collegato. Vuoto rimuove il valore.`)]
|
|
1820
|
+
},
|
|
1821
|
+
"user-filter-codclienti": {
|
|
1822
|
+
title: "Clienti (CODCLIENTI)",
|
|
1823
|
+
blocks: [p2(`Elenco di codici cliente separati da virgola (testo libero). Vuoto rimuove il valore.`)]
|
|
1824
|
+
},
|
|
1825
|
+
"user-filter-tipfil": {
|
|
1826
|
+
title: "Tipo filtro (TIPFIL)",
|
|
1827
|
+
blocks: [p2(`Tipo di filtro applicato. Vuoto rimuove il valore.`)]
|
|
1828
|
+
},
|
|
1829
|
+
"user-filter-idxpos": {
|
|
1830
|
+
title: "Postazione (IDXPOS)",
|
|
1831
|
+
blocks: [p2(`Indice della postazione nella tabella ANTENNE_POS. Vuoto rimuove il valore.`)]
|
|
1832
|
+
},
|
|
1833
|
+
"user-filter-coddip": {
|
|
1834
|
+
title: "Dipendente (CODDIP)",
|
|
1835
|
+
blocks: [p2(`Codice del dipendente collegato. Vuoto rimuove il valore.`)]
|
|
1836
|
+
},
|
|
1837
|
+
"user-filter-codvet": {
|
|
1838
|
+
title: "Vettore (CODVET)",
|
|
1839
|
+
blocks: [p2(`Codice del vettore collegato. Vuoto rimuove il valore.`)]
|
|
1840
|
+
},
|
|
1841
|
+
"user-super": {
|
|
1842
|
+
title: "Superutente (FLGSUPER)",
|
|
1843
|
+
blocks: [
|
|
1844
|
+
p2(`Assegna il flag superutente. Un superutente riceve TUTTE le abilitazioni al livello massimo (30) dopo il login e in console vede solo la sezione Utenti, dove gestisce utenti, ruoli e grant.`),
|
|
1845
|
+
note2("warn", "Non \xE8 un ruolo", `Il flag non sostituisce i ruoli o i grant: li amplia assegnando ogni menu attivo al livello massimo. Usalo solo per gli amministratori di Accessi.`),
|
|
1846
|
+
note2("info", "Chi lo modifica", `Un admin pu\xF2 assegnare o togliere sia Admin sia Superutente; un superutente senza Admin pu\xF2 gestire solo il flag Superutente. La verifica \xE8 ripetuta dal backend.`)
|
|
1847
|
+
]
|
|
1848
|
+
},
|
|
1849
|
+
"user-admin": {
|
|
1850
|
+
title: "Admin (FLGADMIN)",
|
|
1851
|
+
blocks: [
|
|
1852
|
+
p2(`Assegna il flag admin. Un admin accede a tutta la console (catalogo, token di servizio, SSO e utenti) ma NON conferisce abilitazioni: i menu restano quelli assegnati via ABILITAZIONI e RUOLI_MNU.`),
|
|
1853
|
+
note2("info", "Pu\xF2 gestire i flag", `Un admin pu\xF2 assegnare o togliere sia il flag Admin sia il flag Superutente. Un superutente senza Admin non pu\xF2 modificare il flag Admin.`)
|
|
1854
|
+
]
|
|
1855
|
+
},
|
|
1695
1856
|
"user-state": {
|
|
1696
1857
|
title: "Stato di registrazione",
|
|
1697
1858
|
blocks: [
|
|
@@ -1870,7 +2031,16 @@ var HELP = {
|
|
|
1870
2031
|
title: "Imposta stato eliminato",
|
|
1871
2032
|
blocks: [
|
|
1872
2033
|
p2(`Esegue un soft delete: imposta lo stato di registrazione a "Eliminato" (50). L'utente non pu\xF2 pi\xF9 accedere, ma i dati restano nel database.`),
|
|
1873
|
-
note2("info", "Reversibile", `Non \xE8 una cancellazione fisica: puoi ripristinare l'utente cambiando lo stato di registrazione.`)
|
|
2034
|
+
note2("info", "Reversibile", `Non \xE8 una cancellazione fisica: puoi ripristinare l'utente cambiando lo stato di registrazione.`),
|
|
2035
|
+
note2("info", "Chi pu\xF2 farlo", `Sia admin sia superutente possono eseguire il soft delete.`)
|
|
2036
|
+
]
|
|
2037
|
+
},
|
|
2038
|
+
"user-delete-permanent": {
|
|
2039
|
+
title: "Elimina definitivamente",
|
|
2040
|
+
blocks: [
|
|
2041
|
+
p2(`Cancella l'utente e tutti i record collegati (configurazione, password, ruoli, abilitazioni, filtri, identit\xE0 SSO, challenge 2FA e GDPR). Operazione irreversibile.`),
|
|
2042
|
+
note2("danger", "Non reversibile", `I dati vengono rimossi fisicamente dal database; non \xE8 possibile ripristinare l'utente.`),
|
|
2043
|
+
note2("info", "Solo admin", `Il pulsante \xE8 mostrato e accettato solo per il flag Admin. Un superutente senza Admin pu\xF2 solo flaggare l'utente come eliminato.`)
|
|
1874
2044
|
]
|
|
1875
2045
|
},
|
|
1876
2046
|
// ----------------------------------------------------- selezioni di menu
|
|
@@ -1950,8 +2120,24 @@ function result(response) {
|
|
|
1950
2120
|
if (body.Result === void 0) throw new Error(body.message ?? "Risposta Accessi priva di dati.");
|
|
1951
2121
|
return body.Result;
|
|
1952
2122
|
}
|
|
2123
|
+
var noticeTimer;
|
|
1953
2124
|
function showNotice(message, isError = false) {
|
|
1954
|
-
|
|
2125
|
+
const container = byId("notice");
|
|
2126
|
+
if (noticeTimer !== void 0) {
|
|
2127
|
+
window.clearTimeout(noticeTimer);
|
|
2128
|
+
noticeTimer = void 0;
|
|
2129
|
+
}
|
|
2130
|
+
if (!message) {
|
|
2131
|
+
container.innerHTML = "";
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
const kind = isError ? "error" : "success";
|
|
2135
|
+
const icon = isError ? "!" : "\u2713";
|
|
2136
|
+
container.innerHTML = `<div class="notice notice-${kind}${isError ? " error" : ""}" role="alert"><span class="notice-icon" aria-hidden="true">${icon}</span><span class="notice-text">${escapeHtml(message)}</span><button type="button" class="notice-close" aria-label="Chiudi il messaggio">×</button></div>`;
|
|
2137
|
+
container.querySelector(".notice-close")?.addEventListener("click", () => showNotice(""));
|
|
2138
|
+
if (!isError) {
|
|
2139
|
+
noticeTimer = window.setTimeout(() => showNotice(""), 6e3);
|
|
2140
|
+
}
|
|
1955
2141
|
}
|
|
1956
2142
|
function menuMetadata(menu) {
|
|
1957
2143
|
const details = [
|
|
@@ -2074,7 +2260,13 @@ function finishBoot(view) {
|
|
|
2074
2260
|
byId("console").hidden = view !== "console";
|
|
2075
2261
|
}
|
|
2076
2262
|
function errorMessage(error) {
|
|
2077
|
-
|
|
2263
|
+
if (!(error instanceof Error)) return "Operazione non riuscita.";
|
|
2264
|
+
const extra = error;
|
|
2265
|
+
const code3 = typeof extra.code === "string" ? extra.code : void 0;
|
|
2266
|
+
const details = Array.isArray(extra.details) ? extra.details.map((item) => String(item)) : [];
|
|
2267
|
+
const detailSuffix = details.length ? ` (${details.join("; ")})` : "";
|
|
2268
|
+
const message = code3 && !error.message.startsWith(`${code3}:`) ? `${code3}: ${error.message}` : error.message;
|
|
2269
|
+
return `${message}${detailSuffix}`;
|
|
2078
2270
|
}
|
|
2079
2271
|
function handleAction(action) {
|
|
2080
2272
|
void action().catch((error) => showNotice(errorMessage(error), true));
|
|
@@ -2391,15 +2583,130 @@ function authenticationPolicyFields(user) {
|
|
|
2391
2583
|
<p class="form-help">Accesso senza password (richiede il codice email).</p>
|
|
2392
2584
|
</fieldset>`;
|
|
2393
2585
|
}
|
|
2586
|
+
function canManageSuperFlag() {
|
|
2587
|
+
return Boolean(currentUser?.flagSuper || currentUser?.flagAdmin);
|
|
2588
|
+
}
|
|
2589
|
+
function canManageAdminFlag() {
|
|
2590
|
+
return Boolean(currentUser?.flagAdmin);
|
|
2591
|
+
}
|
|
2592
|
+
function privilegeFields(user) {
|
|
2593
|
+
const manageSuper = canManageSuperFlag();
|
|
2594
|
+
const manageAdmin = canManageAdminFlag();
|
|
2595
|
+
if (!manageSuper && !manageAdmin) return "";
|
|
2596
|
+
return `<fieldset><legend>Privilegi Accessi (opzionale)</legend>
|
|
2597
|
+
${manageAdmin ? `<label class="check"><input name="flagAdmin" type="checkbox" ${user.flagAdmin ? "checked" : ""}> Admin ${helpDot("user-admin")}</label><p class="form-help">Accede a tutta la console (catalogo, token, SSO, utenti) e puo gestire i flag. Non modifica le abilitazioni.</p>` : ""}
|
|
2598
|
+
${manageSuper ? `<label class="check"><input name="flagSuper" type="checkbox" ${user.flagSuper ? "checked" : ""}> Superutente ${helpDot("user-super")}</label><p class="form-help">Riceve tutte le abilitazioni al livello massimo e gestisce utenti, ruoli e grant.</p>` : ""}
|
|
2599
|
+
</fieldset>`;
|
|
2600
|
+
}
|
|
2601
|
+
function privilegePayload(form) {
|
|
2602
|
+
const fields = new FormData(form);
|
|
2603
|
+
const payload = {};
|
|
2604
|
+
if (canManageSuperFlag()) payload.flagSuper = fields.has("flagSuper");
|
|
2605
|
+
if (canManageAdminFlag()) payload.flagAdmin = fields.has("flagAdmin");
|
|
2606
|
+
return payload;
|
|
2607
|
+
}
|
|
2608
|
+
function optionalText(values, key) {
|
|
2609
|
+
if (!(key in values)) return void 0;
|
|
2610
|
+
const raw = (values[key] ?? "").trim();
|
|
2611
|
+
return raw === "" ? null : raw;
|
|
2612
|
+
}
|
|
2613
|
+
function optionalNumber(values, key) {
|
|
2614
|
+
if (!(key in values)) return void 0;
|
|
2615
|
+
const raw = (values[key] ?? "").trim();
|
|
2616
|
+
if (raw === "") return null;
|
|
2617
|
+
const parsed = Number(raw);
|
|
2618
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
2619
|
+
}
|
|
2620
|
+
function passwordPolicyViolations(password) {
|
|
2621
|
+
const violations = [];
|
|
2622
|
+
if (password.length < 8) violations.push("almeno 8 caratteri");
|
|
2623
|
+
if (password.length > 100) violations.push("al massimo 100 caratteri");
|
|
2624
|
+
if (/\s/.test(password)) violations.push("nessuno spazio");
|
|
2625
|
+
if (!/[A-Z]/.test(password)) violations.push("una lettera maiuscola");
|
|
2626
|
+
if (!/[a-z]/.test(password)) violations.push("una lettera minuscola");
|
|
2627
|
+
if (!/\d/.test(password)) violations.push("una cifra");
|
|
2628
|
+
if (!/[^A-Za-z0-9]/.test(password)) violations.push("un carattere speciale");
|
|
2629
|
+
return violations;
|
|
2630
|
+
}
|
|
2631
|
+
function dateInputValue(value) {
|
|
2632
|
+
const text = typeof value === "string" ? value : "";
|
|
2633
|
+
const match = /^(\d{4}-\d{2}-\d{2})/.exec(text);
|
|
2634
|
+
return match?.[1] ?? "";
|
|
2635
|
+
}
|
|
2636
|
+
function extendedProfileFields(user) {
|
|
2637
|
+
return `<fieldset><legend>Anagrafica estesa</legend>
|
|
2638
|
+
<label>Cellulare ${helpDot("user-cellulare")}<input name="cellulare" maxlength="30" value="${escapeHtml(user.cellulare ?? "")}"></label>
|
|
2639
|
+
<label>Codice lingua ${helpDot("user-lingua")}<input name="codiceLingua" maxlength="2" value="${escapeHtml(user.codiceLingua ?? "")}"></label>
|
|
2640
|
+
<label>Avatar ${helpDot("user-avatar")}<input name="avatar" maxlength="30" value="${escapeHtml(user.avatar ?? "")}"></label>
|
|
2641
|
+
<label>Scadenza password ${helpDot("user-scadenza-password")}<input name="dataScadenzaPassword" type="date" value="${dateInputValue(user.dataScadenzaPassword)}"></label>
|
|
2642
|
+
<label class="check"><input name="flagGdpr" type="checkbox" ${user.flagGdpr ? "checked" : ""}> GDPR accettato ${helpDot("user-gdpr")}</label>
|
|
2643
|
+
<label>Metadata JSON ${helpDot("user-json")}<textarea name="jsonMetadata" rows="3">${escapeHtml(user.jsonMetadata ?? "")}</textarea></label>
|
|
2644
|
+
</fieldset>`;
|
|
2645
|
+
}
|
|
2646
|
+
function filterFields(user) {
|
|
2647
|
+
return `<fieldset><legend>Filtri utente (FILTRI)</legend>
|
|
2648
|
+
<label>Numero reparto ${helpDot("user-filter-numrep")}<input name="numRep" type="number" value="${escapeHtml(user.numRep ?? "")}"></label>
|
|
2649
|
+
<label>Indice personale ${helpDot("user-filter-idxpers")}<input name="idxPers" type="number" value="${escapeHtml(user.idxPers ?? "")}"></label>
|
|
2650
|
+
<label>Cliente padre ${helpDot("user-filter-codclisuper")}<input name="codCliSuper" type="number" value="${escapeHtml(user.codCliSuper ?? "")}"></label>
|
|
2651
|
+
<label>Agente ${helpDot("user-filter-codage")}<input name="codAge" type="number" value="${escapeHtml(user.codAge ?? "")}"></label>
|
|
2652
|
+
<label>Cliente collegato ${helpDot("user-filter-codclicol")}<input name="codCliCol" type="number" value="${escapeHtml(user.codCliCol ?? "")}"></label>
|
|
2653
|
+
<label>Clienti (separati da virgola) ${helpDot("user-filter-codclienti")}<input name="codClienti" value="${escapeHtml(user.codClienti ?? "")}"></label>
|
|
2654
|
+
<label>Tipo filtro ${helpDot("user-filter-tipfil")}<input name="tipFil" type="number" value="${escapeHtml(user.tipFil ?? "")}"></label>
|
|
2655
|
+
<label>Postazione ${helpDot("user-filter-idxpos")}<input name="idxPos" type="number" value="${escapeHtml(user.idxPos ?? "")}"></label>
|
|
2656
|
+
<label>Dipendente ${helpDot("user-filter-coddip")}<input name="codDip" type="number" value="${escapeHtml(user.codDip ?? "")}"></label>
|
|
2657
|
+
<label>Vettore ${helpDot("user-filter-codvet")}<input name="codVet" type="number" value="${escapeHtml(user.codVet ?? "")}"></label>
|
|
2658
|
+
</fieldset>`;
|
|
2659
|
+
}
|
|
2660
|
+
function profilePayload(form, codiceUtente) {
|
|
2661
|
+
const values = formValues(form);
|
|
2662
|
+
const fields = new FormData(form);
|
|
2663
|
+
const payload = {
|
|
2664
|
+
codiceUtente,
|
|
2665
|
+
email: values.email ?? "",
|
|
2666
|
+
nome: optionalText(values, "nome"),
|
|
2667
|
+
cognome: optionalText(values, "cognome"),
|
|
2668
|
+
cellulare: optionalText(values, "cellulare"),
|
|
2669
|
+
codiceLingua: optionalText(values, "codiceLingua"),
|
|
2670
|
+
avatar: optionalText(values, "avatar"),
|
|
2671
|
+
paginaDefault: optionalText(values, "paginaDefault"),
|
|
2672
|
+
jsonMetadata: optionalText(values, "jsonMetadata"),
|
|
2673
|
+
dataScadenzaPassword: optionalText(values, "dataScadenzaPassword"),
|
|
2674
|
+
flagGdpr: fields.has("flagGdpr"),
|
|
2675
|
+
flagDueFattori: fields.has("flagDueFattori"),
|
|
2676
|
+
passwordlessLoginEnabled: fields.has("passwordlessLoginEnabled"),
|
|
2677
|
+
numRep: optionalNumber(values, "numRep"),
|
|
2678
|
+
idxPers: optionalNumber(values, "idxPers"),
|
|
2679
|
+
codCliSuper: optionalNumber(values, "codCliSuper"),
|
|
2680
|
+
codAge: optionalNumber(values, "codAge"),
|
|
2681
|
+
codCliCol: optionalNumber(values, "codCliCol"),
|
|
2682
|
+
codClienti: optionalText(values, "codClienti"),
|
|
2683
|
+
tipFil: optionalNumber(values, "tipFil"),
|
|
2684
|
+
idxPos: optionalNumber(values, "idxPos"),
|
|
2685
|
+
codDip: optionalNumber(values, "codDip"),
|
|
2686
|
+
codVet: optionalNumber(values, "codVet"),
|
|
2687
|
+
...privilegePayload(form)
|
|
2688
|
+
};
|
|
2689
|
+
if (federatedAuthenticationAvailable) {
|
|
2690
|
+
payload.passwordLoginEnabled = fields.has("passwordLoginEnabled");
|
|
2691
|
+
}
|
|
2692
|
+
return payload;
|
|
2693
|
+
}
|
|
2694
|
+
function passwordFields() {
|
|
2695
|
+
return `<fieldset><legend>Password</legend>
|
|
2696
|
+
<label>Nuova password ${helpDot("user-set-password")}<input name="newPassword" type="password" autocomplete="new-password" minlength="8" maxlength="100"><span class="form-help">Almeno 8 caratteri con maiuscola, minuscola, cifra e carattere speciale; nessuno spazio. Viene salvata come hash e la scadenza segue la policy; nessuna email inviata.</span></label>
|
|
2697
|
+
<button type="button" id="set-password" class="secondary">Imposta password</button>
|
|
2698
|
+
</fieldset>`;
|
|
2699
|
+
}
|
|
2394
2700
|
function showLocalUserForm() {
|
|
2395
2701
|
render(`<button id="back">Indietro</button><h2>Nuovo utente locale</h2><p class="muted">Viene inviata l'e-mail per impostare la password.</p>
|
|
2396
|
-
<form id="local-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Identificativo di accesso; ricever\xC3\xA0 l'email per la password.</span></label><label>Nome ${helpDot("user-name")}<input name="nome"></label><label>Cognome ${helpDot("user-name")}<input name="cognome"></label
|
|
2702
|
+
<form id="local-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Identificativo di accesso; ricever\xC3\xA0 l'email per la password.</span></label><label>Nome ${helpDot("user-name")}<input name="nome"></label><label>Cognome ${helpDot("user-name")}<input name="cognome"></label>${privilegeFields({})}<button>Crea utente</button></form>`);
|
|
2397
2703
|
byId("back").onclick = () => show("users");
|
|
2398
2704
|
const localUserForm = document.getElementById("local-user");
|
|
2399
2705
|
if (localUserForm) localUserForm.onsubmit = (event) => {
|
|
2400
2706
|
event.preventDefault();
|
|
2401
2707
|
handleAction(async () => {
|
|
2402
|
-
|
|
2708
|
+
const form = eventForm(event);
|
|
2709
|
+
await createManagedUser({ ...formValues(form), ...privilegePayload(form) });
|
|
2403
2710
|
showNotice("Utente creato; inviata la procedura di impostazione password.");
|
|
2404
2711
|
await show("users");
|
|
2405
2712
|
});
|
|
@@ -2411,7 +2718,7 @@ async function showSsoUserForm() {
|
|
|
2411
2718
|
throw new Error("Prima di creare un utente SSO, censisci e abilita almeno un provider SSO.");
|
|
2412
2719
|
}
|
|
2413
2720
|
render(`<button id="back">Indietro</button><h2>Nuovo utente SSO</h2><p class="muted">Salva solo collegamenti gi\xC3\xA0 verificati dal backend.</p>
|
|
2414
|
-
<form id="sso-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Email di contatto e identificativo Accessi dell'utente.</span></label><label>Nome ${helpDot("user-name")}<input name="nome"></label><label>Cognome ${helpDot("user-name")}<input name="cognome"></label>${federatedIdentityFields(providers)}<label class="check"><input name="passwordLoginEnabled" type="checkbox"> Abilita anche la password locale ${helpDot("user-password-login")}</label><span class="form-help">Se disattivato, l'utente accede solo via SSO.</span
|
|
2721
|
+
<form id="sso-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Email di contatto e identificativo Accessi dell'utente.</span></label><label>Nome ${helpDot("user-name")}<input name="nome"></label><label>Cognome ${helpDot("user-name")}<input name="cognome"></label>${federatedIdentityFields(providers)}<label class="check"><input name="passwordLoginEnabled" type="checkbox"> Abilita anche la password locale ${helpDot("user-password-login")}</label><span class="form-help">Se disattivato, l'utente accede solo via SSO.</span>${privilegeFields({})}<button>Crea utente SSO</button></form>`);
|
|
2415
2722
|
byId("back").onclick = () => show("ssoUsers");
|
|
2416
2723
|
const ssoUserForm = document.getElementById("sso-user");
|
|
2417
2724
|
if (ssoUserForm) ssoUserForm.onsubmit = (event) => {
|
|
@@ -2424,7 +2731,7 @@ async function showSsoUserForm() {
|
|
|
2424
2731
|
subject: values.subject ?? "",
|
|
2425
2732
|
note: values.note || void 0,
|
|
2426
2733
|
passwordLoginEnabled: new FormData(form).has("passwordLoginEnabled"),
|
|
2427
|
-
user: { email: values.email ?? "", nome: values.nome || void 0, cognome: values.cognome || void 0 }
|
|
2734
|
+
user: { email: values.email ?? "", nome: values.nome || void 0, cognome: values.cognome || void 0, ...privilegePayload(form) }
|
|
2428
2735
|
};
|
|
2429
2736
|
await createFederatedUser(request);
|
|
2430
2737
|
showNotice("Utente SSO creato e collegamento registrato.");
|
|
@@ -2526,9 +2833,9 @@ async function showUser(rawCode) {
|
|
|
2526
2833
|
const linked = federatedAuthenticationAvailable ? result(await getFederatedIdentities(codiceUtente)) : [];
|
|
2527
2834
|
const identitySection = federatedAuthenticationAvailable ? federatedIdentitySection(linked, providerList) : "";
|
|
2528
2835
|
const defaultUserTab = federatedAuthenticationAvailable ? "sso" : "profile";
|
|
2529
|
-
render(`<div class="user-action-bar"><button id="back" type="button" class="secondary">Indietro</button><label class="inline-field">Stato registrazione ${helpDot("user-state")}<select id="user-state">${registrationStateOptions(user.statoRegistrazione)}</select></label><button id="save-state" type="button" class="secondary">Aggiorna stato</button><button id="disable-user" type="button" class="danger-button">Imposta stato eliminato</button>${helpDot("user-delete")}</div><div class="page-header"><div><p class="eyebrow">Utente ${codiceUtente}</p><h2>${escapeHtml(user.email)}</h2></div><span class="muted">Gestione profilo, ruoli e autorizzazioni</span></div>${userDetailTabs(federatedAuthenticationAvailable)}<div class="user-editor">
|
|
2836
|
+
render(`<div class="user-action-bar"><button id="back" type="button" class="secondary">Indietro</button><label class="inline-field">Stato registrazione ${helpDot("user-state")}<select id="user-state">${registrationStateOptions(user.statoRegistrazione)}</select></label><button id="save-state" type="button" class="secondary">Aggiorna stato</button><button id="disable-user" type="button" class="danger-button">Imposta stato eliminato</button>${helpDot("user-delete")}${currentUser?.flagAdmin ? `<button id="purge-user" type="button" class="danger-button">Elimina definitivamente</button>${helpDot("user-delete-permanent")}` : ""}</div><div class="page-header"><div><p class="eyebrow">Utente ${codiceUtente}</p><h2>${escapeHtml(user.email)}</h2></div><span class="muted">Gestione profilo, ruoli e autorizzazioni</span></div>${userDetailTabs(federatedAuthenticationAvailable)}<div class="user-editor">
|
|
2530
2837
|
${identitySection}
|
|
2531
|
-
<form id="profile" data-user-panel="profile" role="tabpanel" aria-labelledby="user-tab-profile"><h3>Profilo e accesso</h3><label>Nome ${helpDot("user-name")}<input name="nome" value="${escapeHtml(user.nome)}"></label><label>Cognome ${helpDot("user-name")}<input name="cognome" value="${escapeHtml(user.cognome)}"></label><label>Email ${helpDot("user-email")}<input name="email" type="email" value="${escapeHtml(user.email)}" required><span class="form-help">Identificativo di accesso e recapito.</span></label><label>Pagina di default ${helpDot("user-default-page")}<input name="paginaDefault" maxlength="50" value="${escapeHtml(user.paginaDefault)}"><span class="form-help">Pagina mostrata dopo il login (percorso o codice pagina).</span></label>${federatedAuthenticationAvailable ? `<label class="check"><input name="passwordLoginEnabled" type="checkbox" ${user.passwordLoginEnabled !== false ? "checked" : ""}> Login con password ${helpDot("user-password-login")}</label><span class="form-help">Se disabilitato, il login con email e password restituisce un errore esplicito; restano valide le identit\xC3\xA0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}<button>Salva</button></form>
|
|
2838
|
+
<form id="profile" data-user-panel="profile" role="tabpanel" aria-labelledby="user-tab-profile"><h3>Profilo e accesso</h3><label>Nome ${helpDot("user-name")}<input name="nome" value="${escapeHtml(user.nome)}"></label><label>Cognome ${helpDot("user-name")}<input name="cognome" value="${escapeHtml(user.cognome)}"></label><label>Email ${helpDot("user-email")}<input name="email" type="email" value="${escapeHtml(user.email)}" required><span class="form-help">Identificativo di accesso e recapito.</span></label><label>Pagina di default ${helpDot("user-default-page")}<input name="paginaDefault" maxlength="50" value="${escapeHtml(user.paginaDefault)}"><span class="form-help">Pagina mostrata dopo il login (percorso o codice pagina).</span></label>${federatedAuthenticationAvailable ? `<label class="check"><input name="passwordLoginEnabled" type="checkbox" ${user.passwordLoginEnabled !== false ? "checked" : ""}> Login con password ${helpDot("user-password-login")}</label><span class="form-help">Se disabilitato, il login con email e password restituisce un errore esplicito; restano valide le identit\xC3\xA0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}${extendedProfileFields(user)}${privilegeFields(user)}${filterFields(user)}${passwordFields()}<button>Salva</button></form>
|
|
2532
2839
|
${roleAssignmentEditor(allRoles, userGrants.ruoli, menuGroups)}
|
|
2533
2840
|
${directGrantEditor(menuGroups, userGrants.abilitazioni)}
|
|
2534
2841
|
</div>`);
|
|
@@ -2558,25 +2865,16 @@ async function showUser(rawCode) {
|
|
|
2558
2865
|
profileForm.onsubmit = (event) => {
|
|
2559
2866
|
event.preventDefault();
|
|
2560
2867
|
handleAction(async () => {
|
|
2561
|
-
const
|
|
2562
|
-
const fields = new FormData(
|
|
2563
|
-
await updateUtente(codiceUtente,
|
|
2564
|
-
|
|
2565
|
-
email: values.email ?? "",
|
|
2566
|
-
nome: values.nome || void 0,
|
|
2567
|
-
cognome: values.cognome || void 0,
|
|
2568
|
-
paginaDefault: values.paginaDefault || void 0,
|
|
2569
|
-
flagDueFattori: fields.has("flagDueFattori"),
|
|
2570
|
-
passwordlessLoginEnabled: fields.has("passwordlessLoginEnabled"),
|
|
2571
|
-
...federatedAuthenticationAvailable ? { passwordLoginEnabled: fields.has("passwordLoginEnabled") } : {}
|
|
2572
|
-
});
|
|
2573
|
-
if (codiceUtente === currentUser?.codiceUtente && (fields.has("flagDueFattori") !== Boolean(user.flagDueFattori) || fields.has("passwordlessLoginEnabled") !== Boolean(user.passwordlessLoginEnabled))) {
|
|
2868
|
+
const form = eventForm(event);
|
|
2869
|
+
const fields = new FormData(form);
|
|
2870
|
+
await updateUtente(codiceUtente, profilePayload(form, codiceUtente));
|
|
2871
|
+
if (codiceUtente === currentUser?.codiceUtente && (fields.has("flagDueFattori") !== Boolean(user.flagDueFattori) || fields.has("passwordlessLoginEnabled") !== Boolean(user.passwordlessLoginEnabled) || canManageSuperFlag() && fields.has("flagSuper") !== Boolean(user.flagSuper) || canManageAdminFlag() && fields.has("flagAdmin") !== Boolean(user.flagAdmin))) {
|
|
2574
2872
|
token = null;
|
|
2575
2873
|
sessionStorage.removeItem("accessi-console-token");
|
|
2576
2874
|
setAccessiConsoleToken(null);
|
|
2577
2875
|
resetLogin();
|
|
2578
2876
|
await bootstrap();
|
|
2579
|
-
byId("login-error").textContent = "
|
|
2877
|
+
byId("login-error").textContent = "Privilegi o policy aggiornati. Accedi di nuovo con la nuova configurazione.";
|
|
2580
2878
|
return;
|
|
2581
2879
|
}
|
|
2582
2880
|
await showUser(String(codiceUtente));
|
|
@@ -2585,6 +2883,16 @@ async function showUser(rawCode) {
|
|
|
2585
2883
|
});
|
|
2586
2884
|
};
|
|
2587
2885
|
}
|
|
2886
|
+
const setPasswordButton = document.getElementById("set-password");
|
|
2887
|
+
if (setPasswordButton) setPasswordButton.onclick = () => handleAction(async () => {
|
|
2888
|
+
const newPassword = (document.querySelector('#profile input[name="newPassword"]')?.value ?? "").trim();
|
|
2889
|
+
const violations = passwordPolicyViolations(newPassword);
|
|
2890
|
+
if (violations.length > 0) throw new Error(`La password non rispetta i requisiti: richiede ${violations.join(", ")}.`);
|
|
2891
|
+
if (!window.confirm(`Impostare una nuova password per ${user.email}?`)) return;
|
|
2892
|
+
await setUserPassword(codiceUtente, { newPassword });
|
|
2893
|
+
showNotice("Password aggiornata.");
|
|
2894
|
+
await showUser(String(codiceUtente));
|
|
2895
|
+
});
|
|
2588
2896
|
const rolesForm = document.getElementById("roles");
|
|
2589
2897
|
if (rolesForm) rolesForm.onsubmit = (event) => {
|
|
2590
2898
|
event.preventDefault();
|
|
@@ -2612,12 +2920,14 @@ async function showUser(rawCode) {
|
|
|
2612
2920
|
handleAction(async () => {
|
|
2613
2921
|
const values = formValues(eventForm(event));
|
|
2614
2922
|
await linkFederatedIdentity(codiceUtente, { provider: values.provider ?? "", subject: values.subject ?? "", note: values.note || void 0 });
|
|
2923
|
+
showNotice("Collegamento SSO registrato.");
|
|
2615
2924
|
await showUser(String(codiceUtente));
|
|
2616
2925
|
});
|
|
2617
2926
|
};
|
|
2618
2927
|
document.querySelectorAll("[data-identity-toggle]").forEach((button) => {
|
|
2619
2928
|
button.onclick = () => handleAction(async () => {
|
|
2620
2929
|
await updateFederatedIdentity(button.dataset.identityToggle ?? "", { active: button.dataset.active === "true" });
|
|
2930
|
+
showNotice("Collegamento SSO aggiornato.");
|
|
2621
2931
|
await showUser(String(codiceUtente));
|
|
2622
2932
|
});
|
|
2623
2933
|
});
|
|
@@ -2642,6 +2952,13 @@ async function showUser(rawCode) {
|
|
|
2642
2952
|
showNotice("Utente impostato come eliminato.");
|
|
2643
2953
|
await show("users");
|
|
2644
2954
|
});
|
|
2955
|
+
const purgeUser = document.getElementById("purge-user");
|
|
2956
|
+
if (purgeUser) purgeUser.onclick = () => handleAction(async () => {
|
|
2957
|
+
if (!window.confirm(`Eliminare DEFINITIVAMENTE l'utente ${user.email} e tutti i dati collegati? Operazione irreversibile.`)) return;
|
|
2958
|
+
await deleteUserPermanently(codiceUtente);
|
|
2959
|
+
showNotice("Utente eliminato definitivamente.");
|
|
2960
|
+
await show("users");
|
|
2961
|
+
});
|
|
2645
2962
|
}
|
|
2646
2963
|
async function showRoles() {
|
|
2647
2964
|
const [rolesResponse, groupsResponse] = await Promise.all([getRoles(), getGroupsWithMenus({ includeDisabled: true })]);
|
|
@@ -2659,14 +2976,17 @@ async function showRoles() {
|
|
|
2659
2976
|
};
|
|
2660
2977
|
});
|
|
2661
2978
|
const roleFormElement = document.getElementById("role-form");
|
|
2662
|
-
if (roleFormElement) roleFormElement.onsubmit =
|
|
2979
|
+
if (roleFormElement) roleFormElement.onsubmit = (event) => {
|
|
2663
2980
|
event.preventDefault();
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2981
|
+
handleAction(async () => {
|
|
2982
|
+
const form = eventForm(event);
|
|
2983
|
+
const data = new FormData(form);
|
|
2984
|
+
const request = { descrizioneRuolo: formValues(form).descrizione ?? "", menu: Array.from(data.getAll("menu"), (codiceMenu) => ({ codiceMenu: String(codiceMenu), tipoAbilitazione: Number(data.get(`menu-level:${codiceMenu}`) ?? 10) })) };
|
|
2985
|
+
if (role?.codiceRuolo) await updateRole(role.codiceRuolo, request);
|
|
2986
|
+
else await createRole(request);
|
|
2987
|
+
showNotice(role?.codiceRuolo ? "Ruolo aggiornato." : "Ruolo creato.");
|
|
2988
|
+
await show("roles");
|
|
2989
|
+
});
|
|
2670
2990
|
};
|
|
2671
2991
|
};
|
|
2672
2992
|
byId("new-role").onclick = () => roleForm();
|
|
@@ -2879,19 +3199,22 @@ async function showFilters() {
|
|
|
2879
3199
|
const users = await loadUsers();
|
|
2880
3200
|
render(`<h2>Filtri utente</h2><p class="form-help">Filtri applicativi salvati per singolo utente.</p><form id="filters"><label>Utente ${helpDot("filters-user")}<select name="codUte">${users.map(({ utente }) => `<option value="${utente.codiceUtente}">${escapeHtml(utente.email)}</option>`).join("")}</select><span class="form-help">Salvati per l'utente selezionato.</span></label><label>Filtro JSON ${helpDot("filters-json")}<textarea name="json" rows="12">{}</textarea><span class="form-help">Usa Carica per partire dalla struttura esistente.</span></label><button name="action" value="load">Carica</button><button name="action" value="save">Salva</button></form>`);
|
|
2881
3201
|
const filtersForm = document.getElementById("filters");
|
|
2882
|
-
if (filtersForm) filtersForm.onsubmit =
|
|
3202
|
+
if (filtersForm) filtersForm.onsubmit = (event) => {
|
|
2883
3203
|
event.preventDefault();
|
|
2884
3204
|
const form = eventForm(event);
|
|
2885
3205
|
const action = event.submitter?.value;
|
|
2886
3206
|
const code3 = Number(formValues(form).codUte);
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3207
|
+
handleAction(async () => {
|
|
3208
|
+
if (action === "load") {
|
|
3209
|
+
const filters = result(await getFiltriUtente({ codUte: code3 }));
|
|
3210
|
+
form.elements.namedItem("json").value = JSON.stringify(filters[0] ?? { codUte: code3 }, null, 2);
|
|
3211
|
+
showNotice("Filtri caricati.");
|
|
3212
|
+
} else {
|
|
3213
|
+
const parsed = JSON.parse(formValues(form).json ?? "{}");
|
|
3214
|
+
await saveFiltriUtente({ ...parsed, codUte: code3 });
|
|
3215
|
+
showNotice("Filtri salvati.");
|
|
3216
|
+
}
|
|
3217
|
+
});
|
|
2895
3218
|
};
|
|
2896
3219
|
}
|
|
2897
3220
|
async function showFilterTypes() {
|
|
@@ -2972,6 +3295,7 @@ async function showServiceTokens(includeRevoked = false) {
|
|
|
2972
3295
|
document.querySelectorAll("[data-rotate]").forEach((button) => {
|
|
2973
3296
|
button.onclick = () => handleAction(async () => {
|
|
2974
3297
|
const issued = result(await rotateServiceToken(button.dataset.rotate ?? ""));
|
|
3298
|
+
showNotice("Token di servizio ruotato.");
|
|
2975
3299
|
showIssuedServiceToken(issued);
|
|
2976
3300
|
});
|
|
2977
3301
|
});
|
|
@@ -2991,6 +3315,7 @@ function showServiceTokenForm() {
|
|
|
2991
3315
|
scopes: scopes.length ? scopes : void 0,
|
|
2992
3316
|
ttlDays: Number.isFinite(ttlDays) ? ttlDays : void 0
|
|
2993
3317
|
}));
|
|
3318
|
+
showNotice("Token di servizio creato.");
|
|
2994
3319
|
showIssuedServiceToken(issued);
|
|
2995
3320
|
});
|
|
2996
3321
|
};
|
|
@@ -3219,7 +3544,6 @@ async function showSql() {
|
|
|
3219
3544
|
}
|
|
3220
3545
|
async function show(view) {
|
|
3221
3546
|
try {
|
|
3222
|
-
showNotice("");
|
|
3223
3547
|
const views = {
|
|
3224
3548
|
users: showUsers,
|
|
3225
3549
|
roles: showRoles,
|
|
@@ -3310,6 +3634,16 @@ byId("nav").onclick = (event) => {
|
|
|
3310
3634
|
window.addEventListener("popstate", () => {
|
|
3311
3635
|
if (token && currentUser) handleAction(() => show(viewFromLocation()));
|
|
3312
3636
|
});
|
|
3637
|
+
window.addEventListener(ACCESSI_UNAUTHORIZED_EVENT, () => {
|
|
3638
|
+
if (!token) return;
|
|
3639
|
+
token = null;
|
|
3640
|
+
currentUser = null;
|
|
3641
|
+
setAccessiConsoleToken(null);
|
|
3642
|
+
sessionStorage.removeItem("accessi-console-token");
|
|
3643
|
+
finishBoot("login");
|
|
3644
|
+
resetLogin();
|
|
3645
|
+
byId("login-error").textContent = "Sessione scaduta o non piu valida. Accedi di nuovo.";
|
|
3646
|
+
});
|
|
3313
3647
|
window.addEventListener("accessi-console-network", (event) => {
|
|
3314
3648
|
updateLoadingState(event.detail.active);
|
|
3315
3649
|
});
|