emilsoftware-utilities 2.0.0-dev.8 → 2.0.0-dev.80

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.
Files changed (914) hide show
  1. package/README.MD +323 -38
  2. package/dist/ContractGenerator.d.ts +4 -3
  3. package/dist/ContractGenerator.js +15 -5
  4. package/dist/ContractGenerator.js.map +1 -1
  5. package/dist/DailyFileTransport.d.ts +21 -0
  6. package/dist/DailyFileTransport.js +74 -0
  7. package/dist/DailyFileTransport.js.map +1 -0
  8. package/dist/DatabaseUpdater.js +32 -29
  9. package/dist/DatabaseUpdater.js.map +1 -1
  10. package/dist/EmilsoftwareModule.d.ts +15 -1
  11. package/dist/EmilsoftwareModule.js +1 -1
  12. package/dist/EmilsoftwareModule.js.map +1 -1
  13. package/dist/ExecutionTimeLogger.d.ts +2 -2
  14. package/dist/ExecutionTimeLogger.js +5 -4
  15. package/dist/ExecutionTimeLogger.js.map +1 -1
  16. package/dist/Logger.d.ts +22 -11
  17. package/dist/Logger.js +132 -119
  18. package/dist/Logger.js.map +1 -1
  19. package/dist/Orm.d.ts +32 -6
  20. package/dist/Orm.js +148 -38
  21. package/dist/Orm.js.map +1 -1
  22. package/dist/RoutesLoggerMiddleware.js.map +1 -1
  23. package/dist/Utilities.d.ts +18 -8
  24. package/dist/Utilities.js +143 -37
  25. package/dist/Utilities.js.map +1 -1
  26. package/dist/accessi/client/admin/index.d.ts +18 -0
  27. package/dist/accessi/client/admin/index.js +58 -0
  28. package/dist/accessi/client/admin/index.js.map +1 -0
  29. package/dist/accessi/client/createAccessiClient.d.ts +21 -0
  30. package/dist/accessi/client/createAccessiClient.js +31 -0
  31. package/dist/accessi/client/createAccessiClient.js.map +1 -0
  32. package/dist/accessi/client/generated/admin.d.ts +621 -0
  33. package/dist/accessi/client/generated/admin.js +591 -0
  34. package/dist/accessi/client/generated/admin.js.map +1 -0
  35. package/dist/accessi/client/generated/public.d.ts +121 -0
  36. package/dist/accessi/client/generated/public.js +119 -0
  37. package/dist/accessi/client/generated/public.js.map +1 -0
  38. package/dist/accessi/client/generated/service.d.ts +53 -0
  39. package/dist/accessi/client/generated/service.js +63 -0
  40. package/dist/accessi/client/generated/service.js.map +1 -0
  41. package/dist/accessi/client/index.d.ts +44 -0
  42. package/dist/accessi/client/index.js +99 -0
  43. package/dist/accessi/client/index.js.map +1 -0
  44. package/dist/accessi/client/public/index.d.ts +18 -0
  45. package/dist/accessi/client/public/index.js +58 -0
  46. package/dist/accessi/client/public/index.js.map +1 -0
  47. package/dist/accessi/client/service/index.d.ts +17 -0
  48. package/dist/accessi/client/service/index.js +57 -0
  49. package/dist/accessi/client/service/index.js.map +1 -0
  50. package/dist/accessi/console/index.d.ts +15 -0
  51. package/dist/accessi/console/index.js +25 -0
  52. package/dist/accessi/console/index.js.map +1 -0
  53. package/dist/accessi/contracts/dtos.d.ts +8 -0
  54. package/dist/accessi/contracts/dtos.js +25 -0
  55. package/dist/accessi/contracts/dtos.js.map +1 -0
  56. package/dist/accessi/contracts/index.d.ts +25 -0
  57. package/dist/accessi/contracts/index.js +28 -0
  58. package/dist/accessi/contracts/index.js.map +1 -0
  59. package/dist/accessi/openapi/index.d.ts +2 -0
  60. package/dist/accessi/openapi/index.js +19 -0
  61. package/dist/accessi/openapi/index.js.map +1 -0
  62. package/dist/accessi/resource-server/core.d.ts +6 -0
  63. package/dist/accessi/resource-server/core.js +23 -0
  64. package/dist/accessi/resource-server/core.js.map +1 -0
  65. package/dist/accessi/resource-server/errors.d.ts +10 -0
  66. package/dist/accessi/resource-server/errors.js +19 -0
  67. package/dist/accessi/resource-server/errors.js.map +1 -0
  68. package/dist/accessi/resource-server/express.d.ts +17 -0
  69. package/dist/accessi/resource-server/express.js +87 -0
  70. package/dist/accessi/resource-server/express.js.map +1 -0
  71. package/dist/accessi/resource-server/index.d.ts +3 -0
  72. package/dist/accessi/resource-server/index.js +20 -0
  73. package/dist/accessi/resource-server/index.js.map +1 -0
  74. package/dist/accessi/resource-server/introspection.d.ts +16 -0
  75. package/dist/accessi/resource-server/introspection.js +115 -0
  76. package/dist/accessi/resource-server/introspection.js.map +1 -0
  77. package/dist/accessi/resource-server/keys.d.ts +11 -0
  78. package/dist/accessi/resource-server/keys.js +22 -0
  79. package/dist/accessi/resource-server/keys.js.map +1 -0
  80. package/dist/accessi/resource-server/nest.d.ts +9 -0
  81. package/dist/accessi/resource-server/nest.js +75 -0
  82. package/dist/accessi/resource-server/nest.js.map +1 -0
  83. package/dist/accessi/resource-server/runtime.d.ts +27 -0
  84. package/dist/accessi/resource-server/runtime.js +179 -0
  85. package/dist/accessi/resource-server/runtime.js.map +1 -0
  86. package/dist/accessi/resource-server/token.d.ts +23 -0
  87. package/dist/accessi/resource-server/token.js +276 -0
  88. package/dist/accessi/resource-server/token.js.map +1 -0
  89. package/dist/accessi/resource-server/types.d.ts +114 -0
  90. package/dist/accessi/resource-server/types.js +3 -0
  91. package/dist/accessi/resource-server/types.js.map +1 -0
  92. package/dist/accessi/server/index.d.ts +12 -0
  93. package/dist/accessi/server/index.js +32 -0
  94. package/dist/accessi/server/index.js.map +1 -0
  95. package/dist/accessi/server/standalone.d.ts +39 -0
  96. package/dist/accessi/server/standalone.js +191 -0
  97. package/dist/accessi/server/standalone.js.map +1 -0
  98. package/dist/accessi/server/tenants.d.ts +97 -0
  99. package/dist/accessi/server/tenants.js +275 -0
  100. package/dist/accessi/server/tenants.js.map +1 -0
  101. package/dist/accessi/ui/index.d.ts +13 -0
  102. package/dist/accessi/ui/index.js +19 -0
  103. package/dist/accessi/ui/index.js.map +1 -0
  104. package/dist/accessi-module/AccessiModule.d.ts +225 -0
  105. package/dist/accessi-module/AccessiModule.js +49 -5
  106. package/dist/accessi-module/AccessiModule.js.map +1 -1
  107. package/dist/accessi-module/Console/accessiFetch.d.ts +59 -0
  108. package/dist/accessi-module/Console/accessiFetch.js +178 -0
  109. package/dist/accessi-module/Console/accessiFetch.js.map +1 -0
  110. package/dist/accessi-module/Console/console.css +774 -0
  111. package/dist/accessi-module/Console/console.d.ts +1 -0
  112. package/dist/accessi-module/Console/console.js +4173 -0
  113. package/dist/accessi-module/Console/console.js.map +1 -0
  114. package/dist/accessi-module/Console/generated/emilsoftware-utilities.d.ts +781 -0
  115. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js +746 -0
  116. package/dist/accessi-module/Console/generated/emilsoftware-utilities.js.map +1 -0
  117. package/dist/accessi-module/Console/generated/model/abilitazioneMenu.d.ts +39 -0
  118. package/dist/accessi-module/Console/generated/model/abilitazioneMenu.js +3 -0
  119. package/dist/accessi-module/Console/generated/model/abilitazioneMenu.js.map +1 -0
  120. package/dist/accessi-module/Console/generated/model/abilitazioneMenuTipoAbilitazione.d.ts +16 -0
  121. package/dist/accessi-module/Console/generated/model/abilitazioneMenuTipoAbilitazione.js +17 -0
  122. package/dist/accessi-module/Console/generated/model/abilitazioneMenuTipoAbilitazione.js.map +1 -0
  123. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.d.ts +16 -0
  124. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js +3 -0
  125. package/dist/accessi-module/Console/generated/model/accessiIntrospectionResultDto.js.map +1 -0
  126. package/dist/accessi-module/Console/generated/model/actionResponse.d.ts +12 -0
  127. package/dist/accessi-module/Console/generated/model/actionResponse.js +9 -0
  128. package/dist/accessi-module/Console/generated/model/actionResponse.js.map +1 -0
  129. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResponse.d.ts +14 -0
  130. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResponse.js +3 -0
  131. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResponse.js.map +1 -0
  132. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResult.d.ts +10 -0
  133. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResult.js +9 -0
  134. package/dist/accessi-module/Console/generated/model/adminBootstrapStatusResult.js.map +1 -0
  135. package/dist/accessi-module/Console/generated/model/assignPermissionsToUserRequest.d.ts +11 -0
  136. package/dist/accessi-module/Console/generated/model/assignPermissionsToUserRequest.js +3 -0
  137. package/dist/accessi-module/Console/generated/model/assignPermissionsToUserRequest.js.map +1 -0
  138. package/dist/accessi-module/Console/generated/model/assignRolesToUserRequest.d.ts +10 -0
  139. package/dist/accessi-module/Console/generated/model/assignRolesToUserRequest.js +9 -0
  140. package/dist/accessi-module/Console/generated/model/assignRolesToUserRequest.js.map +1 -0
  141. package/dist/accessi-module/Console/generated/model/authenticatedTokenPayloadDto.d.ts +10 -0
  142. package/dist/accessi-module/Console/generated/model/authenticatedTokenPayloadDto.js +3 -0
  143. package/dist/accessi-module/Console/generated/model/authenticatedTokenPayloadDto.js.map +1 -0
  144. package/dist/accessi-module/Console/generated/model/authenticatedUserPayloadDto.d.ts +19 -0
  145. package/dist/accessi-module/Console/generated/model/authenticatedUserPayloadDto.js +9 -0
  146. package/dist/accessi-module/Console/generated/model/authenticatedUserPayloadDto.js.map +1 -0
  147. package/dist/accessi-module/Console/generated/model/bootstrapAdminRequest.d.ts +17 -0
  148. package/dist/accessi-module/Console/generated/model/bootstrapAdminRequest.js +9 -0
  149. package/dist/accessi-module/Console/generated/model/bootstrapAdminRequest.js.map +1 -0
  150. package/dist/accessi-module/Console/generated/model/bootstrapAdminResponse.d.ts +14 -0
  151. package/dist/accessi-module/Console/generated/model/bootstrapAdminResponse.js +3 -0
  152. package/dist/accessi-module/Console/generated/model/bootstrapAdminResponse.js.map +1 -0
  153. package/dist/accessi-module/Console/generated/model/bootstrapAdminResultDto.d.ts +13 -0
  154. package/dist/accessi-module/Console/generated/model/bootstrapAdminResultDto.js +9 -0
  155. package/dist/accessi-module/Console/generated/model/bootstrapAdminResultDto.js.map +1 -0
  156. package/dist/accessi-module/Console/generated/model/confirmResetPasswordRequest.d.ts +14 -0
  157. package/dist/accessi-module/Console/generated/model/confirmResetPasswordRequest.js +9 -0
  158. package/dist/accessi-module/Console/generated/model/confirmResetPasswordRequest.js.map +1 -0
  159. package/dist/accessi-module/Console/generated/model/consoleEntryExchangeRequest.d.ts +10 -0
  160. package/dist/accessi-module/Console/generated/model/consoleEntryExchangeRequest.js +9 -0
  161. package/dist/accessi-module/Console/generated/model/consoleEntryExchangeRequest.js.map +1 -0
  162. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResponse.d.ts +14 -0
  163. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResponse.js +3 -0
  164. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResponse.js.map +1 -0
  165. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResult.d.ts +10 -0
  166. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResult.js +3 -0
  167. package/dist/accessi-module/Console/generated/model/consoleEntrySessionResult.js.map +1 -0
  168. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResponse.d.ts +14 -0
  169. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResponse.js +3 -0
  170. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResponse.js.map +1 -0
  171. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResult.d.ts +12 -0
  172. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResult.js +9 -0
  173. package/dist/accessi-module/Console/generated/model/consoleEntryTicketResult.js.map +1 -0
  174. package/dist/accessi-module/Console/generated/model/consoleEntryTokenResult.d.ts +12 -0
  175. package/dist/accessi-module/Console/generated/model/consoleEntryTokenResult.js +9 -0
  176. package/dist/accessi-module/Console/generated/model/consoleEntryTokenResult.js.map +1 -0
  177. package/dist/accessi-module/Console/generated/model/createFederatedIdentityRequest.d.ts +13 -0
  178. package/dist/accessi-module/Console/generated/model/createFederatedIdentityRequest.js +9 -0
  179. package/dist/accessi-module/Console/generated/model/createFederatedIdentityRequest.js.map +1 -0
  180. package/dist/accessi-module/Console/generated/model/createFederatedProviderRequest.d.ts +13 -0
  181. package/dist/accessi-module/Console/generated/model/createFederatedProviderRequest.js +9 -0
  182. package/dist/accessi-module/Console/generated/model/createFederatedProviderRequest.js.map +1 -0
  183. package/dist/accessi-module/Console/generated/model/createFederatedUserRequest.d.ts +18 -0
  184. package/dist/accessi-module/Console/generated/model/createFederatedUserRequest.js +3 -0
  185. package/dist/accessi-module/Console/generated/model/createFederatedUserRequest.js.map +1 -0
  186. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.d.ts +23 -0
  187. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.js +3 -0
  188. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.js.map +1 -0
  189. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.d.ts +14 -0
  190. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.js +15 -0
  191. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.js.map +1 -0
  192. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.d.ts +22 -0
  193. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.js +9 -0
  194. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.js.map +1 -0
  195. package/dist/accessi-module/Console/generated/model/createMenuRequest.d.ts +52 -0
  196. package/dist/accessi-module/Console/generated/model/createMenuRequest.js +9 -0
  197. package/dist/accessi-module/Console/generated/model/createMenuRequest.js.map +1 -0
  198. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.d.ts +18 -0
  199. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.js +9 -0
  200. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.js.map +1 -0
  201. package/dist/accessi-module/Console/generated/model/createScopeRequest.d.ts +18 -0
  202. package/dist/accessi-module/Console/generated/model/createScopeRequest.js +9 -0
  203. package/dist/accessi-module/Console/generated/model/createScopeRequest.js.map +1 -0
  204. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.d.ts +16 -0
  205. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.js +9 -0
  206. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.js.map +1 -0
  207. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.d.ts +14 -0
  208. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.js +3 -0
  209. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.js.map +1 -0
  210. package/dist/accessi-module/Console/generated/model/errorResponse.d.ts +18 -0
  211. package/dist/accessi-module/Console/generated/model/errorResponse.js +9 -0
  212. package/dist/accessi-module/Console/generated/model/errorResponse.js.map +1 -0
  213. package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.d.ts +12 -0
  214. package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.js +9 -0
  215. package/dist/accessi-module/Console/generated/model/exchangeFederatedIdentityRequest.js.map +1 -0
  216. package/dist/accessi-module/Console/generated/model/exportAuditOrder.d.ts +11 -0
  217. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js +15 -0
  218. package/dist/accessi-module/Console/generated/model/exportAuditOrder.js.map +1 -0
  219. package/dist/accessi-module/Console/generated/model/exportAuditParams.d.ts +37 -0
  220. package/dist/accessi-module/Console/generated/model/exportAuditParams.js +3 -0
  221. package/dist/accessi-module/Console/generated/model/exportAuditParams.js.map +1 -0
  222. package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.d.ts +15 -0
  223. package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.js +3 -0
  224. package/dist/accessi-module/Console/generated/model/federatedAuthenticationResultDto.js.map +1 -0
  225. package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.d.ts +14 -0
  226. package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.js +3 -0
  227. package/dist/accessi-module/Console/generated/model/federatedExchangeResponse.js.map +1 -0
  228. package/dist/accessi-module/Console/generated/model/federatedIdentityListResponse.d.ts +14 -0
  229. package/dist/accessi-module/Console/generated/model/federatedIdentityListResponse.js +3 -0
  230. package/dist/accessi-module/Console/generated/model/federatedIdentityListResponse.js.map +1 -0
  231. package/dist/accessi-module/Console/generated/model/federatedIdentityResponse.d.ts +14 -0
  232. package/dist/accessi-module/Console/generated/model/federatedIdentityResponse.js +3 -0
  233. package/dist/accessi-module/Console/generated/model/federatedIdentityResponse.js.map +1 -0
  234. package/dist/accessi-module/Console/generated/model/federatedIdentityResponseDto.d.ts +23 -0
  235. package/dist/accessi-module/Console/generated/model/federatedIdentityResponseDto.js +9 -0
  236. package/dist/accessi-module/Console/generated/model/federatedIdentityResponseDto.js.map +1 -0
  237. package/dist/accessi-module/Console/generated/model/federatedProviderListResponse.d.ts +14 -0
  238. package/dist/accessi-module/Console/generated/model/federatedProviderListResponse.js +3 -0
  239. package/dist/accessi-module/Console/generated/model/federatedProviderListResponse.js.map +1 -0
  240. package/dist/accessi-module/Console/generated/model/federatedProviderResponse.d.ts +14 -0
  241. package/dist/accessi-module/Console/generated/model/federatedProviderResponse.js +3 -0
  242. package/dist/accessi-module/Console/generated/model/federatedProviderResponse.js.map +1 -0
  243. package/dist/accessi-module/Console/generated/model/federatedProviderResponseDto.d.ts +13 -0
  244. package/dist/accessi-module/Console/generated/model/federatedProviderResponseDto.js +9 -0
  245. package/dist/accessi-module/Console/generated/model/federatedProviderResponseDto.js.map +1 -0
  246. package/dist/accessi-module/Console/generated/model/filtriUtente.d.ts +32 -0
  247. package/dist/accessi-module/Console/generated/model/filtriUtente.js +9 -0
  248. package/dist/accessi-module/Console/generated/model/filtriUtente.js.map +1 -0
  249. package/dist/accessi-module/Console/generated/model/getFiltriResponse.d.ts +14 -0
  250. package/dist/accessi-module/Console/generated/model/getFiltriResponse.js +3 -0
  251. package/dist/accessi-module/Console/generated/model/getFiltriResponse.js.map +1 -0
  252. package/dist/accessi-module/Console/generated/model/getFiltriUtenteParams.d.ts +12 -0
  253. package/dist/accessi-module/Console/generated/model/getFiltriUtenteParams.js +9 -0
  254. package/dist/accessi-module/Console/generated/model/getFiltriUtenteParams.js.map +1 -0
  255. package/dist/accessi-module/Console/generated/model/getFiltriUtenteResponse.d.ts +14 -0
  256. package/dist/accessi-module/Console/generated/model/getFiltriUtenteResponse.js +3 -0
  257. package/dist/accessi-module/Console/generated/model/getFiltriUtenteResponse.js.map +1 -0
  258. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusParams.d.ts +16 -0
  259. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusParams.js +9 -0
  260. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusParams.js.map +1 -0
  261. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusResponse.d.ts +14 -0
  262. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusResponse.js +3 -0
  263. package/dist/accessi-module/Console/generated/model/getGroupsWithMenusResponse.js.map +1 -0
  264. package/dist/accessi-module/Console/generated/model/getMenuScopesResponse.d.ts +14 -0
  265. package/dist/accessi-module/Console/generated/model/getMenuScopesResponse.js +3 -0
  266. package/dist/accessi-module/Console/generated/model/getMenuScopesResponse.js.map +1 -0
  267. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.d.ts +14 -0
  268. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.js +3 -0
  269. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.js.map +1 -0
  270. package/dist/accessi-module/Console/generated/model/getMenusParams.d.ts +12 -0
  271. package/dist/accessi-module/Console/generated/model/getMenusParams.js +9 -0
  272. package/dist/accessi-module/Console/generated/model/getMenusParams.js.map +1 -0
  273. package/dist/accessi-module/Console/generated/model/getMenusResponse.d.ts +14 -0
  274. package/dist/accessi-module/Console/generated/model/getMenusResponse.js +3 -0
  275. package/dist/accessi-module/Console/generated/model/getMenusResponse.js.map +1 -0
  276. package/dist/accessi-module/Console/generated/model/getRolesResponse.d.ts +14 -0
  277. package/dist/accessi-module/Console/generated/model/getRolesResponse.js +3 -0
  278. package/dist/accessi-module/Console/generated/model/getRolesResponse.js.map +1 -0
  279. package/dist/accessi-module/Console/generated/model/getSchemaResponse.d.ts +14 -0
  280. package/dist/accessi-module/Console/generated/model/getSchemaResponse.js +3 -0
  281. package/dist/accessi-module/Console/generated/model/getSchemaResponse.js.map +1 -0
  282. package/dist/accessi-module/Console/generated/model/getScopesResponse.d.ts +14 -0
  283. package/dist/accessi-module/Console/generated/model/getScopesResponse.js +3 -0
  284. package/dist/accessi-module/Console/generated/model/getScopesResponse.js.map +1 -0
  285. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.d.ts +12 -0
  286. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.js +9 -0
  287. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.js.map +1 -0
  288. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.d.ts +14 -0
  289. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.js +3 -0
  290. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.js.map +1 -0
  291. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.d.ts +10 -0
  292. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.js +9 -0
  293. package/dist/accessi-module/Console/generated/model/getUserByTokenRequest.js.map +1 -0
  294. package/dist/accessi-module/Console/generated/model/getUserByTokenResponse.d.ts +14 -0
  295. package/dist/accessi-module/Console/generated/model/getUserByTokenResponse.js +3 -0
  296. package/dist/accessi-module/Console/generated/model/getUserByTokenResponse.js.map +1 -0
  297. package/dist/accessi-module/Console/generated/model/getUserRolesAndGrantsParams.d.ts +12 -0
  298. package/dist/accessi-module/Console/generated/model/getUserRolesAndGrantsParams.js +9 -0
  299. package/dist/accessi-module/Console/generated/model/getUserRolesAndGrantsParams.js.map +1 -0
  300. package/dist/accessi-module/Console/generated/model/getUsersParams.d.ts +32 -0
  301. package/dist/accessi-module/Console/generated/model/getUsersParams.js +9 -0
  302. package/dist/accessi-module/Console/generated/model/getUsersParams.js.map +1 -0
  303. package/dist/accessi-module/Console/generated/model/getUsersResponse.d.ts +14 -0
  304. package/dist/accessi-module/Console/generated/model/getUsersResponse.js +3 -0
  305. package/dist/accessi-module/Console/generated/model/getUsersResponse.js.map +1 -0
  306. package/dist/accessi-module/Console/generated/model/getUsersResult.d.ts +17 -0
  307. package/dist/accessi-module/Console/generated/model/getUsersResult.js +3 -0
  308. package/dist/accessi-module/Console/generated/model/getUsersResult.js.map +1 -0
  309. package/dist/accessi-module/Console/generated/model/getUsersResultExtensionFields.d.ts +12 -0
  310. package/dist/accessi-module/Console/generated/model/getUsersResultExtensionFields.js +9 -0
  311. package/dist/accessi-module/Console/generated/model/getUsersResultExtensionFields.js.map +1 -0
  312. package/dist/accessi-module/Console/generated/model/groupWithMenusEntity.d.ts +19 -0
  313. package/dist/accessi-module/Console/generated/model/groupWithMenusEntity.js +3 -0
  314. package/dist/accessi-module/Console/generated/model/groupWithMenusEntity.js.map +1 -0
  315. package/dist/accessi-module/Console/generated/model/index.d.ts +134 -0
  316. package/dist/accessi-module/Console/generated/model/index.js +151 -0
  317. package/dist/accessi-module/Console/generated/model/index.js.map +1 -0
  318. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.d.ts +10 -0
  319. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js +9 -0
  320. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenRequest.js.map +1 -0
  321. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.d.ts +14 -0
  322. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js +3 -0
  323. package/dist/accessi-module/Console/generated/model/introspectAccessiTokenResponse.js.map +1 -0
  324. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.d.ts +30 -0
  325. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.js +9 -0
  326. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.js.map +1 -0
  327. package/dist/accessi-module/Console/generated/model/loginAsRequest.d.ts +10 -0
  328. package/dist/accessi-module/Console/generated/model/loginAsRequest.js +9 -0
  329. package/dist/accessi-module/Console/generated/model/loginAsRequest.js.map +1 -0
  330. package/dist/accessi-module/Console/generated/model/loginAsResponse.d.ts +14 -0
  331. package/dist/accessi-module/Console/generated/model/loginAsResponse.js +3 -0
  332. package/dist/accessi-module/Console/generated/model/loginAsResponse.js.map +1 -0
  333. package/dist/accessi-module/Console/generated/model/loginAsResult.d.ts +16 -0
  334. package/dist/accessi-module/Console/generated/model/loginAsResult.js +9 -0
  335. package/dist/accessi-module/Console/generated/model/loginAsResult.js.map +1 -0
  336. package/dist/accessi-module/Console/generated/model/loginRequest.d.ts +14 -0
  337. package/dist/accessi-module/Console/generated/model/loginRequest.js +9 -0
  338. package/dist/accessi-module/Console/generated/model/loginRequest.js.map +1 -0
  339. package/dist/accessi-module/Console/generated/model/loginResponse.d.ts +14 -0
  340. package/dist/accessi-module/Console/generated/model/loginResponse.js +3 -0
  341. package/dist/accessi-module/Console/generated/model/loginResponse.js.map +1 -0
  342. package/dist/accessi-module/Console/generated/model/loginResult.d.ts +34 -0
  343. package/dist/accessi-module/Console/generated/model/loginResult.js +3 -0
  344. package/dist/accessi-module/Console/generated/model/loginResult.js.map +1 -0
  345. package/dist/accessi-module/Console/generated/model/loginResultExtensionFields.d.ts +12 -0
  346. package/dist/accessi-module/Console/generated/model/loginResultExtensionFields.js +9 -0
  347. package/dist/accessi-module/Console/generated/model/loginResultExtensionFields.js.map +1 -0
  348. package/dist/accessi-module/Console/generated/model/loginResultPasswordRequired.d.ts +13 -0
  349. package/dist/accessi-module/Console/generated/model/loginResultPasswordRequired.js +14 -0
  350. package/dist/accessi-module/Console/generated/model/loginResultPasswordRequired.js.map +1 -0
  351. package/dist/accessi-module/Console/generated/model/menuEntity.d.ts +32 -0
  352. package/dist/accessi-module/Console/generated/model/menuEntity.js +9 -0
  353. package/dist/accessi-module/Console/generated/model/menuEntity.js.map +1 -0
  354. package/dist/accessi-module/Console/generated/model/menuTypeEntity.d.ts +12 -0
  355. package/dist/accessi-module/Console/generated/model/menuTypeEntity.js +9 -0
  356. package/dist/accessi-module/Console/generated/model/menuTypeEntity.js.map +1 -0
  357. package/dist/accessi-module/Console/generated/model/passwordExpiredResponse.d.ts +12 -0
  358. package/dist/accessi-module/Console/generated/model/passwordExpiredResponse.js +9 -0
  359. package/dist/accessi-module/Console/generated/model/passwordExpiredResponse.js.map +1 -0
  360. package/dist/accessi-module/Console/generated/model/permission.d.ts +12 -0
  361. package/dist/accessi-module/Console/generated/model/permission.js +9 -0
  362. package/dist/accessi-module/Console/generated/model/permission.js.map +1 -0
  363. package/dist/accessi-module/Console/generated/model/publicRegisterRequest.d.ts +51 -0
  364. package/dist/accessi-module/Console/generated/model/publicRegisterRequest.js +9 -0
  365. package/dist/accessi-module/Console/generated/model/publicRegisterRequest.js.map +1 -0
  366. package/dist/accessi-module/Console/generated/model/queryAuditOrder.d.ts +11 -0
  367. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js +15 -0
  368. package/dist/accessi-module/Console/generated/model/queryAuditOrder.js.map +1 -0
  369. package/dist/accessi-module/Console/generated/model/queryAuditParams.d.ts +37 -0
  370. package/dist/accessi-module/Console/generated/model/queryAuditParams.js +3 -0
  371. package/dist/accessi-module/Console/generated/model/queryAuditParams.js.map +1 -0
  372. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.d.ts +10 -0
  373. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js +9 -0
  374. package/dist/accessi-module/Console/generated/model/refreshTokenRequest.js.map +1 -0
  375. package/dist/accessi-module/Console/generated/model/refreshTokenResponse.d.ts +14 -0
  376. package/dist/accessi-module/Console/generated/model/refreshTokenResponse.js +3 -0
  377. package/dist/accessi-module/Console/generated/model/refreshTokenResponse.js.map +1 -0
  378. package/dist/accessi-module/Console/generated/model/refreshTokenResult.d.ts +13 -0
  379. package/dist/accessi-module/Console/generated/model/refreshTokenResult.js +3 -0
  380. package/dist/accessi-module/Console/generated/model/refreshTokenResult.js.map +1 -0
  381. package/dist/accessi-module/Console/generated/model/registerRequest.d.ts +62 -0
  382. package/dist/accessi-module/Console/generated/model/registerRequest.js +3 -0
  383. package/dist/accessi-module/Console/generated/model/registerRequest.js.map +1 -0
  384. package/dist/accessi-module/Console/generated/model/registerResponse.d.ts +14 -0
  385. package/dist/accessi-module/Console/generated/model/registerResponse.js +3 -0
  386. package/dist/accessi-module/Console/generated/model/registerResponse.js.map +1 -0
  387. package/dist/accessi-module/Console/generated/model/resendTwoFactorRequest.d.ts +13 -0
  388. package/dist/accessi-module/Console/generated/model/resendTwoFactorRequest.js +9 -0
  389. package/dist/accessi-module/Console/generated/model/resendTwoFactorRequest.js.map +1 -0
  390. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.d.ts +10 -0
  391. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.js +9 -0
  392. package/dist/accessi-module/Console/generated/model/revokeTokenRequest.js.map +1 -0
  393. package/dist/accessi-module/Console/generated/model/role.d.ts +15 -0
  394. package/dist/accessi-module/Console/generated/model/role.js +3 -0
  395. package/dist/accessi-module/Console/generated/model/role.js.map +1 -0
  396. package/dist/accessi-module/Console/generated/model/ruoliMenu.d.ts +15 -0
  397. package/dist/accessi-module/Console/generated/model/ruoliMenu.js +3 -0
  398. package/dist/accessi-module/Console/generated/model/ruoliMenu.js.map +1 -0
  399. package/dist/accessi-module/Console/generated/model/ruoliMenuTipoAbilitazione.d.ts +16 -0
  400. package/dist/accessi-module/Console/generated/model/ruoliMenuTipoAbilitazione.js +17 -0
  401. package/dist/accessi-module/Console/generated/model/ruoliMenuTipoAbilitazione.js.map +1 -0
  402. package/dist/accessi-module/Console/generated/model/schemaDdlResult.d.ts +20 -0
  403. package/dist/accessi-module/Console/generated/model/schemaDdlResult.js +3 -0
  404. package/dist/accessi-module/Console/generated/model/schemaDdlResult.js.map +1 -0
  405. package/dist/accessi-module/Console/generated/model/schemaDdlSection.d.ts +12 -0
  406. package/dist/accessi-module/Console/generated/model/schemaDdlSection.js +9 -0
  407. package/dist/accessi-module/Console/generated/model/schemaDdlSection.js.map +1 -0
  408. package/dist/accessi-module/Console/generated/model/scopeEntity.d.ts +12 -0
  409. package/dist/accessi-module/Console/generated/model/scopeEntity.js +9 -0
  410. package/dist/accessi-module/Console/generated/model/scopeEntity.js.map +1 -0
  411. package/dist/accessi-module/Console/generated/model/sendResetPasswordEmailRequest.d.ts +12 -0
  412. package/dist/accessi-module/Console/generated/model/sendResetPasswordEmailRequest.js +9 -0
  413. package/dist/accessi-module/Console/generated/model/sendResetPasswordEmailRequest.js.map +1 -0
  414. package/dist/accessi-module/Console/generated/model/serveResetPasswordPageParams.d.ts +12 -0
  415. package/dist/accessi-module/Console/generated/model/serveResetPasswordPageParams.js +9 -0
  416. package/dist/accessi-module/Console/generated/model/serveResetPasswordPageParams.js.map +1 -0
  417. package/dist/accessi-module/Console/generated/model/serviceTokenDto.d.ts +28 -0
  418. package/dist/accessi-module/Console/generated/model/serviceTokenDto.js +9 -0
  419. package/dist/accessi-module/Console/generated/model/serviceTokenDto.js.map +1 -0
  420. package/dist/accessi-module/Console/generated/model/setMenuScopesRequest.d.ts +10 -0
  421. package/dist/accessi-module/Console/generated/model/setMenuScopesRequest.js +9 -0
  422. package/dist/accessi-module/Console/generated/model/setMenuScopesRequest.js.map +1 -0
  423. package/dist/accessi-module/Console/generated/model/setPasswordLoginPolicyRequest.d.ts +10 -0
  424. package/dist/accessi-module/Console/generated/model/setPasswordLoginPolicyRequest.js +9 -0
  425. package/dist/accessi-module/Console/generated/model/setPasswordLoginPolicyRequest.js.map +1 -0
  426. package/dist/accessi-module/Console/generated/model/setScopeMenusRequest.d.ts +10 -0
  427. package/dist/accessi-module/Console/generated/model/setScopeMenusRequest.js +9 -0
  428. package/dist/accessi-module/Console/generated/model/setScopeMenusRequest.js.map +1 -0
  429. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDto.d.ts +13 -0
  430. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDto.js +3 -0
  431. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDto.js.map +1 -0
  432. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDtoStatoRegistrazione.d.ts +18 -0
  433. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDtoStatoRegistrazione.js +19 -0
  434. package/dist/accessi-module/Console/generated/model/setStatoRegistrazioneDtoStatoRegistrazione.js.map +1 -0
  435. package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.d.ts +14 -0
  436. package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.js +9 -0
  437. package/dist/accessi-module/Console/generated/model/setUserPasswordRequest.js.map +1 -0
  438. package/dist/accessi-module/Console/generated/model/status.d.ts +12 -0
  439. package/dist/accessi-module/Console/generated/model/status.js +9 -0
  440. package/dist/accessi-module/Console/generated/model/status.js.map +1 -0
  441. package/dist/accessi-module/Console/generated/model/tipoFiltro.d.ts +23 -0
  442. package/dist/accessi-module/Console/generated/model/tipoFiltro.js +3 -0
  443. package/dist/accessi-module/Console/generated/model/tipoFiltro.js.map +1 -0
  444. package/dist/accessi-module/Console/generated/model/tipoFiltroFlgEnabled.d.ts +14 -0
  445. package/dist/accessi-module/Console/generated/model/tipoFiltroFlgEnabled.js +15 -0
  446. package/dist/accessi-module/Console/generated/model/tipoFiltroFlgEnabled.js.map +1 -0
  447. package/dist/accessi-module/Console/generated/model/tokenResult.d.ts +15 -0
  448. package/dist/accessi-module/Console/generated/model/tokenResult.js +3 -0
  449. package/dist/accessi-module/Console/generated/model/tokenResult.js.map +1 -0
  450. package/dist/accessi-module/Console/generated/model/tokenResultType.d.ts +14 -0
  451. package/dist/accessi-module/Console/generated/model/tokenResultType.js +15 -0
  452. package/dist/accessi-module/Console/generated/model/tokenResultType.js.map +1 -0
  453. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDto.d.ts +18 -0
  454. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDto.js +3 -0
  455. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDto.js.map +1 -0
  456. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoMethod.d.ts +10 -0
  457. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoMethod.js +14 -0
  458. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoMethod.js.map +1 -0
  459. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoTwoFactorRequired.d.ts +13 -0
  460. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoTwoFactorRequired.js +14 -0
  461. package/dist/accessi-module/Console/generated/model/twoFactorChallengeDtoTwoFactorRequired.js.map +1 -0
  462. package/dist/accessi-module/Console/generated/model/updateEnabledStatusRequest.d.ts +10 -0
  463. package/dist/accessi-module/Console/generated/model/updateEnabledStatusRequest.js +9 -0
  464. package/dist/accessi-module/Console/generated/model/updateEnabledStatusRequest.js.map +1 -0
  465. package/dist/accessi-module/Console/generated/model/updateFederatedIdentityRequest.d.ts +11 -0
  466. package/dist/accessi-module/Console/generated/model/updateFederatedIdentityRequest.js +9 -0
  467. package/dist/accessi-module/Console/generated/model/updateFederatedIdentityRequest.js.map +1 -0
  468. package/dist/accessi-module/Console/generated/model/updateFederatedProviderRequest.d.ts +12 -0
  469. package/dist/accessi-module/Console/generated/model/updateFederatedProviderRequest.js +9 -0
  470. package/dist/accessi-module/Console/generated/model/updateFederatedProviderRequest.js.map +1 -0
  471. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.d.ts +21 -0
  472. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.js +3 -0
  473. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.js.map +1 -0
  474. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.d.ts +14 -0
  475. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.js +15 -0
  476. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.js.map +1 -0
  477. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.d.ts +17 -0
  478. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.js +9 -0
  479. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.js.map +1 -0
  480. package/dist/accessi-module/Console/generated/model/updateMenuRequest.d.ts +47 -0
  481. package/dist/accessi-module/Console/generated/model/updateMenuRequest.js +9 -0
  482. package/dist/accessi-module/Console/generated/model/updateMenuRequest.js.map +1 -0
  483. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.d.ts +13 -0
  484. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.js +9 -0
  485. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.js.map +1 -0
  486. package/dist/accessi-module/Console/generated/model/updateScopeRequest.d.ts +13 -0
  487. package/dist/accessi-module/Console/generated/model/updateScopeRequest.js +9 -0
  488. package/dist/accessi-module/Console/generated/model/updateScopeRequest.js.map +1 -0
  489. package/dist/accessi-module/Console/generated/model/userDto.d.ts +88 -0
  490. package/dist/accessi-module/Console/generated/model/userDto.js +3 -0
  491. package/dist/accessi-module/Console/generated/model/userDto.js.map +1 -0
  492. package/dist/accessi-module/Console/generated/model/userDtoStatoRegistrazione.d.ts +18 -0
  493. package/dist/accessi-module/Console/generated/model/userDtoStatoRegistrazione.js +19 -0
  494. package/dist/accessi-module/Console/generated/model/userDtoStatoRegistrazione.js.map +1 -0
  495. package/dist/accessi-module/Console/generated/model/userGrantsDto.d.ts +16 -0
  496. package/dist/accessi-module/Console/generated/model/userGrantsDto.js +3 -0
  497. package/dist/accessi-module/Console/generated/model/userGrantsDto.js.map +1 -0
  498. package/dist/accessi-module/Console/generated/model/userGrantsResponse.d.ts +14 -0
  499. package/dist/accessi-module/Console/generated/model/userGrantsResponse.js +3 -0
  500. package/dist/accessi-module/Console/generated/model/userGrantsResponse.js.map +1 -0
  501. package/dist/accessi-module/Console/generated/model/verifyTwoFactorRequest.d.ts +20 -0
  502. package/dist/accessi-module/Console/generated/model/verifyTwoFactorRequest.js +9 -0
  503. package/dist/accessi-module/Console/generated/model/verifyTwoFactorRequest.js.map +1 -0
  504. package/dist/accessi-module/Console/help.d.ts +13 -0
  505. package/dist/accessi-module/Console/help.js +417 -0
  506. package/dist/accessi-module/Console/help.js.map +1 -0
  507. package/dist/accessi-module/Console/index.html +141 -0
  508. package/dist/accessi-module/Console/wiki.d.ts +52 -0
  509. package/dist/accessi-module/Console/wiki.js +867 -0
  510. package/dist/accessi-module/Console/wiki.js.map +1 -0
  511. package/dist/accessi-module/Controllers/AccessiAuditController.d.ts +16 -0
  512. package/dist/accessi-module/Controllers/AccessiAuditController.js +155 -0
  513. package/dist/accessi-module/Controllers/AccessiAuditController.js.map +1 -0
  514. package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +25 -0
  515. package/dist/accessi-module/Controllers/AccessiConsoleController.js +169 -0
  516. package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -0
  517. package/dist/accessi-module/Controllers/AccessiIntrospectionController.d.ts +17 -0
  518. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js +102 -0
  519. package/dist/accessi-module/Controllers/AccessiIntrospectionController.js.map +1 -0
  520. package/dist/accessi-module/Controllers/AuthController.d.ts +16 -6
  521. package/dist/accessi-module/Controllers/AuthController.js +435 -85
  522. package/dist/accessi-module/Controllers/AuthController.js.map +1 -1
  523. package/dist/accessi-module/Controllers/ConfiguratorController.d.ts +1 -5
  524. package/dist/accessi-module/Controllers/ConfiguratorController.js +26 -44
  525. package/dist/accessi-module/Controllers/ConfiguratorController.js.map +1 -1
  526. package/dist/accessi-module/Controllers/ConsoleEntryController.d.ts +16 -0
  527. package/dist/accessi-module/Controllers/ConsoleEntryController.js +159 -0
  528. package/dist/accessi-module/Controllers/ConsoleEntryController.js.map +1 -0
  529. package/dist/accessi-module/Controllers/EmailController.d.ts +4 -6
  530. package/dist/accessi-module/Controllers/EmailController.js +13 -15
  531. package/dist/accessi-module/Controllers/EmailController.js.map +1 -1
  532. package/dist/accessi-module/Controllers/FiltriController.d.ts +12 -4
  533. package/dist/accessi-module/Controllers/FiltriController.js +153 -6
  534. package/dist/accessi-module/Controllers/FiltriController.js.map +1 -1
  535. package/dist/accessi-module/Controllers/PermissionController.d.ts +31 -5
  536. package/dist/accessi-module/Controllers/PermissionController.js +559 -66
  537. package/dist/accessi-module/Controllers/PermissionController.js.map +1 -1
  538. package/dist/accessi-module/Controllers/SchemaController.d.ts +9 -0
  539. package/dist/accessi-module/Controllers/SchemaController.js +79 -0
  540. package/dist/accessi-module/Controllers/SchemaController.js.map +1 -0
  541. package/dist/accessi-module/Controllers/ServiceTokenController.d.ts +23 -0
  542. package/dist/accessi-module/Controllers/ServiceTokenController.js +176 -0
  543. package/dist/accessi-module/Controllers/ServiceTokenController.js.map +1 -0
  544. package/dist/accessi-module/Controllers/UserController.d.ts +22 -5
  545. package/dist/accessi-module/Controllers/UserController.js +385 -33
  546. package/dist/accessi-module/Controllers/UserController.js.map +1 -1
  547. package/dist/accessi-module/Dtos/Abilitazione.js.map +1 -1
  548. package/dist/accessi-module/Dtos/AbilitazioneMenu.js +16 -8
  549. package/dist/accessi-module/Dtos/AbilitazioneMenu.js.map +1 -1
  550. package/dist/accessi-module/Dtos/AdminBootstrapDtos.d.ts +26 -0
  551. package/dist/accessi-module/Dtos/AdminBootstrapDtos.js +100 -0
  552. package/dist/accessi-module/Dtos/AdminBootstrapDtos.js.map +1 -0
  553. package/dist/accessi-module/Dtos/AssignPermissionsToUserRequest.js +0 -1
  554. package/dist/accessi-module/Dtos/AssignPermissionsToUserRequest.js.map +1 -1
  555. package/dist/accessi-module/Dtos/AssignRolesToUserRequest.js +1 -2
  556. package/dist/accessi-module/Dtos/AssignRolesToUserRequest.js.map +1 -1
  557. package/dist/accessi-module/Dtos/AuditDtos.d.ts +10 -0
  558. package/dist/accessi-module/Dtos/AuditDtos.js +68 -0
  559. package/dist/accessi-module/Dtos/AuditDtos.js.map +1 -0
  560. package/dist/accessi-module/Dtos/AuthenticatedUserPayloadDto.d.ts +13 -0
  561. package/dist/accessi-module/Dtos/AuthenticatedUserPayloadDto.js +70 -0
  562. package/dist/accessi-module/Dtos/AuthenticatedUserPayloadDto.js.map +1 -0
  563. package/dist/accessi-module/Dtos/BaseResponse.d.ts +22 -0
  564. package/dist/accessi-module/Dtos/BaseResponse.js +106 -1
  565. package/dist/accessi-module/Dtos/BaseResponse.js.map +1 -1
  566. package/dist/accessi-module/Dtos/CatalogDtos.d.ts +64 -0
  567. package/dist/accessi-module/Dtos/CatalogDtos.js +322 -0
  568. package/dist/accessi-module/Dtos/CatalogDtos.js.map +1 -0
  569. package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.d.ts +3 -0
  570. package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.js +31 -0
  571. package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.js.map +1 -0
  572. package/dist/accessi-module/Dtos/ConsoleEntryDtos.d.ts +24 -0
  573. package/dist/accessi-module/Dtos/ConsoleEntryDtos.js +77 -0
  574. package/dist/accessi-module/Dtos/ConsoleEntryDtos.js.map +1 -0
  575. package/dist/accessi-module/Dtos/FederatedExchangeDtos.d.ts +21 -0
  576. package/dist/accessi-module/Dtos/FederatedExchangeDtos.js +74 -0
  577. package/dist/accessi-module/Dtos/FederatedExchangeDtos.js.map +1 -0
  578. package/dist/accessi-module/Dtos/FederatedIdentityDtos.d.ts +69 -0
  579. package/dist/accessi-module/Dtos/FederatedIdentityDtos.js +236 -0
  580. package/dist/accessi-module/Dtos/FederatedIdentityDtos.js.map +1 -0
  581. package/dist/accessi-module/Dtos/FiltriUtente.js +5 -1
  582. package/dist/accessi-module/Dtos/FiltriUtente.js.map +1 -1
  583. package/dist/accessi-module/Dtos/GetGroupsWithMenusResponse.js.map +1 -1
  584. package/dist/accessi-module/Dtos/GetMenusResponse.js +1 -1
  585. package/dist/accessi-module/Dtos/GetMenusResponse.js.map +1 -1
  586. package/dist/accessi-module/Dtos/GetRolesResponse.js.map +1 -1
  587. package/dist/accessi-module/Dtos/GetUserByTokenRequest.d.ts +3 -0
  588. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js +27 -0
  589. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js.map +1 -0
  590. package/dist/accessi-module/Dtos/GetUserByTokenResponse.d.ts +5 -0
  591. package/dist/accessi-module/Dtos/GetUserByTokenResponse.js +27 -0
  592. package/dist/accessi-module/Dtos/GetUserByTokenResponse.js.map +1 -0
  593. package/dist/accessi-module/Dtos/GetUsersResponse.d.ts +1 -1
  594. package/dist/accessi-module/Dtos/GetUsersResponse.js.map +1 -1
  595. package/dist/accessi-module/Dtos/IntrospectionDtos.d.ts +16 -0
  596. package/dist/accessi-module/Dtos/IntrospectionDtos.js +59 -0
  597. package/dist/accessi-module/Dtos/IntrospectionDtos.js.map +1 -0
  598. package/dist/accessi-module/Dtos/LoginAsDtos.d.ts +15 -0
  599. package/dist/accessi-module/Dtos/LoginAsDtos.js +68 -0
  600. package/dist/accessi-module/Dtos/LoginAsDtos.js.map +1 -0
  601. package/dist/accessi-module/Dtos/LoginRequest.d.ts +3 -1
  602. package/dist/accessi-module/Dtos/LoginRequest.js +13 -2
  603. package/dist/accessi-module/Dtos/LoginRequest.js.map +1 -1
  604. package/dist/accessi-module/Dtos/LoginResponse.d.ts +7 -2
  605. package/dist/accessi-module/Dtos/LoginResponse.js +26 -7
  606. package/dist/accessi-module/Dtos/LoginResponse.js.map +1 -1
  607. package/dist/accessi-module/Dtos/MenuItem.js +1 -0
  608. package/dist/accessi-module/Dtos/Permission.d.ts +1 -0
  609. package/dist/accessi-module/Dtos/Permission.js +1 -0
  610. package/dist/accessi-module/Dtos/Permission.js.map +1 -1
  611. package/dist/accessi-module/Dtos/PublicRegisterRequest.d.ts +5 -0
  612. package/dist/accessi-module/Dtos/PublicRegisterRequest.js +15 -0
  613. package/dist/accessi-module/Dtos/PublicRegisterRequest.js.map +1 -0
  614. package/dist/accessi-module/Dtos/RefreshTokenDtos.d.ts +12 -0
  615. package/dist/accessi-module/Dtos/RefreshTokenDtos.js +53 -0
  616. package/dist/accessi-module/Dtos/RefreshTokenDtos.js.map +1 -0
  617. package/dist/accessi-module/Dtos/RegisterRequest.d.ts +2 -4
  618. package/dist/accessi-module/Dtos/RegisterRequest.js +17 -27
  619. package/dist/accessi-module/Dtos/RegisterRequest.js.map +1 -1
  620. package/dist/accessi-module/Dtos/RegisterResponse.d.ts +1 -1
  621. package/dist/accessi-module/Dtos/RegisterResponse.js +3 -3
  622. package/dist/accessi-module/Dtos/RegisterResponse.js.map +1 -1
  623. package/dist/accessi-module/Dtos/RevokeTokenRequest.d.ts +3 -0
  624. package/dist/accessi-module/Dtos/RevokeTokenRequest.js +27 -0
  625. package/dist/accessi-module/Dtos/RevokeTokenRequest.js.map +1 -0
  626. package/dist/accessi-module/Dtos/Role.d.ts +4 -0
  627. package/dist/accessi-module/Dtos/Role.js +4 -0
  628. package/dist/accessi-module/Dtos/Role.js.map +1 -1
  629. package/dist/accessi-module/Dtos/RuoliMenu.js.map +1 -1
  630. package/dist/accessi-module/Dtos/SchemaDtos.d.ts +19 -0
  631. package/dist/accessi-module/Dtos/SchemaDtos.js +84 -0
  632. package/dist/accessi-module/Dtos/SchemaDtos.js.map +1 -0
  633. package/dist/accessi-module/Dtos/ScopeDtos.d.ts +28 -0
  634. package/dist/accessi-module/Dtos/ScopeDtos.js +103 -0
  635. package/dist/accessi-module/Dtos/ScopeDtos.js.map +1 -0
  636. package/dist/accessi-module/Dtos/SendResetPasswordEmailRequest.d.ts +4 -0
  637. package/dist/accessi-module/Dtos/SendResetPasswordEmailRequest.js +36 -0
  638. package/dist/accessi-module/Dtos/SendResetPasswordEmailRequest.js.map +1 -0
  639. package/dist/accessi-module/Dtos/ServiceToken.d.ts +34 -0
  640. package/dist/accessi-module/Dtos/ServiceToken.js +128 -0
  641. package/dist/accessi-module/Dtos/ServiceToken.js.map +1 -0
  642. package/dist/accessi-module/Dtos/SetStatoRegistrazione.js.map +1 -1
  643. package/dist/accessi-module/Dtos/SetUserPasswordRequest.d.ts +4 -0
  644. package/dist/accessi-module/Dtos/SetUserPasswordRequest.js +32 -0
  645. package/dist/accessi-module/Dtos/SetUserPasswordRequest.js.map +1 -0
  646. package/dist/accessi-module/Dtos/StatoRegistrazione.js.map +1 -1
  647. package/dist/accessi-module/Dtos/TipoAbilitazione.d.ts +4 -0
  648. package/dist/accessi-module/Dtos/TipoAbilitazione.js +4 -0
  649. package/dist/accessi-module/Dtos/TipoAbilitazione.js.map +1 -1
  650. package/dist/accessi-module/Dtos/TipoFiltro.js.map +1 -1
  651. package/dist/accessi-module/Dtos/TokenResult.js.map +1 -1
  652. package/dist/accessi-module/Dtos/TwoFactorDtos.d.ts +14 -0
  653. package/dist/accessi-module/Dtos/TwoFactorDtos.js +71 -0
  654. package/dist/accessi-module/Dtos/TwoFactorDtos.js.map +1 -0
  655. package/dist/accessi-module/Dtos/UpdateEnabledStatusRequest.js.map +1 -1
  656. package/dist/accessi-module/Dtos/UserDto.d.ts +9 -4
  657. package/dist/accessi-module/Dtos/UserDto.js +37 -22
  658. package/dist/accessi-module/Dtos/UserDto.js.map +1 -1
  659. package/dist/accessi-module/Dtos/UserGrantsDto.js.map +1 -1
  660. package/dist/accessi-module/Dtos/UserGrantsResponse.d.ts +5 -0
  661. package/dist/accessi-module/Dtos/UserGrantsResponse.js +27 -0
  662. package/dist/accessi-module/Dtos/UserGrantsResponse.js.map +1 -0
  663. package/dist/accessi-module/Dtos/index.d.ts +20 -0
  664. package/dist/accessi-module/Dtos/index.js +20 -0
  665. package/dist/accessi-module/Dtos/index.js.map +1 -1
  666. package/dist/accessi-module/Services/AdminBootstrapService/AdminBootstrapService.d.ts +33 -0
  667. package/dist/accessi-module/Services/AdminBootstrapService/AdminBootstrapService.js +106 -0
  668. package/dist/accessi-module/Services/AdminBootstrapService/AdminBootstrapService.js.map +1 -0
  669. package/dist/accessi-module/Services/AuthService/AuthService.d.ts +108 -5
  670. package/dist/accessi-module/Services/AuthService/AuthService.js +458 -54
  671. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  672. package/dist/accessi-module/Services/ConfiguratorService/ConfiguratorService.d.ts +3 -1
  673. package/dist/accessi-module/Services/ConfiguratorService/ConfiguratorService.js +5 -1
  674. package/dist/accessi-module/Services/ConfiguratorService/ConfiguratorService.js.map +1 -1
  675. package/dist/accessi-module/Services/EmailService/EmailService.d.ts +9 -2
  676. package/dist/accessi-module/Services/EmailService/EmailService.js +47 -22
  677. package/dist/accessi-module/Services/EmailService/EmailService.js.map +1 -1
  678. package/dist/accessi-module/Services/FiltriService/FiltriService.d.ts +18 -3
  679. package/dist/accessi-module/Services/FiltriService/FiltriService.js +92 -16
  680. package/dist/accessi-module/Services/FiltriService/FiltriService.js.map +1 -1
  681. package/dist/accessi-module/Services/PermissionService/PermissionService.d.ts +107 -5
  682. package/dist/accessi-module/Services/PermissionService/PermissionService.js +788 -215
  683. package/dist/accessi-module/Services/PermissionService/PermissionService.js.map +1 -1
  684. package/dist/accessi-module/Services/SchemaExportService/SchemaExportService.d.ts +36 -0
  685. package/dist/accessi-module/Services/SchemaExportService/SchemaExportService.js +125 -0
  686. package/dist/accessi-module/Services/SchemaExportService/SchemaExportService.js.map +1 -0
  687. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.d.ts +75 -0
  688. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.js +265 -0
  689. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.js.map +1 -0
  690. package/dist/accessi-module/Services/TwoFactorService/TwoFactorService.d.ts +27 -0
  691. package/dist/accessi-module/Services/TwoFactorService/TwoFactorService.js +175 -0
  692. package/dist/accessi-module/Services/TwoFactorService/TwoFactorService.js.map +1 -0
  693. package/dist/accessi-module/Services/UserService/UserService.d.ts +73 -5
  694. package/dist/accessi-module/Services/UserService/UserService.js +578 -337
  695. package/dist/accessi-module/Services/UserService/UserService.js.map +1 -1
  696. package/dist/accessi-module/Views/reset-password.html +229 -213
  697. package/dist/accessi-module/accessiRuntime.d.ts +45 -0
  698. package/dist/accessi-module/accessiRuntime.js +39 -0
  699. package/dist/accessi-module/accessiRuntime.js.map +1 -0
  700. package/dist/accessi-module/database-updates/AccessiCatalogMigrator.d.ts +83 -0
  701. package/dist/accessi-module/database-updates/AccessiCatalogMigrator.js +360 -0
  702. package/dist/accessi-module/database-updates/AccessiCatalogMigrator.js.map +1 -0
  703. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.d.ts +58 -11
  704. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js +504 -210
  705. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js.map +1 -1
  706. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.d.ts +17 -0
  707. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js +134 -0
  708. package/dist/accessi-module/database-updates/accessiOptionsFromEnv.js.map +1 -0
  709. package/dist/accessi-module/database-updates/accessiSchema.d.ts +67 -0
  710. package/dist/accessi-module/database-updates/accessiSchema.js +135 -0
  711. package/dist/accessi-module/database-updates/accessiSchema.js.map +1 -0
  712. package/dist/accessi-module/database-updates/menuScopeSeed.d.ts +15 -0
  713. package/dist/accessi-module/database-updates/menuScopeSeed.js +386 -0
  714. package/dist/accessi-module/database-updates/menuScopeSeed.js.map +1 -0
  715. package/dist/accessi-module/database-updates/menuScopeSeeder.d.ts +8 -0
  716. package/dist/accessi-module/database-updates/menuScopeSeeder.js +131 -0
  717. package/dist/accessi-module/database-updates/menuScopeSeeder.js.map +1 -0
  718. package/dist/accessi-module/database-updates/optionalColumns.d.ts +7 -0
  719. package/dist/accessi-module/database-updates/optionalColumns.js +58 -0
  720. package/dist/accessi-module/database-updates/optionalColumns.js.map +1 -0
  721. package/dist/accessi-module/database-updates/runAccessiCatalogMigrations.d.ts +1 -0
  722. package/dist/accessi-module/database-updates/runAccessiCatalogMigrations.js +69 -0
  723. package/dist/accessi-module/database-updates/runAccessiCatalogMigrations.js.map +1 -0
  724. package/dist/accessi-module/database-updates/runAccessiDbUpdate.js +18 -53
  725. package/dist/accessi-module/database-updates/runAccessiDbUpdate.js.map +1 -1
  726. package/dist/accessi-module/federated-auth/FederatedAuthController.d.ts +33 -0
  727. package/dist/accessi-module/federated-auth/FederatedAuthController.js +373 -0
  728. package/dist/accessi-module/federated-auth/FederatedAuthController.js.map +1 -0
  729. package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.d.ts +15 -0
  730. package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.js +85 -0
  731. package/dist/accessi-module/federated-auth/FederatedAuthExchangeController.js.map +1 -0
  732. package/dist/accessi-module/federated-auth/FederatedAuthService.d.ts +102 -0
  733. package/dist/accessi-module/federated-auth/FederatedAuthService.js +472 -0
  734. package/dist/accessi-module/federated-auth/FederatedAuthService.js.map +1 -0
  735. package/dist/accessi-module/federated-auth/FederatedAuthTypes.d.ts +53 -0
  736. package/dist/accessi-module/federated-auth/FederatedAuthTypes.js +3 -0
  737. package/dist/accessi-module/federated-auth/FederatedAuthTypes.js.map +1 -0
  738. package/dist/accessi-module/health/nest.d.ts +31 -0
  739. package/dist/accessi-module/health/nest.js +89 -0
  740. package/dist/accessi-module/health/nest.js.map +1 -0
  741. package/dist/accessi-module/health/readiness.d.ts +53 -0
  742. package/dist/accessi-module/health/readiness.js +110 -0
  743. package/dist/accessi-module/health/readiness.js.map +1 -0
  744. package/dist/accessi-module/index.d.ts +79 -1
  745. package/dist/accessi-module/index.js +224 -22
  746. package/dist/accessi-module/index.js.map +1 -1
  747. package/dist/accessi-module/jwt/jwt.strategy.d.ts +1 -1
  748. package/dist/accessi-module/jwt/jwt.strategy.js +21 -42
  749. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  750. package/dist/accessi-module/middleware/accessiRequirements.d.ts +23 -1
  751. package/dist/accessi-module/middleware/accessiRequirements.js +7 -0
  752. package/dist/accessi-module/middleware/accessiRequirements.js.map +1 -1
  753. package/dist/accessi-module/middleware/authenticateGen.d.ts +16 -1
  754. package/dist/accessi-module/middleware/authenticateGen.js +68 -60
  755. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  756. package/dist/accessi-module/observability/metrics.d.ts +79 -0
  757. package/dist/accessi-module/observability/metrics.js +220 -0
  758. package/dist/accessi-module/observability/metrics.js.map +1 -0
  759. package/dist/accessi-module/observability/redisMetrics.d.ts +12 -0
  760. package/dist/accessi-module/observability/redisMetrics.js +44 -0
  761. package/dist/accessi-module/observability/redisMetrics.js.map +1 -0
  762. package/dist/accessi-module/security/AccessiHttpExceptionFilter.d.ts +12 -0
  763. package/dist/accessi-module/security/AccessiHttpExceptionFilter.js +77 -0
  764. package/dist/accessi-module/security/AccessiHttpExceptionFilter.js.map +1 -0
  765. package/dist/accessi-module/security/accessControl.d.ts +48 -2
  766. package/dist/accessi-module/security/accessControl.js +84 -8
  767. package/dist/accessi-module/security/accessControl.js.map +1 -1
  768. package/dist/accessi-module/security/accessTokenRevocation.d.ts +47 -0
  769. package/dist/accessi-module/security/accessTokenRevocation.js +148 -0
  770. package/dist/accessi-module/security/accessTokenRevocation.js.map +1 -0
  771. package/dist/accessi-module/security/accessiValidation.d.ts +9 -0
  772. package/dist/accessi-module/security/accessiValidation.js +41 -0
  773. package/dist/accessi-module/security/accessiValidation.js.map +1 -0
  774. package/dist/accessi-module/security/adminBootstrap.d.ts +8 -0
  775. package/dist/accessi-module/security/adminBootstrap.js +41 -0
  776. package/dist/accessi-module/security/adminBootstrap.js.map +1 -0
  777. package/dist/accessi-module/security/audit.d.ts +74 -0
  778. package/dist/accessi-module/security/audit.js +189 -0
  779. package/dist/accessi-module/security/audit.js.map +1 -0
  780. package/dist/accessi-module/security/auditSinks.d.ts +55 -0
  781. package/dist/accessi-module/security/auditSinks.js +228 -0
  782. package/dist/accessi-module/security/auditSinks.js.map +1 -0
  783. package/dist/accessi-module/security/authCookies.d.ts +104 -0
  784. package/dist/accessi-module/security/authCookies.js +240 -0
  785. package/dist/accessi-module/security/authCookies.js.map +1 -0
  786. package/dist/accessi-module/security/authenticatedToken.d.ts +23 -3
  787. package/dist/accessi-module/security/authenticatedToken.js +45 -5
  788. package/dist/accessi-module/security/authenticatedToken.js.map +1 -1
  789. package/dist/accessi-module/security/consoleEntry.d.ts +63 -0
  790. package/dist/accessi-module/security/consoleEntry.js +202 -0
  791. package/dist/accessi-module/security/consoleEntry.js.map +1 -0
  792. package/dist/accessi-module/security/edgeRateLimit.d.ts +33 -0
  793. package/dist/accessi-module/security/edgeRateLimit.js +84 -0
  794. package/dist/accessi-module/security/edgeRateLimit.js.map +1 -0
  795. package/dist/accessi-module/security/emailConfiguration.d.ts +20 -0
  796. package/dist/accessi-module/security/emailConfiguration.js +41 -0
  797. package/dist/accessi-module/security/emailConfiguration.js.map +1 -0
  798. package/dist/accessi-module/security/jwks.d.ts +33 -0
  799. package/dist/accessi-module/security/jwks.js +86 -0
  800. package/dist/accessi-module/security/jwks.js.map +1 -0
  801. package/dist/accessi-module/security/jwksBootstrap.d.ts +15 -0
  802. package/dist/accessi-module/security/jwksBootstrap.js +44 -0
  803. package/dist/accessi-module/security/jwksBootstrap.js.map +1 -0
  804. package/dist/accessi-module/security/jwtTokenOptions.d.ts +57 -0
  805. package/dist/accessi-module/security/jwtTokenOptions.js +173 -0
  806. package/dist/accessi-module/security/jwtTokenOptions.js.map +1 -0
  807. package/dist/accessi-module/security/loginAs.d.ts +8 -0
  808. package/dist/accessi-module/security/loginAs.js +45 -0
  809. package/dist/accessi-module/security/loginAs.js.map +1 -0
  810. package/dist/accessi-module/security/passwordPolicy.d.ts +21 -0
  811. package/dist/accessi-module/security/passwordPolicy.js +71 -0
  812. package/dist/accessi-module/security/passwordPolicy.js.map +1 -0
  813. package/dist/accessi-module/security/passwordResetToken.d.ts +13 -3
  814. package/dist/accessi-module/security/passwordResetToken.js +13 -5
  815. package/dist/accessi-module/security/passwordResetToken.js.map +1 -1
  816. package/dist/accessi-module/security/publicAuthRateLimit.d.ts +10 -8
  817. package/dist/accessi-module/security/publicAuthRateLimit.js +71 -60
  818. package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
  819. package/dist/accessi-module/security/rateLimitStore.d.ts +34 -0
  820. package/dist/accessi-module/security/rateLimitStore.js +147 -0
  821. package/dist/accessi-module/security/rateLimitStore.js.map +1 -0
  822. package/dist/accessi-module/security/refreshTokenRotation.d.ts +80 -0
  823. package/dist/accessi-module/security/refreshTokenRotation.js +206 -0
  824. package/dist/accessi-module/security/refreshTokenRotation.js.map +1 -0
  825. package/dist/accessi-module/security/securityHeaders.d.ts +89 -0
  826. package/dist/accessi-module/security/securityHeaders.js +155 -0
  827. package/dist/accessi-module/security/securityHeaders.js.map +1 -0
  828. package/dist/accessi-module/security/serviceTokenGuard.d.ts +27 -0
  829. package/dist/accessi-module/security/serviceTokenGuard.js +90 -0
  830. package/dist/accessi-module/security/serviceTokenGuard.js.map +1 -0
  831. package/dist/accessi-module/security/serviceTokenRateLimit.d.ts +9 -0
  832. package/dist/accessi-module/security/serviceTokenRateLimit.js +56 -0
  833. package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -0
  834. package/dist/accessi-module/security/sessionRevocation.d.ts +67 -0
  835. package/dist/accessi-module/security/sessionRevocation.js +174 -0
  836. package/dist/accessi-module/security/sessionRevocation.js.map +1 -0
  837. package/dist/accessi-module/security/validateOptions.d.ts +19 -0
  838. package/dist/accessi-module/security/validateOptions.js +176 -0
  839. package/dist/accessi-module/security/validateOptions.js.map +1 -0
  840. package/dist/accessi-module/startupLog.d.ts +7 -0
  841. package/dist/accessi-module/startupLog.js +29 -0
  842. package/dist/accessi-module/startupLog.js.map +1 -0
  843. package/dist/allegati-module/AllegatiModule.d.ts +11 -0
  844. package/dist/allegati-module/AllegatiModule.js +11 -1
  845. package/dist/allegati-module/AllegatiModule.js.map +1 -1
  846. package/dist/allegati-module/Controllers/AllegatiController.d.ts +2 -1
  847. package/dist/allegati-module/Controllers/AllegatiController.js +23 -15
  848. package/dist/allegati-module/Controllers/AllegatiController.js.map +1 -1
  849. package/dist/allegati-module/Dtos/AllegatoDto.js.map +1 -1
  850. package/dist/allegati-module/Dtos/BaseResponse.js.map +1 -1
  851. package/dist/allegati-module/Dtos/UploadSingleFileRequest.js.map +1 -1
  852. package/dist/allegati-module/Dtos/index.js.map +1 -1
  853. package/dist/allegati-module/Dtos/responses/DownloadAllegatoResponseDto.js.map +1 -1
  854. package/dist/allegati-module/Dtos/responses/GetListResponse.js.map +1 -1
  855. package/dist/allegati-module/Dtos/responses/UploadAllegatoResponseDto.js.map +1 -1
  856. package/dist/allegati-module/Services/AllegatiService/AllegatiService.d.ts +10 -3
  857. package/dist/allegati-module/Services/AllegatiService/AllegatiService.js +150 -101
  858. package/dist/allegati-module/Services/AllegatiService/AllegatiService.js.map +1 -1
  859. package/dist/allegati-module/index.js +2 -2
  860. package/dist/allegati-module/index.js.map +1 -1
  861. package/dist/allegati-module/security/allegatiAuthorizationGuard.d.ts +14 -0
  862. package/dist/allegati-module/security/allegatiAuthorizationGuard.js +70 -0
  863. package/dist/allegati-module/security/allegatiAuthorizationGuard.js.map +1 -0
  864. package/dist/angular-components/aree-disponibili/aree-disponibili.js +1336 -0
  865. package/dist/angular-components/aree-disponibili/demo.html +232 -0
  866. package/dist/autobind.d.ts +10 -3
  867. package/dist/autobind.js +24 -9
  868. package/dist/autobind.js.map +1 -1
  869. package/dist/emilsoftware-utilities-2.0.0-dev.80.tgz +0 -0
  870. package/dist/firebird-compat.d.ts +41 -0
  871. package/dist/firebird-compat.js +54 -3
  872. package/dist/firebird-compat.js.map +1 -1
  873. package/dist/index.d.ts +11 -1
  874. package/dist/index.js +11 -1
  875. package/dist/index.js.map +1 -1
  876. package/dist/initEmilsoftwareModule.d.ts +11 -1
  877. package/dist/initEmilsoftwareModule.js +66 -14
  878. package/dist/initEmilsoftwareModule.js.map +1 -1
  879. package/dist/openapi/OpenApiConfig.d.ts +68 -0
  880. package/dist/openapi/OpenApiConfig.js +157 -0
  881. package/dist/openapi/OpenApiConfig.js.map +1 -0
  882. package/dist/openapi/OpenApiMerge.d.ts +15 -0
  883. package/dist/openapi/OpenApiMerge.js +119 -0
  884. package/dist/openapi/OpenApiMerge.js.map +1 -0
  885. package/dist/scripts/generateAccessiOpenApi.d.ts +1 -0
  886. package/dist/scripts/generateAccessiOpenApi.js +57 -0
  887. package/dist/scripts/generateAccessiOpenApi.js.map +1 -0
  888. package/dist/scripts/generateAccessiWiki.d.ts +1 -0
  889. package/dist/scripts/generateAccessiWiki.js +131 -0
  890. package/dist/scripts/generateAccessiWiki.js.map +1 -0
  891. package/dist/scripts/startAccessiService.d.ts +1 -0
  892. package/dist/scripts/startAccessiService.js +11 -0
  893. package/dist/scripts/startAccessiService.js.map +1 -0
  894. package/docs/accessi-openapi-baseline.json +4500 -0
  895. package/docs/accessi-openapi-breaking-allowlist.json +1 -0
  896. package/docs/accessi-openapi-fingerprint.json +192 -0
  897. package/docs/adr/0001-split-package.md +79 -0
  898. package/docs/adr/0002-multi-tenancy.md +154 -0
  899. package/docs/audit.md +136 -0
  900. package/docs/auth-cookies.md +123 -0
  901. package/docs/gateway-rate-limit.md +98 -0
  902. package/docs/import-migration.md +65 -0
  903. package/docs/jwt-key-rotation.md +143 -0
  904. package/docs/metrics.md +153 -0
  905. package/docs/multi-tenancy.md +305 -0
  906. package/docs/proxy-ip.md +99 -0
  907. package/docs/redis-load-testing.md +88 -0
  908. package/docs/security-headers.md +151 -0
  909. package/docs/standalone-service.md +201 -0
  910. package/integration-prompt.md +776 -0
  911. package/package.json +237 -83
  912. package/dist/swagger/SwaggerConfig.d.ts +0 -2
  913. package/dist/swagger/SwaggerConfig.js +0 -26
  914. package/dist/swagger/SwaggerConfig.js.map +0 -1
@@ -0,0 +1,4173 @@
1
+ // src/accessi-module/Console/accessiFetch.ts
2
+ var accessToken = null;
3
+ var baseUrl = null;
4
+ var tokenProvider = null;
5
+ var scopedConfig = null;
6
+ var loadingEventName = "accessi-console-network";
7
+ var ACCESSI_UNAUTHORIZED_EVENT = "accessi-console-unauthorized";
8
+ var AccessiRequestError = class extends Error {
9
+ constructor(message, status, code3, details) {
10
+ super(message);
11
+ this.status = status;
12
+ this.code = code3;
13
+ this.details = details;
14
+ this.name = "AccessiRequestError";
15
+ }
16
+ };
17
+ function notifyNetworkActivity(active) {
18
+ if (typeof window !== "undefined") {
19
+ window.dispatchEvent(new CustomEvent(loadingEventName, { detail: { active } }));
20
+ }
21
+ }
22
+ function setAccessiConsoleToken(token2) {
23
+ accessToken = token2;
24
+ }
25
+ function payloadString(payload, key) {
26
+ if (!payload || typeof payload !== "object") return void 0;
27
+ const value = payload[key];
28
+ return typeof value === "string" && value.length > 0 ? value : void 0;
29
+ }
30
+ function readBrowserCookie(name) {
31
+ if (typeof document === "undefined" || !document.cookie) return null;
32
+ for (const part of document.cookie.split(";")) {
33
+ const separator = part.indexOf("=");
34
+ if (separator < 0) continue;
35
+ if (part.slice(0, separator).trim() !== name) continue;
36
+ try {
37
+ return decodeURIComponent(part.slice(separator + 1).trim());
38
+ } catch {
39
+ return part.slice(separator + 1).trim();
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ function isCookieAuthMode() {
45
+ return typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
46
+ }
47
+ var UNSAFE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
48
+ async function accessiFetch(url, options) {
49
+ const config = scopedConfig;
50
+ const cookieMode2 = isCookieAuthMode();
51
+ const headers = new Headers(options.headers);
52
+ headers.set("Accept", "application/json");
53
+ if (options.body !== void 0 && !headers.has("Content-Type")) {
54
+ headers.set("Content-Type", "application/json");
55
+ }
56
+ if (cookieMode2) {
57
+ const method = (options.method ?? "GET").toUpperCase();
58
+ if (UNSAFE_METHODS.has(method)) {
59
+ const csrfCookie = window.__ACCESSI_CSRF_COOKIE_NAME__ ?? "accessi_csrf";
60
+ const csrfHeader = window.__ACCESSI_CSRF_HEADER_NAME__ ?? "x-csrf-token";
61
+ const csrfToken = readBrowserCookie(csrfCookie);
62
+ if (csrfToken) headers.set(csrfHeader, csrfToken);
63
+ }
64
+ }
65
+ const resolvedToken = config ? config.getToken ? await config.getToken() : config.token ?? null : tokenProvider ? await tokenProvider() : accessToken;
66
+ const isAuthEntryRequest = /\/(?:auth\/(?:login|refresh|two-factor\/(?:verify|resend))|console\/entry\/exchange)\b/.test(url);
67
+ const authenticatedRequest = !isAuthEntryRequest && (cookieMode2 || resolvedToken !== null && resolvedToken !== void 0);
68
+ if (resolvedToken) headers.set("Authorization", `Bearer ${resolvedToken}`);
69
+ const runtimeBaseUrl = config?.baseUrl !== void 0 ? config.baseUrl : baseUrl ?? (typeof window !== "undefined" ? window.__ACCESSI_API_BASE_URL__ ?? null : null);
70
+ const requestUrl = runtimeBaseUrl && url.startsWith("/") ? `${runtimeBaseUrl.replace(/\/+$/, "")}${url}` : url;
71
+ notifyNetworkActivity(true);
72
+ try {
73
+ const response = await fetch(requestUrl, {
74
+ ...options,
75
+ headers,
76
+ ...cookieMode2 ? { credentials: "include" } : {}
77
+ });
78
+ const payload = await response.json().catch(() => void 0);
79
+ if (!response.ok) {
80
+ const message = payloadString(payload, "message") ?? payloadString(payload, "error") ?? `Errore HTTP ${response.status}`;
81
+ const rawDetails = payload && typeof payload === "object" ? payload.details : void 0;
82
+ const details = Array.isArray(rawDetails) ? rawDetails.map((item) => String(item)) : void 0;
83
+ if (response.status === 401 && authenticatedRequest && typeof window !== "undefined") {
84
+ window.dispatchEvent(new CustomEvent(ACCESSI_UNAUTHORIZED_EVENT));
85
+ }
86
+ throw new AccessiRequestError(message, response.status, payloadString(payload, "code") ?? payloadString(payload, "error"), details);
87
+ }
88
+ return {
89
+ data: payload,
90
+ status: response.status,
91
+ headers: response.headers
92
+ };
93
+ } finally {
94
+ notifyNetworkActivity(false);
95
+ }
96
+ }
97
+
98
+ // src/accessi-module/Console/generated/emilsoftware-utilities.ts
99
+ var getGetUserByTokenUrl = () => {
100
+ return `/api/accessi/auth/get-user-by-token`;
101
+ };
102
+ var getUserByToken = async (getUserByTokenRequest, options) => {
103
+ return accessiFetch(
104
+ getGetUserByTokenUrl(),
105
+ {
106
+ ...options,
107
+ method: "POST",
108
+ body: JSON.stringify(
109
+ getUserByTokenRequest
110
+ )
111
+ }
112
+ );
113
+ };
114
+ var getRevokeTokenUrl = () => {
115
+ return `/api/accessi/auth/revoke-token`;
116
+ };
117
+ var revokeToken = async (revokeTokenRequest, options) => {
118
+ return accessiFetch(
119
+ getRevokeTokenUrl(),
120
+ {
121
+ ...options,
122
+ method: "POST",
123
+ body: JSON.stringify(
124
+ revokeTokenRequest
125
+ )
126
+ }
127
+ );
128
+ };
129
+ var getLoginUrl = () => {
130
+ return `/api/accessi/auth/login`;
131
+ };
132
+ var login = async (loginRequest, options) => {
133
+ return accessiFetch(
134
+ getLoginUrl(),
135
+ {
136
+ ...options,
137
+ method: "POST",
138
+ body: JSON.stringify(
139
+ loginRequest
140
+ )
141
+ }
142
+ );
143
+ };
144
+ var getVerifyTwoFactorUrl = () => {
145
+ return `/api/accessi/auth/two-factor/verify`;
146
+ };
147
+ var verifyTwoFactor = async (verifyTwoFactorRequest, options) => {
148
+ return accessiFetch(
149
+ getVerifyTwoFactorUrl(),
150
+ {
151
+ ...options,
152
+ method: "POST",
153
+ body: JSON.stringify(
154
+ verifyTwoFactorRequest
155
+ )
156
+ }
157
+ );
158
+ };
159
+ var getResendTwoFactorUrl = () => {
160
+ return `/api/accessi/auth/two-factor/resend`;
161
+ };
162
+ var resendTwoFactor = async (resendTwoFactorRequest, options) => {
163
+ return accessiFetch(
164
+ getResendTwoFactorUrl(),
165
+ {
166
+ ...options,
167
+ method: "POST",
168
+ body: JSON.stringify(
169
+ resendTwoFactorRequest
170
+ )
171
+ }
172
+ );
173
+ };
174
+ var getBootstrapAdminUrl = () => {
175
+ return `/api/accessi/auth/bootstrap-admin`;
176
+ };
177
+ var bootstrapAdmin = async (bootstrapAdminRequest, options) => {
178
+ return accessiFetch(
179
+ getBootstrapAdminUrl(),
180
+ {
181
+ ...options,
182
+ method: "POST",
183
+ body: JSON.stringify(
184
+ bootstrapAdminRequest
185
+ )
186
+ }
187
+ );
188
+ };
189
+ var getGetAdminBootstrapStatusUrl = () => {
190
+ return `/api/accessi/auth/bootstrap-admin/status`;
191
+ };
192
+ var getAdminBootstrapStatus = async (options) => {
193
+ return accessiFetch(
194
+ getGetAdminBootstrapStatusUrl(),
195
+ {
196
+ ...options,
197
+ method: "GET"
198
+ }
199
+ );
200
+ };
201
+ var getGetRolesUrl = () => {
202
+ return `/api/accessi/permission/roles`;
203
+ };
204
+ var getRoles = async (options) => {
205
+ return accessiFetch(
206
+ getGetRolesUrl(),
207
+ {
208
+ ...options,
209
+ method: "GET"
210
+ }
211
+ );
212
+ };
213
+ var getUpdateRoleUrl = (codiceRuolo) => {
214
+ return `/api/accessi/permission/update-role/${codiceRuolo}`;
215
+ };
216
+ var updateRole = async (codiceRuolo, role, options) => {
217
+ return accessiFetch(
218
+ getUpdateRoleUrl(codiceRuolo),
219
+ {
220
+ ...options,
221
+ method: "PUT",
222
+ body: JSON.stringify(
223
+ role
224
+ )
225
+ }
226
+ );
227
+ };
228
+ var getCreateRoleUrl = () => {
229
+ return `/api/accessi/permission/create-role`;
230
+ };
231
+ var createRole = async (role, options) => {
232
+ return accessiFetch(
233
+ getCreateRoleUrl(),
234
+ {
235
+ ...options,
236
+ method: "POST",
237
+ body: JSON.stringify(
238
+ role
239
+ )
240
+ }
241
+ );
242
+ };
243
+ var getAssignRolesToUserUrl = (codiceUtente) => {
244
+ return `/api/accessi/permission/assign-roles/${codiceUtente}`;
245
+ };
246
+ var assignRolesToUser = async (codiceUtente, assignRolesToUserRequest, options) => {
247
+ return accessiFetch(
248
+ getAssignRolesToUserUrl(codiceUtente),
249
+ {
250
+ ...options,
251
+ method: "POST",
252
+ body: JSON.stringify(
253
+ assignRolesToUserRequest
254
+ )
255
+ }
256
+ );
257
+ };
258
+ var getAssignPermissionsToUserUrl = (codiceUtente) => {
259
+ return `/api/accessi/permission/assign-permissions/${codiceUtente}`;
260
+ };
261
+ var assignPermissionsToUser = async (codiceUtente, assignPermissionsToUserRequest, options) => {
262
+ return accessiFetch(
263
+ getAssignPermissionsToUserUrl(codiceUtente),
264
+ {
265
+ ...options,
266
+ method: "POST",
267
+ body: JSON.stringify(
268
+ assignPermissionsToUserRequest
269
+ )
270
+ }
271
+ );
272
+ };
273
+ var getDeleteRoleUrl = (codiceRuolo) => {
274
+ return `/api/accessi/permission/delete-role/${codiceRuolo}`;
275
+ };
276
+ var deleteRole = async (codiceRuolo, options) => {
277
+ return accessiFetch(
278
+ getDeleteRoleUrl(codiceRuolo),
279
+ {
280
+ ...options,
281
+ method: "DELETE"
282
+ }
283
+ );
284
+ };
285
+ var getCreateMenuUrl = () => {
286
+ return `/api/accessi/permission/menus`;
287
+ };
288
+ var createMenu = async (createMenuRequest, options) => {
289
+ return accessiFetch(
290
+ getCreateMenuUrl(),
291
+ {
292
+ ...options,
293
+ method: "POST",
294
+ body: JSON.stringify(
295
+ createMenuRequest
296
+ )
297
+ }
298
+ );
299
+ };
300
+ var getGetGroupsWithMenusUrl = (params) => {
301
+ const normalizedParams = new URLSearchParams();
302
+ Object.entries(params || {}).forEach(([key, value]) => {
303
+ if (value === null) {
304
+ normalizedParams.append(key, "null");
305
+ } else if (value !== void 0) {
306
+ normalizedParams.append(key, value.toString());
307
+ }
308
+ });
309
+ return `/api/accessi/permission/groups-with-menus?${normalizedParams.toString()}`;
310
+ };
311
+ var getGroupsWithMenus = async (params, options) => {
312
+ return accessiFetch(
313
+ getGetGroupsWithMenusUrl(params),
314
+ {
315
+ ...options,
316
+ method: "GET"
317
+ }
318
+ );
319
+ };
320
+ var getGetMenuTypesUrl = () => {
321
+ return `/api/accessi/permission/menu-types`;
322
+ };
323
+ var getMenuTypes = async (options) => {
324
+ return accessiFetch(
325
+ getGetMenuTypesUrl(),
326
+ {
327
+ ...options,
328
+ method: "GET"
329
+ }
330
+ );
331
+ };
332
+ var getCreateMenuTypeUrl = () => {
333
+ return `/api/accessi/permission/menu-types`;
334
+ };
335
+ var createMenuType = async (createMenuTypeRequest, options) => {
336
+ return accessiFetch(
337
+ getCreateMenuTypeUrl(),
338
+ {
339
+ ...options,
340
+ method: "POST",
341
+ body: JSON.stringify(
342
+ createMenuTypeRequest
343
+ )
344
+ }
345
+ );
346
+ };
347
+ var getUpdateMenuUrl = (codiceMenu) => {
348
+ return `/api/accessi/permission/menus/${codiceMenu}`;
349
+ };
350
+ var updateMenu = async (codiceMenu, updateMenuRequest, options) => {
351
+ return accessiFetch(
352
+ getUpdateMenuUrl(codiceMenu),
353
+ {
354
+ ...options,
355
+ method: "PUT",
356
+ body: JSON.stringify(
357
+ updateMenuRequest
358
+ )
359
+ }
360
+ );
361
+ };
362
+ var getDeleteMenuUrl = (codiceMenu) => {
363
+ return `/api/accessi/permission/menus/${codiceMenu}`;
364
+ };
365
+ var deleteMenu = async (codiceMenu, options) => {
366
+ return accessiFetch(
367
+ getDeleteMenuUrl(codiceMenu),
368
+ {
369
+ ...options,
370
+ method: "DELETE"
371
+ }
372
+ );
373
+ };
374
+ var getCreateMenuGroupUrl = () => {
375
+ return `/api/accessi/permission/menu-groups`;
376
+ };
377
+ var createMenuGroup = async (createMenuGroupRequest, options) => {
378
+ return accessiFetch(
379
+ getCreateMenuGroupUrl(),
380
+ {
381
+ ...options,
382
+ method: "POST",
383
+ body: JSON.stringify(
384
+ createMenuGroupRequest
385
+ )
386
+ }
387
+ );
388
+ };
389
+ var getUpdateMenuGroupUrl = (codiceGruppo) => {
390
+ return `/api/accessi/permission/menu-groups/${codiceGruppo}`;
391
+ };
392
+ var updateMenuGroup = async (codiceGruppo, updateMenuGroupRequest, options) => {
393
+ return accessiFetch(
394
+ getUpdateMenuGroupUrl(codiceGruppo),
395
+ {
396
+ ...options,
397
+ method: "PUT",
398
+ body: JSON.stringify(
399
+ updateMenuGroupRequest
400
+ )
401
+ }
402
+ );
403
+ };
404
+ var getDeleteMenuGroupUrl = (codiceGruppo) => {
405
+ return `/api/accessi/permission/menu-groups/${codiceGruppo}`;
406
+ };
407
+ var deleteMenuGroup = async (codiceGruppo, options) => {
408
+ return accessiFetch(
409
+ getDeleteMenuGroupUrl(codiceGruppo),
410
+ {
411
+ ...options,
412
+ method: "DELETE"
413
+ }
414
+ );
415
+ };
416
+ var getUpdateMenuTypeUrl = (codiceTipo) => {
417
+ return `/api/accessi/permission/menu-types/${codiceTipo}`;
418
+ };
419
+ var updateMenuType = async (codiceTipo, updateMenuTypeRequest, options) => {
420
+ return accessiFetch(
421
+ getUpdateMenuTypeUrl(codiceTipo),
422
+ {
423
+ ...options,
424
+ method: "PUT",
425
+ body: JSON.stringify(
426
+ updateMenuTypeRequest
427
+ )
428
+ }
429
+ );
430
+ };
431
+ var getDeleteMenuTypeUrl = (codiceTipo) => {
432
+ return `/api/accessi/permission/menu-types/${codiceTipo}`;
433
+ };
434
+ var deleteMenuType = async (codiceTipo, options) => {
435
+ return accessiFetch(
436
+ getDeleteMenuTypeUrl(codiceTipo),
437
+ {
438
+ ...options,
439
+ method: "DELETE"
440
+ }
441
+ );
442
+ };
443
+ var getGetUserRolesAndGrantsUrl = (codiceUtente, params) => {
444
+ const normalizedParams = new URLSearchParams();
445
+ Object.entries(params || {}).forEach(([key, value]) => {
446
+ if (value === null) {
447
+ normalizedParams.append(key, "null");
448
+ } else if (value !== void 0) {
449
+ normalizedParams.append(key, value.toString());
450
+ }
451
+ });
452
+ return `/api/accessi/permission/grants/${codiceUtente}?${normalizedParams.toString()}`;
453
+ };
454
+ var getUserRolesAndGrants = async (codiceUtente, params, options) => {
455
+ return accessiFetch(
456
+ getGetUserRolesAndGrantsUrl(codiceUtente, params),
457
+ {
458
+ ...options,
459
+ method: "GET"
460
+ }
461
+ );
462
+ };
463
+ var getGetScopesUrl = () => {
464
+ return `/api/accessi/permission/scopes`;
465
+ };
466
+ var getScopes = async (options) => {
467
+ return accessiFetch(
468
+ getGetScopesUrl(),
469
+ {
470
+ ...options,
471
+ method: "GET"
472
+ }
473
+ );
474
+ };
475
+ var getCreateScopeUrl = () => {
476
+ return `/api/accessi/permission/scopes`;
477
+ };
478
+ var createScope = async (createScopeRequest, options) => {
479
+ return accessiFetch(
480
+ getCreateScopeUrl(),
481
+ {
482
+ ...options,
483
+ method: "POST",
484
+ body: JSON.stringify(
485
+ createScopeRequest
486
+ )
487
+ }
488
+ );
489
+ };
490
+ var getUpdateScopeUrl = (codiceScope) => {
491
+ return `/api/accessi/permission/scopes/${codiceScope}`;
492
+ };
493
+ var updateScope = async (codiceScope, updateScopeRequest, options) => {
494
+ return accessiFetch(
495
+ getUpdateScopeUrl(codiceScope),
496
+ {
497
+ ...options,
498
+ method: "PUT",
499
+ body: JSON.stringify(
500
+ updateScopeRequest
501
+ )
502
+ }
503
+ );
504
+ };
505
+ var getDeleteScopeUrl = (codiceScope) => {
506
+ return `/api/accessi/permission/scopes/${codiceScope}`;
507
+ };
508
+ var deleteScope = async (codiceScope, options) => {
509
+ return accessiFetch(
510
+ getDeleteScopeUrl(codiceScope),
511
+ {
512
+ ...options,
513
+ method: "DELETE"
514
+ }
515
+ );
516
+ };
517
+ var getGetMenuScopesUrl = (codiceMenu) => {
518
+ return `/api/accessi/permission/menus/${codiceMenu}/scopes`;
519
+ };
520
+ var getMenuScopes = async (codiceMenu, options) => {
521
+ return accessiFetch(
522
+ getGetMenuScopesUrl(codiceMenu),
523
+ {
524
+ ...options,
525
+ method: "GET"
526
+ }
527
+ );
528
+ };
529
+ var getSetScopeMenusUrl = (codiceScope) => {
530
+ return `/api/accessi/permission/scopes/${codiceScope}/menus`;
531
+ };
532
+ var setScopeMenus = async (codiceScope, setScopeMenusRequest, options) => {
533
+ return accessiFetch(
534
+ getSetScopeMenusUrl(codiceScope),
535
+ {
536
+ ...options,
537
+ method: "PUT",
538
+ body: JSON.stringify(
539
+ setScopeMenusRequest
540
+ )
541
+ }
542
+ );
543
+ };
544
+ var getGetUsersUrl = (params) => {
545
+ const normalizedParams = new URLSearchParams();
546
+ Object.entries(params || {}).forEach(([key, value]) => {
547
+ if (value === null) {
548
+ normalizedParams.append(key, "null");
549
+ } else if (value !== void 0) {
550
+ normalizedParams.append(key, value.toString());
551
+ }
552
+ });
553
+ return `/api/accessi/user/get-users?${normalizedParams.toString()}`;
554
+ };
555
+ var getUsers = async (params, options) => {
556
+ return accessiFetch(
557
+ getGetUsersUrl(params),
558
+ {
559
+ ...options,
560
+ method: "GET"
561
+ }
562
+ );
563
+ };
564
+ var getDeleteUserUrl = (codiceUtente) => {
565
+ return `/api/accessi/user/delete-user/${codiceUtente}`;
566
+ };
567
+ var deleteUser = async (codiceUtente, options) => {
568
+ return accessiFetch(
569
+ getDeleteUserUrl(codiceUtente),
570
+ {
571
+ ...options,
572
+ method: "DELETE"
573
+ }
574
+ );
575
+ };
576
+ var getDeleteUserPermanentlyUrl = (codiceUtente) => {
577
+ return `/api/accessi/user/delete-user-permanent/${codiceUtente}`;
578
+ };
579
+ var deleteUserPermanently = async (codiceUtente, options) => {
580
+ return accessiFetch(
581
+ getDeleteUserPermanentlyUrl(codiceUtente),
582
+ {
583
+ ...options,
584
+ method: "DELETE"
585
+ }
586
+ );
587
+ };
588
+ var getForcePasswordResetLegacyUrl = () => {
589
+ return `/api/accessi/user/force-password-reset-legacy`;
590
+ };
591
+ var forcePasswordResetLegacy = async (options) => {
592
+ return accessiFetch(
593
+ getForcePasswordResetLegacyUrl(),
594
+ {
595
+ ...options,
596
+ method: "POST"
597
+ }
598
+ );
599
+ };
600
+ var getCreateLoginAsUrlUrl = (codiceUtente) => {
601
+ return `/api/accessi/user/login-as/${codiceUtente}`;
602
+ };
603
+ var createLoginAsUrl = async (codiceUtente, loginAsRequest, options) => {
604
+ return accessiFetch(
605
+ getCreateLoginAsUrlUrl(codiceUtente),
606
+ {
607
+ ...options,
608
+ method: "POST",
609
+ body: JSON.stringify(
610
+ loginAsRequest
611
+ )
612
+ }
613
+ );
614
+ };
615
+ var getSetUserPasswordUrl = (codiceUtente) => {
616
+ return `/api/accessi/user/set-password/${codiceUtente}`;
617
+ };
618
+ var setUserPassword = async (codiceUtente, setUserPasswordRequest, options) => {
619
+ return accessiFetch(
620
+ getSetUserPasswordUrl(codiceUtente),
621
+ {
622
+ ...options,
623
+ method: "POST",
624
+ body: JSON.stringify(
625
+ setUserPasswordRequest
626
+ )
627
+ }
628
+ );
629
+ };
630
+ var getSetStatoRegistrazioneUrl = () => {
631
+ return `/api/accessi/user/set-stato`;
632
+ };
633
+ var setStatoRegistrazione = async (setStatoRegistrazioneDto, options) => {
634
+ return accessiFetch(
635
+ getSetStatoRegistrazioneUrl(),
636
+ {
637
+ ...options,
638
+ method: "POST",
639
+ body: JSON.stringify(
640
+ setStatoRegistrazioneDto
641
+ )
642
+ }
643
+ );
644
+ };
645
+ var getCreateManagedUserUrl = () => {
646
+ return `/api/accessi/user/create-managed-user`;
647
+ };
648
+ var createManagedUser = async (registerRequest, options) => {
649
+ return accessiFetch(
650
+ getCreateManagedUserUrl(),
651
+ {
652
+ ...options,
653
+ method: "POST",
654
+ body: JSON.stringify(
655
+ registerRequest
656
+ )
657
+ }
658
+ );
659
+ };
660
+ var getUpdateUtenteUrl = (codiceUtente) => {
661
+ return `/api/accessi/user/update-user/${codiceUtente}`;
662
+ };
663
+ var updateUtente = async (codiceUtente, userDto, options) => {
664
+ return accessiFetch(
665
+ getUpdateUtenteUrl(codiceUtente),
666
+ {
667
+ ...options,
668
+ method: "PUT",
669
+ body: JSON.stringify(
670
+ userDto
671
+ )
672
+ }
673
+ );
674
+ };
675
+ var getSetGdprUrl = (codiceUtente) => {
676
+ return `/api/accessi/user/set-gdpr/${codiceUtente}`;
677
+ };
678
+ var setGdpr = async (codiceUtente, options) => {
679
+ return accessiFetch(
680
+ getSetGdprUrl(codiceUtente),
681
+ {
682
+ ...options,
683
+ method: "PATCH"
684
+ }
685
+ );
686
+ };
687
+ var getSetMenuEnabledUrl = (codiceMenu) => {
688
+ return `/api/accessi/configurator/menus/${codiceMenu}/enabled`;
689
+ };
690
+ var setMenuEnabled = async (codiceMenu, updateEnabledStatusRequest, options) => {
691
+ return accessiFetch(
692
+ getSetMenuEnabledUrl(codiceMenu),
693
+ {
694
+ ...options,
695
+ method: "PATCH",
696
+ body: JSON.stringify(
697
+ updateEnabledStatusRequest
698
+ )
699
+ }
700
+ );
701
+ };
702
+ var getSetGroupEnabledUrl = (codiceGruppo) => {
703
+ return `/api/accessi/configurator/groups/${codiceGruppo}/enabled`;
704
+ };
705
+ var setGroupEnabled = async (codiceGruppo, updateEnabledStatusRequest, options) => {
706
+ return accessiFetch(
707
+ getSetGroupEnabledUrl(codiceGruppo),
708
+ {
709
+ ...options,
710
+ method: "PATCH",
711
+ body: JSON.stringify(
712
+ updateEnabledStatusRequest
713
+ )
714
+ }
715
+ );
716
+ };
717
+ var getGetFederatedProvidersUrl = () => {
718
+ return `/api/accessi/federated-auth/providers`;
719
+ };
720
+ var getFederatedProviders = async (options) => {
721
+ return accessiFetch(
722
+ getGetFederatedProvidersUrl(),
723
+ {
724
+ ...options,
725
+ method: "GET"
726
+ }
727
+ );
728
+ };
729
+ var getCreateFederatedProviderUrl = () => {
730
+ return `/api/accessi/federated-auth/providers`;
731
+ };
732
+ var createFederatedProvider = async (createFederatedProviderRequest, options) => {
733
+ return accessiFetch(
734
+ getCreateFederatedProviderUrl(),
735
+ {
736
+ ...options,
737
+ method: "POST",
738
+ body: JSON.stringify(
739
+ createFederatedProviderRequest
740
+ )
741
+ }
742
+ );
743
+ };
744
+ var getUpdateFederatedProviderUrl = (provider) => {
745
+ return `/api/accessi/federated-auth/providers/${provider}`;
746
+ };
747
+ var updateFederatedProvider = async (provider, updateFederatedProviderRequest, options) => {
748
+ return accessiFetch(
749
+ getUpdateFederatedProviderUrl(provider),
750
+ {
751
+ ...options,
752
+ method: "PATCH",
753
+ body: JSON.stringify(
754
+ updateFederatedProviderRequest
755
+ )
756
+ }
757
+ );
758
+ };
759
+ var getDeleteFederatedProviderUrl = (provider) => {
760
+ return `/api/accessi/federated-auth/providers/${provider}`;
761
+ };
762
+ var deleteFederatedProvider = async (provider, options) => {
763
+ return accessiFetch(
764
+ getDeleteFederatedProviderUrl(provider),
765
+ {
766
+ ...options,
767
+ method: "DELETE"
768
+ }
769
+ );
770
+ };
771
+ var getGetFederatedIdentitiesUrl = (codiceUtente) => {
772
+ return `/api/accessi/federated-auth/users/${codiceUtente}/identities`;
773
+ };
774
+ var getFederatedIdentities = async (codiceUtente, options) => {
775
+ return accessiFetch(
776
+ getGetFederatedIdentitiesUrl(codiceUtente),
777
+ {
778
+ ...options,
779
+ method: "GET"
780
+ }
781
+ );
782
+ };
783
+ var getLinkFederatedIdentityUrl = (codiceUtente) => {
784
+ return `/api/accessi/federated-auth/users/${codiceUtente}/identities`;
785
+ };
786
+ var linkFederatedIdentity = async (codiceUtente, createFederatedIdentityRequest, options) => {
787
+ return accessiFetch(
788
+ getLinkFederatedIdentityUrl(codiceUtente),
789
+ {
790
+ ...options,
791
+ method: "POST",
792
+ body: JSON.stringify(
793
+ createFederatedIdentityRequest
794
+ )
795
+ }
796
+ );
797
+ };
798
+ var getCreateFederatedUserUrl = () => {
799
+ return `/api/accessi/federated-auth/users`;
800
+ };
801
+ var createFederatedUser = async (createFederatedUserRequest, options) => {
802
+ return accessiFetch(
803
+ getCreateFederatedUserUrl(),
804
+ {
805
+ ...options,
806
+ method: "POST",
807
+ body: JSON.stringify(
808
+ createFederatedUserRequest
809
+ )
810
+ }
811
+ );
812
+ };
813
+ var getUpdateFederatedIdentityUrl = (identityKey) => {
814
+ return `/api/accessi/federated-auth/identities/${identityKey}`;
815
+ };
816
+ var updateFederatedIdentity = async (identityKey, updateFederatedIdentityRequest, options) => {
817
+ return accessiFetch(
818
+ getUpdateFederatedIdentityUrl(identityKey),
819
+ {
820
+ ...options,
821
+ method: "PATCH",
822
+ body: JSON.stringify(
823
+ updateFederatedIdentityRequest
824
+ )
825
+ }
826
+ );
827
+ };
828
+ var getDeleteFederatedIdentityPermanentlyUrl = (codiceUtente, identityKey) => {
829
+ return `/api/accessi/federated-auth/users/${codiceUtente}/identities/${identityKey}`;
830
+ };
831
+ var deleteFederatedIdentityPermanently = async (codiceUtente, identityKey, options) => {
832
+ return accessiFetch(
833
+ getDeleteFederatedIdentityPermanentlyUrl(codiceUtente, identityKey),
834
+ {
835
+ ...options,
836
+ method: "DELETE"
837
+ }
838
+ );
839
+ };
840
+ var getExchangeConsoleEntryTicketUrl = () => {
841
+ return `/api/accessi/console/entry/exchange`;
842
+ };
843
+ var exchangeConsoleEntryTicket = async (consoleEntryExchangeRequest, options) => {
844
+ return accessiFetch(
845
+ getExchangeConsoleEntryTicketUrl(),
846
+ {
847
+ ...options,
848
+ method: "POST",
849
+ body: JSON.stringify(
850
+ consoleEntryExchangeRequest
851
+ )
852
+ }
853
+ );
854
+ };
855
+ var getCreateServiceTokenUrl = () => {
856
+ return `/api/accessi/service-token`;
857
+ };
858
+ var createServiceToken = async (createServiceTokenRequest, options) => {
859
+ return accessiFetch(
860
+ getCreateServiceTokenUrl(),
861
+ {
862
+ ...options,
863
+ method: "POST",
864
+ body: JSON.stringify(
865
+ createServiceTokenRequest
866
+ )
867
+ }
868
+ );
869
+ };
870
+ var getGetServiceTokensUrl = (params) => {
871
+ const normalizedParams = new URLSearchParams();
872
+ Object.entries(params || {}).forEach(([key, value]) => {
873
+ if (value === null) {
874
+ normalizedParams.append(key, "null");
875
+ } else if (value !== void 0) {
876
+ normalizedParams.append(key, value.toString());
877
+ }
878
+ });
879
+ return `/api/accessi/service-token?${normalizedParams.toString()}`;
880
+ };
881
+ var getServiceTokens = async (params, options) => {
882
+ return accessiFetch(
883
+ getGetServiceTokensUrl(params),
884
+ {
885
+ ...options,
886
+ method: "GET"
887
+ }
888
+ );
889
+ };
890
+ var getRevokeServiceTokenUrl = (tokenId) => {
891
+ return `/api/accessi/service-token/${tokenId}`;
892
+ };
893
+ var revokeServiceToken = async (tokenId, options) => {
894
+ return accessiFetch(
895
+ getRevokeServiceTokenUrl(tokenId),
896
+ {
897
+ ...options,
898
+ method: "DELETE"
899
+ }
900
+ );
901
+ };
902
+ var getRotateServiceTokenUrl = (tokenId) => {
903
+ return `/api/accessi/service-token/${tokenId}/rotate`;
904
+ };
905
+ var rotateServiceToken = async (tokenId, options) => {
906
+ return accessiFetch(
907
+ getRotateServiceTokenUrl(tokenId),
908
+ {
909
+ ...options,
910
+ method: "POST"
911
+ }
912
+ );
913
+ };
914
+ var getGetSchemaUrl = () => {
915
+ return `/api/accessi/schema`;
916
+ };
917
+ var getSchema = async (options) => {
918
+ return accessiFetch(
919
+ getGetSchemaUrl(),
920
+ {
921
+ ...options,
922
+ method: "GET"
923
+ }
924
+ );
925
+ };
926
+
927
+ // src/accessi-module/Console/wiki.ts
928
+ var p = (text) => ({ kind: "p", text });
929
+ var h = (text) => ({ kind: "h", text });
930
+ var list = (items, ordered = false) => ({ kind: "list", items, ordered });
931
+ var code = (language, codeText, title, variant) => ({ kind: "code", language, title, code: codeText, variant });
932
+ var good = (language, codeText, title) => code(language, codeText, title, "good");
933
+ var bad = (language, codeText, title) => code(language, codeText, title, "bad");
934
+ var note = (tone, title, text) => ({ kind: "callout", tone, title, text });
935
+ var table = (head, rows) => ({ kind: "table", head, rows });
936
+ var WIKI_SECTIONS = [
937
+ // -------------------------------------------------------------------------
938
+ {
939
+ id: "panoramica",
940
+ group: "Per iniziare",
941
+ title: "Panoramica e architettura",
942
+ summary: "Che cosa fa Accessi, come si incastra nel backend e come scorre una richiesta.",
943
+ blocks: [
944
+ p(`Accessi \xE8 un modulo Node/NestJS che aggiunge a un backend gi\xE0 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 \xE8 Firebird/InterBase.`),
945
+ h(`Che cosa possiede il modulo`),
946
+ list([
947
+ `Le rotte sotto /api/accessi/* (login, utenti, ruoli, menu, SSO, token, console).`,
948
+ `Lo schema database del dominio Accessi, riconciliato automaticamente al bootstrap.`,
949
+ `Il middleware di autorizzazione authorizeAccessi per le rotte della tua applicazione.`,
950
+ `La console web su /api/accessi/console.`
951
+ ]),
952
+ h(`Che cosa resta alla tua applicazione`),
953
+ list([
954
+ `Il proprio frontend e le proprie rotte di business.`,
955
+ `La validazione dei token dei provider SSO esterni (Accessi non li vede).`,
956
+ `I segreti: jwtOptions.secret, SMTP, database ed encryptionKey non raggiungono mai il browser.`
957
+ ]),
958
+ h(`Flusso di una richiesta protetta`),
959
+ list([
960
+ `Il client chiama una rotta della tua app con header Authorization: Bearer <jwt>.`,
961
+ `La tua rotta esegue authorizeAccessi, che verifica firma HS256, scadenza e stato utente corrente.`,
962
+ `Se passi un requisito, il modulo carica ruoli e grant dell'utente e li valuta.`,
963
+ `Su successo req.user, req.data e (se richiesto) req.userGrants sono popolati; altrimenti risponde con il contratto errore Accessi.`
964
+ ], true),
965
+ note("info", `Due modalit\xE0 di bootstrap`, `Express esistente: initializeAccessiModule(app, options). NestJS esistente: AccessiModule.forRoot(options) nei tuoi imports. Non usarle entrambe.`)
966
+ ]
967
+ },
968
+ // -------------------------------------------------------------------------
969
+ {
970
+ id: "installazione",
971
+ group: "Per iniziare",
972
+ title: "Installazione",
973
+ summary: "Dipendenze, peer e build.",
974
+ blocks: [
975
+ code("bash", `npm install emilsoftware-utilities`, "Installazione"),
976
+ p(`La libreria dipende da node-firebird per il database e da Express 5 come peer per il montaggio. \xC8 consigliato Node 18 o superiore.`),
977
+ p(`Il pacchetto \xE8 pubblicato in CommonJS con export map esplicita per ogni entry point (accessi/server, accessi/resource-server/*, accessi/client/*, accessi/contracts*, accessi/openapi, accessi/console, accessi/ui): gli import ESM funzionano tramite interop Node. sideEffects elenca solo root barrel, firebird-compat e bundle console, cos\xEC il resto \xE8 tree-shakable.`),
978
+ note("warn", `Un solo modulo per processo`, `AccessiModule \xE8 @Global(): inizializzalo una sola volta per processo. In caso di pi\xF9 istanze, usa un processo per istanza.`)
979
+ ]
980
+ },
981
+ // -------------------------------------------------------------------------
982
+ {
983
+ id: "bootstrap-express",
984
+ group: "Per iniziare",
985
+ title: "Bootstrap su Express esistente",
986
+ summary: "initializeAccessiModule: ordine corretto e attesa prima di listen.",
987
+ blocks: [
988
+ p(`Chiama initializeAccessiModule prima di aprire la porta HTTP. Durante il bootstrap, authorizeAccessi risponde 503 ACCESSI_AUTH_INITIALIZING invece di autorizzare richieste parziali.`),
989
+ good("ts", [
990
+ "import express from 'express';",
991
+ "import { initializeAccessiModule, authorizeAccessi } from 'emilsoftware-utilities';",
992
+ "",
993
+ "const app = express();",
994
+ "app.use(express.json());",
995
+ "",
996
+ "await initializeAccessiModule(app, options); // prima di app.listen",
997
+ "",
998
+ "app.get('/api/ordini', authorizeAccessi, (req, res) => {",
999
+ " res.json({ utente: req.user });",
1000
+ "});",
1001
+ "",
1002
+ "app.get('/api/ordini/scrivi', (req, res, next) =>",
1003
+ " authorizeAccessi(req, res, next, {",
1004
+ " requirements: [{ menuCode: 'ORDINI', minPermissionLevel: 20 }],",
1005
+ " }),",
1006
+ "(req, res) => res.json({ ok: true }));",
1007
+ "",
1008
+ "app.listen(3000);"
1009
+ ].join("\n"), "Express + Accessi"),
1010
+ bad("ts", [
1011
+ "const app = express();",
1012
+ "app.listen(3000); // porta aperta prima del bootstrap",
1013
+ "await initializeAccessiModule(app, options); // le richieste nel frattempo ricevono 503"
1014
+ ].join("\n"), "Non aprire la porta prima del bootstrap"),
1015
+ h(`Rotte montate`),
1016
+ p(`Accessi intercetta solo /api/accessi/* e i path OpenAPI configurati (default /accessi/openapi* e /accessi/openapi.json). Tutte le altre rotte passano intatte al tuo stack Express.`),
1017
+ note("ok", `req.user`, `authorizeAccessi popola req.user con il payload utente corrente (utente, codiceUtente, flag) e req.data come alias storico. req.userGrants \xE8 presente solo se hai richiesto dei requisiti.`)
1018
+ ]
1019
+ },
1020
+ // -------------------------------------------------------------------------
1021
+ {
1022
+ id: "bootstrap-nest",
1023
+ group: "Per iniziare",
1024
+ title: "Bootstrap su NestJS esistente",
1025
+ summary: "AccessiModule.forRoot e uso dei provider esportati.",
1026
+ blocks: [
1027
+ p(`Se la tua applicazione \xE8 gi\xE0 NestJS, importa AccessiModule.forRoot(options). Il modulo \xE8 @Global e riesporta i servizi (AuthService, UserService, PermissionService, FederatedAuthService, ServiceTokenService, ServiceTokenGuard, AuthenticateGenService).`),
1028
+ good("ts", [
1029
+ "import { Module } from '@nestjs/common';",
1030
+ "import { AccessiModule } from 'emilsoftware-utilities';",
1031
+ "",
1032
+ "@Module({",
1033
+ " imports: [AccessiModule.forRoot(options)],",
1034
+ "})",
1035
+ "export class AppModule {}"
1036
+ ].join("\n"), "app.module.ts"),
1037
+ 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. Per health e readiness usare createAccessiHealthModule({ accessiOptions }): espone /health/live e /health/ready con lo stesso contratto del servizio standalone (bootstrap, database, store Redis e check aggiuntivi).`)
1038
+ ]
1039
+ },
1040
+ // -------------------------------------------------------------------------
1041
+ {
1042
+ id: "distribuzione",
1043
+ group: "Per iniziare",
1044
+ title: "Distribuzione multi-app e package entry point",
1045
+ summary: "Come usare Accessi come servizio centrale, come resource server o solo come contratti.",
1046
+ blocks: [
1047
+ p(`Accessi non va montato per forza in ogni backend. La distribuzione consigliata separa il servizio che possiede il database e la console dai backend fratelli che devono solo autenticare le richieste.`),
1048
+ table(
1049
+ ["Entry point", "Uso", "Dipendenze server"],
1050
+ [
1051
+ ["accessi/server", "Bootstrap completo: DB, SMTP, migrazioni, console, API.", "s\xEC"],
1052
+ ["accessi/resource-server", "Guard/middleware per backend fratelli: verifica JWT e autorizzazione remota.", "no"],
1053
+ ["accessi/client", "Client OpenAPI generato per chiamare Accessi Service.", "no"],
1054
+ ["accessi/client/public", "Solo API pubbliche (login, refresh, 2FA, reset password, registrazione, ticket console).", "no"],
1055
+ ["accessi/client/admin", "Solo API amministrative (utenti, ruoli, menu, filtri, provider federati, schema).", "no"],
1056
+ ["accessi/client/service", "Solo API service-to-service (service token e introspezione), mai nei frontend.", "no"],
1057
+ ["accessi/contracts", "Tipi, enum e helper puri browser-safe.", "no"],
1058
+ ["accessi/contracts/dtos", "DTO Nest con class-validator e decoratori OpenAPI.", "no"],
1059
+ ["accessi/openapi", "setupOpenApi e merge del documento OpenAPI.", "no"],
1060
+ ["accessi/console", "Helper per URL console, ticket e 2FA handoff.", "no"],
1061
+ ["accessi/ui", "Percorsi degli asset Web Component frameworkless.", "no"]
1062
+ ]
1063
+ ),
1064
+ p(`Topologia consigliata: un solo Accessi Service possiede database, encryptionKey, SMTP, chiavi JWT e console. Gli altri backend usano resource-server e client, senza credenziali del database Accessi.`),
1065
+ p(`Multi-tenancy: un database per tenant, configurato in una cartella di file <id>.env. Il database resta invariato (nessuna colonna tenant); ogni tenant gira in un processo separato con chiavi, issuer e store propri. Validazione e avvio con npm run accessi:tenants; dettagli in docs/multi-tenancy.md.`),
1066
+ note("warn", `Root barrel legacy`, `Il root barrel e i deep import restano supportati fino alla prossima major. I nuovi consumer devono usare gli entry point della tabella; la mappa di migrazione e in docs/import-migration.md.`),
1067
+ p(`Per avviare Accessi come servizio autonomo usare startAccessiService: espone /health/live, /health/ready, /health/metrics, valida la configurazione e gestisce SIGTERM/SIGINT.`),
1068
+ p(`Per parlare con piu Accessi Service o tenant dallo stesso processo usare createAccessiClient: ogni istanza ha base URL e token propri, isolati anche tra richieste concorrenti. configureAccessiClient resta il default globale per le funzioni esportate dal package.`),
1069
+ good("ts", [
1070
+ "// Backend che ospita Accessi",
1071
+ "import { initializeAccessiModule } from 'emilsoftware-utilities/accessi/server';",
1072
+ "",
1073
+ "// Backend fratello: nessun AccessiModule, nessun DB Accessi",
1074
+ "import { createAccessiAuthMiddleware } from 'emilsoftware-utilities/accessi/resource-server';",
1075
+ "import { createAccessiClient } from 'emilsoftware-utilities/accessi/client';",
1076
+ "",
1077
+ "const milano = createAccessiClient({ baseUrl: 'https://accessi-milano.example.com', getToken });",
1078
+ "const roma = createAccessiClient({ baseUrl: 'https://accessi-roma.example.com', token: tokenRoma });",
1079
+ "const [utentiMilano, utentiRoma] = await Promise.all([milano.getUsers(), roma.getUsers()]);"
1080
+ ].join("\n"), "Import corretti per ruolo"),
1081
+ note("warn", `Un solo proprietario`, `Non montare AccessiModule in due backend puntati allo stesso database: migrazioni, bootstrap admin, ticket console e rate limit diventano ambigui.`),
1082
+ note("info", `Client per scope`, `Usare accessi/client/public nei frontend pubblici, accessi/client/admin nella console e nei tool amministrativi, accessi/client/service solo nei backend fratelli. Gli scope partizionano il client completo e condividono gli stessi tipi; il client completo resta disponibile per compatibilita.`),
1083
+ note("info", `Split in pacchetti`, `Il package resta unico con entry point per scope: decisione, soglie per rivalutarla e piano di migrazione sono nell'ADR docs/adr/0001-split-package.md.`),
1084
+ note("info", `IP reali dietro proxy`, `Imposta http.trustProxy sul numero di proxy davanti al servizio (1 per Nginx/Traefik/ALB, 2 con Cloudflare + ingress) e lascialo false se il servizio e esposto direttamente. Rate limit e audit usano req.ip: dettagli in docs/proxy-ip.md.`),
1085
+ note("info", `Prompt di integrazione`, `Il file integration-prompt.md nella root del repository contiene il contesto completo per frontend, backend e agenti AI.`)
1086
+ ]
1087
+ },
1088
+ // -------------------------------------------------------------------------
1089
+ {
1090
+ id: "opzioni",
1091
+ group: "Configurazione",
1092
+ title: "AccessiOptions (riferimento completo)",
1093
+ summary: "Ogni campo di configurazione con tipo, obbligo e note.",
1094
+ blocks: [
1095
+ table(
1096
+ ["Campo", "Tipo", "Obbligo", "Note"],
1097
+ [
1098
+ ["databaseOptions", "Options (node-firebird)", "s\xEC", "Connessione Firebird al database Accessi."],
1099
+ ["confirmationEmailUrl", "string", "s\xEC", "Base URL usata per il reset password."],
1100
+ ["confirmationEmailReturnUrl", "string", "s\xEC", "URL di ritorno dopo il reset."],
1101
+ ["confirmationEmailPrefix", "string", "no", "Prefisso passato alla pagina di reset."],
1102
+ ["customResetPage", "string", "no", "Sostituisce la pagina HTML di reset inclusa."],
1103
+ ["encryptionKey", "string", "s\xEC", "Compatibilit\xE0 e migrazione delle password legacy."],
1104
+ ["mockDemoUser", "boolean", "s\xEC", "Utenti fittizi admin e demo solo fuori produzione."],
1105
+ ["passwordExpiration", "boolean", "no", "Attiva il controllo della scadenza password al login."],
1106
+ ["passwordExpirationDays", "number", "no", "Valore predefinito: 90 giorni."],
1107
+ ["legacyPasswordMigrationOnStartup", "boolean", "no", "Predefinito true: converte le password legacy."],
1108
+ ["autoUpdateDatabase", "boolean", "no", "Predefinito true: riconcilia lo schema al boot."],
1109
+ ["jwtOptions", "{ secret, expiresIn, refreshExpiresIn, algorithm, issuer, audience, privateKey, publicKey, keyId, jwksPath, previousPublicKeys, onUnknownKeyId }", "s\xEC", "Firma, durata, refresh token, JWKS e rotazione chiavi. Default HS256; RS/ES consigliati per resource server multi-servizio."],
1110
+ ["emailOptions", "EmailOptions", "s\xEC", "SMTP per reset e codici email."],
1111
+ ["publicAuthRateLimit", "PublicAuthRateLimitOptions", "no", "Limiti per endpoint pubblici; store condiviso opzionale (Redis) per il cluster."],
1112
+ ["edgeRateLimit", "AccessiEdgeRateLimitOptions", "no", "Limite grossolano per IP davanti alle rotte Accessi; opzionale, store condiviso supportato."],
1113
+ ["http", "{ cors, trustProxy, securityHeaders }", "no", "CORS, IP reali dietro proxy e header di sicurezza con CSP a nonce per la console e direttive configurabili."],
1114
+ ["authCookie", "{ enabled, secure, sameSite, csrf, ... }", "no", "Cookie HttpOnly e CSRF double-submit al posto del Bearer. Default disattivato."],
1115
+ ["metrics", "{ enabled, sink, prefix }", "no", "Metriche Prometheus di dominio (login, 2FA, refresh, rate limit, store) e sink esterno opzionale."],
1116
+ ["audit", "{ enabled, sinks, store, retentionDays, redact }", "no", "Audit persistente redatto, retention e query/export admin. Default: solo log strutturato."],
1117
+ ["publicRegistration", "{ enabled }", "no", "Registrazione pubblica: disattivata per impostazione predefinita."],
1118
+ ["federatedAuthentication", "FederatedAuthenticationOptions", "no", `Abilita l'SSO generico.`],
1119
+ ["extensionFieldsOptions", "ExtensionFieldsOptions[]", "no", "Tabelle esterne allegate al profilo."],
1120
+ ["serviceTokens", "{ enabled, defaultTtlDays, rateLimitStore }", "no", "Token macchina-a-macchina, abilitati per impostazione predefinita; rate limit condiviso opzionale."],
1121
+ ["catalogScripts", "{ enabled, folder, ... }", "no", "Catalog migrations SQL eseguite dopo la riconciliazione dello schema."],
1122
+ ["adminBootstrap", "{ enabled, superUser }", "no", "Token casuale a ogni avvio per creare il primo admin dalla console."],
1123
+ ["publicBaseUrl", "string", "no", "Base URL usata solo per loggare console/OpenAPI/API."],
1124
+ ["consoleApiBaseUrl", "string", "no", "Base URL API per la console quando UI e API sono su domini diversi."],
1125
+ ["openapi", "OpenApiSetupOptions", "no", "Path, titolo, descrizione e enabled della documentazione OpenAPI. Default invariati."],
1126
+ ["consoleEntry", "{ ttlMs, store }", "no", "TTL e store condiviso dei ticket console (Redis per multi-istanza). Default in memoria."],
1127
+ ["sessionRevocation", "{ enabled, store, ttlMs }", "no", "Revoca sessioni per utente tramite store memory/Redis; abilita POST /api/accessi/auth/revoke-sessions."],
1128
+ ["accessTokenRevocation", "{ enabled, store, ttlMs }", "no", "Revoca dei singoli access token tramite jti (memory/Redis); abilita POST /api/accessi/auth/revoke-token."],
1129
+ ["refreshTokenRotation", "{ enabled, store, reuseGraceMs, revokeUserSessionsOnReuse }", "no", "Refresh token monouso tramite store memory/Redis; al riuso oltre la finestra di grazia revoca la famiglia di token. Consigliato in multi-istanza."],
1130
+ ["logger", "LoggerService", "no", "Logger Nest del sotto-app; se assente Nest usa il proprio logger."],
1131
+ ["bufferLogs", "boolean", "no", "Buffer dei log di bootstrap del sotto-app. Default true."]
1132
+ ]
1133
+ ),
1134
+ good("ts", [
1135
+ "import type { AccessiOptions } from 'emilsoftware-utilities';",
1136
+ "",
1137
+ "export const options: AccessiOptions = {",
1138
+ " databaseOptions: {",
1139
+ " host: process.env.ACCESSI_DB_HOST!,",
1140
+ " port: Number(process.env.ACCESSI_DB_PORT ?? 3050),",
1141
+ " database: process.env.ACCESSI_DB_NAME!,",
1142
+ " user: process.env.ACCESSI_DB_USER!,",
1143
+ " password: process.env.ACCESSI_DB_PASSWORD!,",
1144
+ " },",
1145
+ " confirmationEmailUrl: 'https://app.example.com',",
1146
+ " confirmationEmailReturnUrl: 'https://app.example.com/login',",
1147
+ " encryptionKey: process.env.ACCESSI_ENCRYPTION_KEY!,",
1148
+ " mockDemoUser: false,",
1149
+ " passwordExpiration: true,",
1150
+ " passwordExpirationDays: 90,",
1151
+ " jwtOptions: {",
1152
+ " secret: process.env.ACCESSI_JWT_SECRET!, // almeno 32 caratteri casuali, unico per ambiente",
1153
+ " expiresIn: '24h',",
1154
+ " issuer: 'accessi',",
1155
+ " audience: 'backend-app',",
1156
+ " },",
1157
+ " emailOptions: {",
1158
+ " host: process.env.SMTP_HOST!,",
1159
+ " port: Number(process.env.SMTP_PORT ?? 587),",
1160
+ " secure: false,",
1161
+ " requireTLS: true,",
1162
+ " tls: { rejectUnauthorized: true },",
1163
+ " from: 'no-reply@example.com',",
1164
+ " auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },",
1165
+ " },",
1166
+ " publicRegistration: { enabled: false },",
1167
+ " serviceTokens: { enabled: true, defaultTtlDays: 365 },",
1168
+ "};"
1169
+ ].join("\n"), "Configurazione tipica"),
1170
+ p(`options.openapi personalizza path, titolo e descrizione della documentazione OpenAPI. Con enabled: false UI e JSON non vengono montati, ma le API /api/accessi restano attive. initializeAccessiModule restituisce { nestApp, document } per mergiare il contratto nel documento OpenAPI unico dell'host.`),
1171
+ good("ts", [
1172
+ "const { document } = await initializeAccessiModule(app, {",
1173
+ " ...options,",
1174
+ " openapi: { openApiPath: 'auth/docs', openApiJsonPath: 'auth/docs.json', title: 'Auth API' },",
1175
+ "});",
1176
+ "// document.paths e document.components.schemas sono pronti per il merge"
1177
+ ].join("\n"), "OpenAPI personalizzato"),
1178
+ bad("ts", [
1179
+ "jwtOptions: { secret: 'accessi', expiresIn: '24h' }, // segreto debole e condiviso",
1180
+ "mockDemoUser: true, // MAI in produzione"
1181
+ ].join("\n"), "Errori comuni"),
1182
+ 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.`)
1183
+ ]
1184
+ },
1185
+ // -------------------------------------------------------------------------
1186
+ {
1187
+ id: "database",
1188
+ group: "Configurazione",
1189
+ title: "Schema e aggiornamento del database",
1190
+ summary: "Tabelle, riconciliazione automatica e CLI.",
1191
+ blocks: [
1192
+ p(`Accessi possiede lo schema del dominio. Con autoUpdateDatabase true (predefinito) lo riconcilia a ogni avvio; con false verifica comunque la compatibilit\xE0 in sola lettura e blocca gli avvii incompatibili.`),
1193
+ h(`Tabelle principali`),
1194
+ list([
1195
+ `PARAMETRI, UTENTI, UTENTI_CONFIG, UTENTI_PWD, UTENTI_OLDPWD, UTENTI_GDPR`,
1196
+ `RUOLI, MENU_GRP, MENU_TIPI, MENU, ABILITAZIONI, RUOLI_MNU, UTENTI_RUOLI`,
1197
+ `FILTRI, FILTRI_TIPO`,
1198
+ `SSO_PROVIDER, UTENTI_IDENTITA_EXT`,
1199
+ `ACCESSI_2FA, ACCESSI_SERVICE_TOKEN, ACCESSI_CATALOG_SCRIPT`
1200
+ ]),
1201
+ code("bash", `npm run db:update:accessi # riconcilia lo schema
1202
+ npm run db:check:accessi # verifica senza modificare`, "CLI schema"),
1203
+ 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.`),
1204
+ 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.`)
1205
+ ]
1206
+ },
1207
+ // -------------------------------------------------------------------------
1208
+ {
1209
+ id: "catalog-migrations",
1210
+ group: "Configurazione",
1211
+ title: "Catalog migrations SQL",
1212
+ summary: "Allinea menu, ruoli e cataloghi su piu database in modo idempotente.",
1213
+ blocks: [
1214
+ 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.`),
1215
+ h(`Configurazione`),
1216
+ good("ts", [
1217
+ "catalogScripts: {",
1218
+ " enabled: true,",
1219
+ " folder: './db/accessi-catalog', // versionata nel repo del backend",
1220
+ " onChecksumMismatch: 'error', // 'error' (default) | 'warn' | 'reapply'",
1221
+ " stopOnError: true,",
1222
+ "}"
1223
+ ].join("\n"), "AccessiOptions"),
1224
+ list([
1225
+ "Gli script sono ordinati per percorso (usa prefissi numerici: 0001_, 0002_, ...).",
1226
+ "Devono essere idempotenti e ripetibili: UPDATE OR INSERT ... MATCHING, MERGE, DELETE+INSERT mirati.",
1227
+ "Non includere utenti: le catalog migrations allineano solo catalogo e configurazione.",
1228
+ "Supportati stringhe, commenti e statement multipli; SET TERM non serve perch\xE9 gli script sono solo DML."
1229
+ ]),
1230
+ 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.`),
1231
+ 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).`),
1232
+ 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.`),
1233
+ code("bash", `# Applica (schema + catalog migrations) su un database
1234
+ ACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi
1235
+
1236
+ # Elenca solo quelle da applicare / modificate
1237
+ ACCESSI_CATALOG_FOLDER=./db/accessi-catalog npm run db:catalog:accessi:check`, "CLI catalog migrations"),
1238
+ 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.`)
1239
+ ]
1240
+ },
1241
+ // -------------------------------------------------------------------------
1242
+ {
1243
+ id: "login",
1244
+ group: "Autenticazione",
1245
+ title: "Login locale e JWT",
1246
+ summary: "Endpoint di login, risposta e gestione della password scaduta.",
1247
+ blocks: [
1248
+ code("http", `POST /api/accessi/auth/login
1249
+ Content-Type: application/json
1250
+
1251
+ { "email": "utente@example.com", "password": "Segreta123!" }`, "Login con password"),
1252
+ 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).`),
1253
+ note("info", `Scadenza del token`, `Result.token.expiresIn e sempre espresso in secondi (per esempio 86400 per 24h), indipendentemente dal formato usato in jwtOptions.expiresIn.`),
1254
+ 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.`),
1255
+ good("ts", [
1256
+ "const res = await fetch('/api/accessi/auth/login', {",
1257
+ " method: 'POST',",
1258
+ " headers: { 'Content-Type': 'application/json' },",
1259
+ " body: JSON.stringify({ email, password }),",
1260
+ "});",
1261
+ "const body = await res.json();",
1262
+ "if (body.Result?.challenge) { /* richiedi il codice */ }",
1263
+ "const token = body.Result?.token;"
1264
+ ].join("\n"), "Chiamata dal frontend"),
1265
+ 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.`),
1266
+ h(`Verifica del token`),
1267
+ code("http", `POST /api/accessi/auth/get-user-by-token
1268
+ Content-Type: application/json
1269
+
1270
+ { "token": "<jwt>" }`, "get-user-by-token"),
1271
+ p(`Usalo per verificare un JWT e ottenere il profilo corrente (utile al bootstrap di una SPA). Restituisce Result.userData.`)
1272
+ ]
1273
+ },
1274
+ // -------------------------------------------------------------------------
1275
+ {
1276
+ id: "passwordless-2fa",
1277
+ group: "Autenticazione",
1278
+ title: "Codici email, 2FA e passwordless",
1279
+ summary: "Verifica in due passi, challenge e policy per utente.",
1280
+ blocks: [
1281
+ p(`La verifica con codice email \xE8 per-utente (flagDueFattori / UTENTI_CONFIG.FLG2FATT) ed \xE8 disattivata per impostazione predefinita. Con passwordlessLoginEnabled (FLGPWDLESS, richiede la 2FA attiva) l'accesso avviene con il solo codice email.`),
1282
+ table(
1283
+ ["Configurazione", "Accesso locale", "Accesso SSO"],
1284
+ [
1285
+ ["2FA disattivata", "Password", "Provider"],
1286
+ ["2FA attiva", "Password + codice email", "Provider + codice email"],
1287
+ ["2FA + passwordless", "Solo codice email", "Provider + codice email"]
1288
+ ]
1289
+ ),
1290
+ code("json", `{
1291
+ "Result": {
1292
+ "challenge": {
1293
+ "twoFactorRequired": true,
1294
+ "challengeId": "64-caratteri-esadecimali",
1295
+ "method": "email",
1296
+ "expiresAt": "2026-09-11T14:00:00.000Z",
1297
+ "resendAfterSeconds": 60
1298
+ }
1299
+ }
1300
+ }`, "Risposta con challenge"),
1301
+ code("http", `POST /api/accessi/auth/two-factor/verify
1302
+ { "challengeId": "...", "code": "012345" }
1303
+
1304
+ POST /api/accessi/auth/two-factor/resend
1305
+ { "challengeId": "..." }`, "Verifica e reinvio"),
1306
+ list([
1307
+ `Il codice ha 6 cifre, dura 10 minuti, consente 5 tentativi totali e al massimo 3 invii per challenge.`,
1308
+ `Fino alla verifica non sono presenti token, profilo o grant.`,
1309
+ `Il challenge non \xE8 un JWT e non va usato nell'header Authorization.`
1310
+ ]),
1311
+ bad("ts", [
1312
+ "// Sbagliato: usare il challenge come token",
1313
+ "fetch('/api/ordini', { headers: { Authorization: 'Bearer ' + challengeId } }); // 401"
1314
+ ].join("\n"), "Il challenge non \xE8 un token")
1315
+ ]
1316
+ },
1317
+ // -------------------------------------------------------------------------
1318
+ {
1319
+ id: "reset-password",
1320
+ group: "Autenticazione",
1321
+ title: "Reset password",
1322
+ summary: "Invio email e conferma con token monouso.",
1323
+ blocks: [
1324
+ code("http", `POST /api/accessi/email/send-reset-password-email
1325
+ { "email": "utente@example.com" }
1326
+
1327
+ POST /api/accessi/auth/confirm-reset-password/{token}
1328
+ { "newPassword": "NuovaSegreta123!" }`, "Reset in due passi"),
1329
+ p(`L'invio non rivela se l'email esiste (anti-enumerazione). Il token di reset \xE8 monouso ed \xE8 firmato con una chiave dedicata.`),
1330
+ 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.`),
1331
+ note("info", `Pagina personalizzata`, `Imposta customResetPage per usare la tua pagina di reset invece di quella inclusa. confirmationEmailReturnUrl e confirmationEmailPrefix controllano il ritorno.`)
1332
+ ]
1333
+ },
1334
+ // -------------------------------------------------------------------------
1335
+ {
1336
+ id: "middleware",
1337
+ group: "Autorizzazione",
1338
+ title: "authorizeAccessi (middleware)",
1339
+ summary: "Proteggere le rotte della tua app con il JWT Accessi.",
1340
+ blocks: [
1341
+ p(`authorizeAccessi \xE8 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)).`),
1342
+ good("ts", [
1343
+ "app.get('/api/report',",
1344
+ " (req, res, next) => authorizeAccessi(req, res, next, {",
1345
+ " requirements: [{ menuCode: 'REPORT', minPermissionLevel: 10 }],",
1346
+ " }),",
1347
+ " (req, res) => res.json({ user: req.user, grants: req.userGrants }),",
1348
+ ");"
1349
+ ].join("\n"), "Rotta protetta"),
1350
+ h(`Che cosa trovi nella request`),
1351
+ list([
1352
+ `req.user e req.data: payload utente corrente (alias storici).`,
1353
+ `req.userGrants: grant effettivi, presente solo quando hai richiesto dei requisiti.`
1354
+ ]),
1355
+ bad("ts", [
1356
+ "app.get('/api/report', authorizeAccessi, (req, res) => {",
1357
+ " // Sbagliato: assumere che req.userGrants esista senza requisiti",
1358
+ " const grants = req.userGrants.grants; // possibile TypeError",
1359
+ "});"
1360
+ ].join("\n"), "Non dare per scontato req.userGrants"),
1361
+ note("danger", `Lo stato utente \xE8 sempre ricontrollato`, `Un JWT valido non basta: se l'utente \xE8 stato bloccato, eliminato o ha cambiato policy, il middleware risponde 401 AUTH_USER_DISABLED.`)
1362
+ ]
1363
+ },
1364
+ // -------------------------------------------------------------------------
1365
+ {
1366
+ id: "resource-server",
1367
+ group: "Autorizzazione",
1368
+ title: "Resource server e introspection",
1369
+ summary: "Proteggere un backend fratello senza accedere al database Accessi.",
1370
+ blocks: [
1371
+ p(`Un backend che non possiede Accessi usa resource-server. La verifica puo essere locale (JWT) oppure delegata all'introspection di Accessi Service. La policy di autorizzazione usa gli stessi accessiRequirement del middleware storico.`),
1372
+ good("ts", [
1373
+ "import { createAccessiAuthMiddleware } from 'emilsoftware-utilities/accessi/resource-server';",
1374
+ "",
1375
+ "app.use(createAccessiAuthMiddleware({",
1376
+ " jwt: {",
1377
+ " // In una topologia multi-servizio preferire RS256/JWKS:",
1378
+ " // publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY,",
1379
+ ' // algorithms: ["RS256"],',
1380
+ " serverSecret: process.env.ACCESSI_JWT_SECRET, // HS256: deriva la chiave access",
1381
+ ' issuer: "accessi",',
1382
+ ' audience: "backend-a",',
1383
+ " },",
1384
+ " introspection: {",
1385
+ ' url: "https://accessi.example.com/api/accessi/auth/introspect",',
1386
+ " serviceToken: process.env.ACCESSI_SERVICE_TOKEN!,",
1387
+ " },",
1388
+ "}));"
1389
+ ].join("\n"), "Express resource server"),
1390
+ good("ts", [
1391
+ "import { createAccessiAuthGuard } from 'emilsoftware-utilities/accessi/resource-server';",
1392
+ "",
1393
+ "@UseGuards(createAccessiAuthGuard({",
1394
+ ' jwt: { publicKey: process.env.ACCESSI_JWT_PUBLIC_KEY, algorithms: ["RS256"] },',
1395
+ " introspection: { url: process.env.ACCESSI_INTROSPECT_URL!, serviceToken: process.env.ACCESSI_SERVICE_TOKEN! },",
1396
+ ' authorization: { requirements: [{ menuCode: "ORDINI", minPermissionLevel: 20 }] },',
1397
+ "}))",
1398
+ '@Get("ordini")',
1399
+ "list(@Req() req: Request) { return req.user; }"
1400
+ ].join("\n"), "Guard Nest"),
1401
+ table(
1402
+ ["Campo", "Obbligo", "Note"],
1403
+ [
1404
+ ["jwt.serverSecret", "HS256", "Segreto grezzo Accessi; il resource server applica la KDF access."],
1405
+ ["jwt.publicKey / jwt.jwks", "RS/ES", "Consigliato in multi-servizio. JWKS ha cache e timeout."],
1406
+ ["jwt.issuer / jwt.audience", "no", "Consigliati per evitare token di altri ambienti."],
1407
+ ["introspection.url", "se servono grant", "Endpoint service-to-service protetto da service token."],
1408
+ ["introspection.serviceToken", "se introspection", "Scope richiesto: accessi:introspect."],
1409
+ ["authorization", "no", "Policy accessiRequirement; senza introspection i grant sono obbligatori."]
1410
+ ]
1411
+ ),
1412
+ note("warn", `Fail closed`, `Se introspection e configurata ma non raggiungibile, il resource server risponde 503. Non autorizza la richiesta.`),
1413
+ note("info", `Introspection`, `POST /api/accessi/auth/introspect restituisce active=false per token invalido, scaduto o utente disabilitato. I risultati attivi sono cachati lato resource server per 30 secondi per default.`),
1414
+ note("info", `JWKS`, `Con RS256/ES256 Accessi pubblica la JWKS su /accessi/.well-known/jwks.json (configurabile con jwtOptions.jwksPath). Il resource server puo usare jwks.url e ruotare le chiavi con previousPublicKeys.`),
1415
+ note("info", `Revoca`, `revocation.store applica la revoca per utente; revocation.accessTokenStore rifiuta i singoli jti revocati e revocation.familyStore invalida le famiglie di refresh token revocate per riuso sospetto.`),
1416
+ note("info", `Audit`, `I fallimenti di autenticazione del resource server, i blocchi edge, il riuso dei refresh token e le revoche di sessione producono eventi strutturati tramite auditAccessi; i token e i segreti vengono redatti dal Logger.`)
1417
+ ]
1418
+ },
1419
+ // -------------------------------------------------------------------------
1420
+ {
1421
+ id: "requisiti",
1422
+ group: "Autorizzazione",
1423
+ title: "Requisiti e livelli di abilitazione",
1424
+ summary: "DSL accessiRequirement: permission, and, or, not, custom.",
1425
+ blocks: [
1426
+ p(`I requisiti si compongono con accessiRequirement. La forma storica requirements \xE8 un AND di permessi. Il livello \xE8 TipoAbilitazione: NESSUNA=0, LETTURA=10, SCRITTURA=20, SPECIAL=30; il confronto \xE8 tipoAbilitazione >= soglia.`),
1427
+ good("ts", [
1428
+ "import { authorizeAccessi, accessiRequirement as r } from 'emilsoftware-utilities';",
1429
+ "",
1430
+ "// AND (entrambi i menu)",
1431
+ 'const both = r.and(r.permission("ORDINI", 20), r.permission("CLIENTI", 10));',
1432
+ "",
1433
+ "// OR",
1434
+ 'const either = r.or(r.permission("REPORT", 10), r.permission("EXPORT", 20));',
1435
+ "",
1436
+ "// NOT + custom",
1437
+ "const policy = r.and(",
1438
+ ' r.permission("AREA_RISERVATA", 10),',
1439
+ ' r.not(r.permission("BLOCCO_TOTALE", 0)),',
1440
+ ");",
1441
+ "",
1442
+ 'app.get("/api/riservato", (req, res, next) => authorizeAccessi(req, res, next, {',
1443
+ " requirementTree: policy,",
1444
+ " customRequirementHandlers: {",
1445
+ " stessoReparto: async (ctx) => ctx.userCode === Number(ctx.req.query.reparto),",
1446
+ " },",
1447
+ "}), handler);"
1448
+ ].join("\n"), "Policy composte"),
1449
+ bad("ts", [
1450
+ "// Sbagliato: AND/OR senza figli -> errore di configurazione (500 AUTH_REQUIREMENTS_MISCONFIGURED)",
1451
+ "const empty = r.and();"
1452
+ ].join("\n"), "Configurazione non valida"),
1453
+ note("warn", `custom senza handler`, `Un requisito custom la cui chiave non ha un handler produce 500 AUTH_REQUIREMENTS_MISCONFIGURED: \xE8 un errore di programmazione, non un 403.`)
1454
+ ]
1455
+ },
1456
+ // -------------------------------------------------------------------------
1457
+ {
1458
+ id: "utenti",
1459
+ group: "Dominio",
1460
+ title: "Utenti",
1461
+ summary: "Creazione, aggiornamento, stati e GDPR.",
1462
+ blocks: [
1463
+ h(`Stati di registrazione (STAREG)`),
1464
+ table(
1465
+ ["Valore", "Nome", "Effetto sul login"],
1466
+ [
1467
+ ["0", "NULL", "Non confermato"],
1468
+ ["5", "INSERT", "Non confermato"],
1469
+ ["10", "INVIO", "Richiede il rinnovo della password"],
1470
+ ["20", "CONF", "Valido"],
1471
+ ["50", "DELETE", "Bloccato (soft delete)"],
1472
+ ["99", "BLOCC", "Bloccato"]
1473
+ ]
1474
+ ),
1475
+ h(`Endpoint principali (superutente salvo diversamente indicato)`),
1476
+ table(
1477
+ ["Metodo", "Percorso", "Descrizione"],
1478
+ [
1479
+ ["GET", "/api/accessi/user/get-users", "Elenco con paginazione (limit e offset, header X-Total-Count)."],
1480
+ ["POST", "/api/accessi/user/create-managed-user", "Crea un utente locale e invia il reset password."],
1481
+ ["PUT", "/api/accessi/user/update-user/:codiceUtente", "Aggiorna profilo e policy (campi privilegiati solo superutente)."],
1482
+ ["DELETE", "/api/accessi/user/delete-user/:codiceUtente", "Soft delete: STAREG=DELETE. Admin o superutente."],
1483
+ ["DELETE", "/api/accessi/user/delete-user-permanent/:codiceUtente", "Eliminazione definitiva: rimuove utente e record collegati. Solo admin."],
1484
+ ["POST", "/api/accessi/user/set-password/:codiceUtente", "Imposta direttamente la password: hash lato backend e scadenza secondo policy, nessuna email."],
1485
+ ["POST", "/api/accessi/user/set-stato", "Imposta lo stato di registrazione."],
1486
+ ["PATCH", "/api/accessi/user/set-gdpr/:codiceUtente", "Registra il consenso GDPR."],
1487
+ ["POST", "/api/accessi/user/register", "Registrazione pubblica, solo se publicRegistration.enabled."]
1488
+ ]
1489
+ ),
1490
+ 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. 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.`),
1491
+ note("ok", `Creazione con invito atomica`, `La creazione utente con invio email \xE8 atomica: se SMTP non \xE8 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.`),
1492
+ 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.`),
1493
+ 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.`),
1494
+ good("ts", [
1495
+ "await fetch('/api/accessi/user/create-managed-user', {",
1496
+ " method: 'POST',",
1497
+ " headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + adminToken },",
1498
+ " body: JSON.stringify({",
1499
+ " email: 'nuovo@example.com',",
1500
+ " nome: 'Mario',",
1501
+ " cognome: 'Rossi',",
1502
+ " roles: [1, 2],",
1503
+ " permissions: [{ codiceMenu: 'REPORT', tipoAbilitazione: 10 }],",
1504
+ " }),",
1505
+ "});"
1506
+ ].join("\n"), "Creazione di un utente gestito"),
1507
+ bad("ts", [
1508
+ "// Sbagliato: inviare flag privilegiati dal frontend pubblico",
1509
+ 'fetch("/api/accessi/user/register", {',
1510
+ ' method: "POST",',
1511
+ " body: JSON.stringify({ email, flagSuper: true }), // ignorato o negato",
1512
+ "});"
1513
+ ].join("\n"), "Privilegi dal client"),
1514
+ 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.`),
1515
+ 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.`),
1516
+ 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.`)
1517
+ ]
1518
+ },
1519
+ // -------------------------------------------------------------------------
1520
+ {
1521
+ id: "ruoli",
1522
+ group: "Dominio",
1523
+ title: "Ruoli e permessi",
1524
+ summary: "Assegnazioni sostitutive e composizione dei grant.",
1525
+ blocks: [
1526
+ 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.`),
1527
+ table(
1528
+ ["Metodo", "Percorso", "Descrizione"],
1529
+ [
1530
+ ["GET", "/api/accessi/permission/roles", "Ruoli con i menu associati."],
1531
+ ["POST", "/api/accessi/permission/create-role", "Crea un ruolo (i menu sono obbligatori)."],
1532
+ ["PUT", "/api/accessi/permission/update-role/:codiceRuolo", "Aggiorna un ruolo (menu sostitutivi)."],
1533
+ ["DELETE", "/api/accessi/permission/delete-role/:codiceRuolo", "Elimina ruolo, mapping e assegnazioni."],
1534
+ ["POST", "/api/accessi/permission/assign-roles/:codiceUtente", `Sostituisce i ruoli dell'utente.`],
1535
+ ["POST", "/api/accessi/permission/assign-permissions/:codiceUtente", "Sostituisce i permessi diretti."],
1536
+ ["GET", "/api/accessi/permission/grants/:codiceUtente", "Grant effettivi, permessi diretti e ruoli."]
1537
+ ]
1538
+ ),
1539
+ good("ts", [
1540
+ "const role = {",
1541
+ " descrizioneRuolo: 'Gestore ordini',",
1542
+ " menu: [",
1543
+ " { codiceMenu: 'ORDINI', tipoAbilitazione: 20 },",
1544
+ " { codiceMenu: 'CLIENTI', tipoAbilitazione: 10 },",
1545
+ " ],",
1546
+ "};",
1547
+ "await createRole(role); // POST /api/accessi/permission/create-role",
1548
+ "",
1549
+ "// Sostitutivo: questa lista diventa l'intero set di ruoli dell'utente",
1550
+ "await assignRolesToUser(codiceUtente, { roles: [3, 7] });"
1551
+ ].join("\n"), "Creazione di un ruolo e assegnazione"),
1552
+ bad("ts", [
1553
+ "// Sbagliato: pensare che l'assegnazione sia incrementale",
1554
+ "await assignRolesToUser(codiceUtente, { roles: [3] });",
1555
+ "// ora l'utente ha SOLO il ruolo 3, non 3 pi\xF9 quelli di prima"
1556
+ ].join("\n"), "Assegnazione non additiva"),
1557
+ note("warn", `Menu obbligatori`, `create-role e update-role rifiutano un ruolo senza menu. Se ti serve un ruolo senza accessi, non crearlo.`)
1558
+ ]
1559
+ },
1560
+ // -------------------------------------------------------------------------
1561
+ {
1562
+ id: "menu",
1563
+ group: "Dominio",
1564
+ title: "Menu, gruppi e tipi menu",
1565
+ summary: "Catalogo di navigazione e CRUD completo (superutente).",
1566
+ blocks: [
1567
+ table(
1568
+ ["Metodo", "Percorso", "Descrizione"],
1569
+ [
1570
+ ["GET", "/api/accessi/permission/menus", "Menu attivi."],
1571
+ ["GET", "/api/accessi/permission/menu-types", "Catalogo dei tipi menu."],
1572
+ ["GET", "/api/accessi/permission/groups-with-menus?includeDisabled=true", "Gruppi con menu (anche disabilitati)."],
1573
+ ["POST", "/api/accessi/permission/menus", "Crea un menu."],
1574
+ ["PUT", "/api/accessi/permission/menus/:codiceMenu", "Aggiorna un menu."],
1575
+ ["DELETE", "/api/accessi/permission/menus/:codiceMenu", "Elimina un menu (con grant e associazioni ai ruoli)."],
1576
+ ["POST", "/api/accessi/permission/menu-groups", "Crea un gruppo."],
1577
+ ["PUT", "/api/accessi/permission/menu-groups/:codiceGruppo", "Aggiorna un gruppo."],
1578
+ ["DELETE", "/api/accessi/permission/menu-groups/:codiceGruppo", "Elimina un gruppo (solo se vuoto)."],
1579
+ ["POST/PUT/DELETE", "/api/accessi/permission/menu-types[/:codiceTipo]", "CRUD dei tipi menu."]
1580
+ ]
1581
+ ),
1582
+ 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 \xE8 referenziato.`),
1583
+ good("ts", [
1584
+ "await createMenuGroup({ codiceGruppo: 'A', descrizioneGruppo: 'Amministrazione', ordineGruppo: 1, enabled: true });",
1585
+ "await createMenuType({ codiceTipo: 'X', descrizioneTipo: 'Operativo' });",
1586
+ "await createMenu({ codiceMenu: 'ORDINI', descrizioneMenu: 'Ordini', codiceGruppo: 'A', tipo: 'X', ordineMenu: 10, enabled: true });"
1587
+ ].join("\n"), "Catalogo in ordine"),
1588
+ bad("ts", [
1589
+ "// Sbagliato: eliminare un gruppo che contiene menu -> 400 con spiegazione",
1590
+ "await deleteMenuGroup('A'); // contiene N menu"
1591
+ ].join("\n"), "Vincoli di integrit\xE0")
1592
+ ]
1593
+ },
1594
+ // -------------------------------------------------------------------------
1595
+ {
1596
+ id: "sso",
1597
+ group: "SSO",
1598
+ title: "SSO generico (federated authentication)",
1599
+ summary: "Il backend valida il provider, Accessi gestisce identit\xE0 e sessioni.",
1600
+ blocks: [
1601
+ p(`Accessi non valida i token Azure, Google o SAML: \xE8 il TUO backend a validare il flusso SSO e a passare solo { provider, subject } verificati a FederatedAuthService. Cos\xEC issuer, secret e token restano fuori dal modulo.`),
1602
+ good("ts", [
1603
+ "import { FederatedAuthService } from 'emilsoftware-utilities';",
1604
+ "",
1605
+ "// Nel tuo callback SSO, dopo aver validato il token del provider:",
1606
+ 'const verified = { provider: "azure-ad-acme", subject: claims.oid };',
1607
+ "const result = await federatedAuthService.authenticate(verified);",
1608
+ "",
1609
+ "if (result.login.challenge) {",
1610
+ " // 2FA attiva: restituisci il challenge al frontend",
1611
+ " return res.json({ challenge: result.login.challenge });",
1612
+ "}",
1613
+ "return res.json({ login: result.login, token: result.token });"
1614
+ ].join("\n"), "Login SSO"),
1615
+ note("info", `Scambio service-to-service`, `Se il backend fratello valida il provider SSO, pu\xF2 scambiare { provider, subject } con POST /api/accessi/federated-auth/exchange usando un service token con scope accessi:federated:exchange. Accessi non riceve il token del provider.`),
1616
+ h(`API amministrative (superutente)`),
1617
+ list([
1618
+ `GET/POST /api/accessi/federated-auth/providers, PATCH/DELETE providers/:provider`,
1619
+ `GET/POST /api/accessi/federated-auth/users/:codiceUtente/identities`,
1620
+ `POST /api/accessi/federated-auth/users (crea un utente SSO-only)`,
1621
+ `PATCH /api/accessi/federated-auth/users/:codiceUtente/password-login`
1622
+ ]),
1623
+ bad("ts", [
1624
+ "// Sbagliato: far validare il token del provider ad Accessi o fidarsi del frontend",
1625
+ "await federatedAuthService.authenticate({ provider, subject: req.body.email }); // subject non verificato"
1626
+ ].join("\n"), `Non fidarti dell'input`),
1627
+ note("warn", `Provider in uso`, `Un provider si elimina solo se nessuna identit\xE0 \xE8 collegata; altrimenti disabilitalo. La chiave del provider deve coincidere con la configurazione del backend.`)
1628
+ ]
1629
+ },
1630
+ // -------------------------------------------------------------------------
1631
+ {
1632
+ id: "service-token",
1633
+ group: "Token di servizio",
1634
+ title: "Token macchina-a-macchina",
1635
+ summary: "Autenticazione tecnica con scope: non \xE8 un superutente.",
1636
+ blocks: [
1637
+ p(`Formato: st_<id>.<segreto> (segreto da 256 bit). Nel database si salva solo lo SHA-256 del segreto; il segreto \xE8 mostrato una sola volta. La verifica restituisce { tokenId, label, scopes }: nessun ruolo o grant.`),
1638
+ note("danger", `Non \xE8 un superutente`, `Un service token non apre le API Accessi: abilita solo le rotte della tua app che proteggi con ServiceTokenGuard.`),
1639
+ note("info", `Introspection`, `Per un backend fratello creare un service token con scope accessi:introspect e usarlo in POST /api/accessi/auth/introspect. Il resource server lo configura in introspection.serviceToken.`),
1640
+ note("warn", `Rate limit condiviso`, `In cluster configurare serviceTokens.rateLimitStore e publicAuthRateLimit.store con createRedisRateLimitStore: i contatori in memoria valgono solo per singola istanza.`),
1641
+ note("info", `Edge rate limit`, `Per una soglia grossolana per IP configurare edgeRateLimit: viene applicata davanti alle rotte Accessi. Il gateway/API gateway resta comunque consigliato per bot protection e limiti di connessione.`),
1642
+ good("ts", [
1643
+ "import { ServiceTokenGuard, RequireServiceTokenScopes } from 'emilsoftware-utilities';",
1644
+ "",
1645
+ "@UseGuards(ServiceTokenGuard)",
1646
+ "@RequireServiceTokenScopes('ia')",
1647
+ '@Post("internal/ask")',
1648
+ "ask() { /* req.accessiServiceToken = { tokenId, label, scopes } */ }"
1649
+ ].join("\n"), "Rotta tecnica protetta"),
1650
+ h(`Gestione (superutente)`),
1651
+ list([
1652
+ `POST /api/accessi/service-token (label, scopes, expiresAt o ttlDays) -> segreto mostrato una volta`,
1653
+ `GET /api/accessi/service-token?includeRevoked=true`,
1654
+ `DELETE /api/accessi/service-token/:tokenId (revoca immediata)`,
1655
+ `POST /api/accessi/service-token/:tokenId/rotate`
1656
+ ]),
1657
+ bad("ts", [
1658
+ "// Sbagliato: usare un service token per autenticare un utente umano",
1659
+ "fetch('/api/profilo', { headers: { Authorization: 'Bearer ' + serviceToken } }); // req.user assente"
1660
+ ].join("\n"), "Non \xE8 un utente"),
1661
+ note("warn", `Scadenza`, `Senza expiresAt n\xE9 defaultTtlDays il token non scade: imposta serviceTokens.defaultTtlDays o una scadenza esplicita.`)
1662
+ ]
1663
+ },
1664
+ // -------------------------------------------------------------------------
1665
+ {
1666
+ id: "console",
1667
+ group: "Console",
1668
+ title: "Console amministrativa",
1669
+ summary: "Accesso, sezioni e che cosa si pu\xF2 fare.",
1670
+ blocks: [
1671
+ p(`La console \xE8 servita dal modulo su /api/accessi/console. Non ha stato proprio: ogni azione chiama le API protette con il JWT dell'utente connesso.`),
1672
+ list([
1673
+ `Utenti: elenco, creazione locale o SSO, profilo, ruoli, grant diretti, stato, 2FA e Login as.`,
1674
+ `Ruoli e grant: CRUD dei ruoli con l'albero delle abilitazioni.`,
1675
+ `Menu e gruppi: CRUD di gruppi, menu e tipi menu.`,
1676
+ `Token di servizio: creazione, rotazione, revoca.`,
1677
+ `SSO: provider e identit\xE0.`,
1678
+ `Wiki e Changelog: documentazione e novit\xE0 della libreria.`,
1679
+ `SQL: esporta il DDL delle entit\xE0 Accessi (tabelle, generatori, FK, indici, check, trigger) per replicare il database.`
1680
+ ]),
1681
+ 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.`),
1682
+ 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.`),
1683
+ note("info", `Gerarchia dei ruoli`, `Vale ADMIN (2) > SUPER (1) > utente comune (0). Un attore pu\xF2 gestire solo utenti di rango pari o inferiore: un superutente pu\xF2 amministrare totalmente utenti comuni e superutenti (profilo, password, flag, ruoli, grant, stato, eliminazione e identit\xE0/utenti SSO) ma NON pu\xF2 toccare un admin. Un admin pu\xF2 gestire chiunque. La regola \xE8 imposta dal backend (ensureCanManageTargetUser): un tentativo non consentito restituisce 403.`),
1684
+ 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).`),
1685
+ note("warn", `Ultimo admin`, `Per non restare chiusi fuori dalla console, l'ultimo admin attivo non pu\xF2 essere disattivato, bloccato, eliminato n\xE9 privato del flag Admin: l'operazione restituisce 400 finch\xE9 non viene promosso un altro admin.`),
1686
+ 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).`),
1687
+ 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 \xE8 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 \xE8 opzionale: se assente viene generata e mostrata una sola volta.`),
1688
+ note("ok", `Auto-ingresso dal frontend`, `Se un frontend ha gi\xE0 una sessione Accessi valida e l'utente \xE8 abilitato alla console, pu\xF2 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 \xE8 utilizzabile una sola volta (endpoint di scambio protetto da rate limit).`),
1689
+ note("info", `Console centralizzata`, `In una topologia multi-backend la console va ospitata una sola volta su Accessi Service. Gli altri backend possono linkarla con \`buildAccessiConsoleUrl\` di \`accessi/console\` oppure proxare \`/api/accessi/*\` sullo stesso dominio. Non copiare gli asset della console in ogni applicazione.`),
1690
+ note("warn", `Ticket multi-istanza`, `I ticket di ingresso console sono in memoria per default. Con piu istanze configurare \`consoleEntry.store\` con \`createRedisConsoleEntryStore\`: il JWT viene cifrato e il consume e atomico.`)
1691
+ ]
1692
+ },
1693
+ // -------------------------------------------------------------------------
1694
+ {
1695
+ id: "login-as",
1696
+ group: "Console",
1697
+ title: "Login as (accesso temporaneo)",
1698
+ summary: "Superutenti e admin possono generare un URL per entrare in un applicazione come un altro utente.",
1699
+ blocks: [
1700
+ p(`Dalla lista utenti della console il pulsante Login as apre il pannello dedicato: inserisci l'URL dell'applicazione e premi "Genera URL di accesso". Accessi emette un JWT per l'utente target e lo allega all'URL come query parameter \`loginAsToken\`.`),
1701
+ code("http", `POST /api/accessi/user/login-as/:codiceUtente
1702
+ Content-Type: application/json
1703
+ Authorization: Bearer <jwt amministratore>
1704
+
1705
+ { "url": "https://app.example.com/dashboard" }`, "Richiesta"),
1706
+ code("json", `{
1707
+ "Result": {
1708
+ "loginUrl": "https://app.example.com/dashboard?loginAsToken=eyJ...",
1709
+ "token": "eyJ...",
1710
+ "expiresInSeconds": 86400,
1711
+ "expiresAt": "2026-10-24T14:05:00.000Z"
1712
+ }
1713
+ }`, "Risposta"),
1714
+ note("danger", `La durata \xE8 quella standard`, `Il token dura quanto la sessione Accessi standard (jwtOptions.expiresIn). \xC8 una credenziale: chiunque abbia l'URL pu\xF2 usarlo fino alla scadenza per entrare nell'applicazione come l'utente target. Non condividerlo, non salvarlo in log o documenti e non usarlo in produzione per operazioni distruttive.`),
1715
+ note("info", `Chi pu\xF2 generarlo`, `Solo utenti con FLGSUPER o FLGADMIN. Vale la stessa gerarchia della gestione utenti: un superutente non pu\xF2 impersonare un admin; un admin pu\xF2 impersonare chiunque. Il tentativo non consentito restituisce 403.`),
1716
+ note("warn", `Responsabilit\xE0 dell applicazione`, `Accessi non controlla l'URL di destinazione n\xE9 consuma il token al posto dell'applicazione. L'app deve leggere \`loginAsToken\`, validarlo (per esempio con \`POST /api/accessi/auth/get-user-by-token\` o \`authorizeAccessi\`) e decidere cosa consentire. Il token \xE8 un access token Accessi a tutti gli effetti, con claim \`loginAs: true\`, \`amr: ["login_as"]\` e \`act\` valorizzato con l'attore.`),
1717
+ p(`Il token viene emesso anche per utenti con 2FA attiva: l'attore ha gi\xE0 completato l'autenticazione amministrativa. Non usare questa funzione per aggirare la 2FA di un utente comune al di fuori di un intervento amministrativo autorizzato.`)
1718
+ ]
1719
+ },
1720
+ // -------------------------------------------------------------------------
1721
+ {
1722
+ id: "errori",
1723
+ group: "Riferimenti",
1724
+ title: "Contratto errori e codici",
1725
+ summary: "Formato della risposta e codici stabili ACCESSI_* e AUTH_*.",
1726
+ blocks: [
1727
+ 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.`),
1728
+ code("json", `{
1729
+ "severity": "error",
1730
+ "status": 400,
1731
+ "statusCode": 2,
1732
+ "code": "ACCESSI_VALIDATION_ERROR",
1733
+ "error": "ACCESSI_VALIDATION_ERROR",
1734
+ "message": "La richiesta contiene dati non validi.",
1735
+ "details": ["email: deve essere una stringa."]
1736
+ }`, "Errore di validazione"),
1737
+ table(
1738
+ ["Codice", "Quando"],
1739
+ [
1740
+ ["ACCESSI_VALIDATION_ERROR", "Body o parametri non validi (400)."],
1741
+ ["ACCESSI_AUTH_INITIALIZING", "Bootstrap in corso (503)."],
1742
+ ["ACCESSI_AUTH_NOT_INITIALIZED", "Modulo non inizializzato (500)."],
1743
+ ["AUTH_HEADER_MISSING / AUTH_TOKEN_MISSING", "Header Authorization assente (401)."],
1744
+ ["AUTH_TOKEN_INVALID", "JWT non valido o scaduto (401)."],
1745
+ ["AUTH_USER_DISABLED", "Utente non pi\xF9 autorizzato (401)."],
1746
+ ["AUTH_INSUFFICIENT_PERMISSIONS", "Requisiti non soddisfatti (403)."],
1747
+ ["AUTH_REQUIREMENTS_MISCONFIGURED", "Policy malformata o handler custom mancante (500)."],
1748
+ ["ACCESSI_DATABASE_SCHEMA_OUTDATED", "Schema del database incompatibile."],
1749
+ ["ACCESSI_SERVICE_TOKEN_RATE_LIMITED", "Troppi tentativi (429)."]
1750
+ ]
1751
+ )
1752
+ ]
1753
+ },
1754
+ // -------------------------------------------------------------------------
1755
+ {
1756
+ id: "endpoint",
1757
+ group: "Riferimenti",
1758
+ title: "Indice degli endpoint",
1759
+ summary: "Tutte le rotte /api/accessi/* in una tabella.",
1760
+ blocks: [
1761
+ table(
1762
+ ["Area", "Endpoint principali"],
1763
+ [
1764
+ ["Auth", "POST auth/login, auth/get-user-by-token, auth/confirm-reset-password/:token, auth/refresh, auth/revoke-sessions, auth/revoke-token, auth/two-factor/verify, auth/two-factor/resend"],
1765
+ ["Email", "POST email/send-reset-password-email, GET email/reset-password-page/:token"],
1766
+ ["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, POST user/login-as/:codiceUtente"],
1767
+ ["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"],
1768
+ ["Catalogo", "POST/PUT/DELETE permission/menus[/:codiceMenu], permission/menu-groups[/:codiceGruppo], permission/menu-types[/:codiceTipo]"],
1769
+ ["Configurator", "PATCH configurator/menus/:codiceMenu/enabled, PATCH configurator/groups/:codiceGruppo/enabled"],
1770
+ ["SSO", "GET/POST federated-auth/providers, PATCH/DELETE providers/:provider, GET/POST users/:codiceUtente/identities, POST users, PATCH users/:codiceUtente/password-login"],
1771
+ ["Token", "POST/GET service-token, DELETE service-token/:tokenId, POST service-token/:tokenId/rotate"],
1772
+ ["Schema", "GET schema (DDL completo + entit\xE0 presenti, solo admin)"],
1773
+ ["Console", "GET console, console/:view, console/:view/:sub, console/assets/:file"]
1774
+ ]
1775
+ )
1776
+ ]
1777
+ },
1778
+ // -------------------------------------------------------------------------
1779
+ {
1780
+ id: "antipattern",
1781
+ group: "Riferimenti",
1782
+ title: "Antipattern e checklist",
1783
+ summary: "Che cosa non fare, in breve.",
1784
+ blocks: [
1785
+ list([
1786
+ `Non aprire la porta HTTP prima di initializeAccessiModule.`,
1787
+ `Non usare authorizeAccessi come unico strato: valida sempre anche l'input applicativo.`,
1788
+ `Non dare per scontato che req.userGrants esista se non hai richiesto dei requisiti.`,
1789
+ `Non considerare incrementali assignRolesToUser e assignPermissionsToUser: sono sostitutivi.`,
1790
+ `Non far validare i token SSO ad Accessi: passagli solo { provider, subject } verificati.`,
1791
+ `Non usare un service token come identit\xE0 utente.`,
1792
+ `Non versionare segreti; jwtOptions.secret deve essere unico per ambiente.`,
1793
+ `Non eliminare gruppi o tipi in uso: gestisci l'errore 400 e disabilita invece.`,
1794
+ `Non usare il challenge 2FA come JWT.`
1795
+ ]),
1796
+ 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.`)
1797
+ ]
1798
+ }
1799
+ ];
1800
+ function wikiGroups() {
1801
+ const groups = [];
1802
+ for (const section of WIKI_SECTIONS) {
1803
+ let bucket = groups.find((item) => item.group === section.group);
1804
+ if (!bucket) {
1805
+ bucket = { group: section.group, sections: [] };
1806
+ groups.push(bucket);
1807
+ }
1808
+ bucket.sections.push(section);
1809
+ }
1810
+ return groups;
1811
+ }
1812
+ function buildAiDigest() {
1813
+ const lines = [
1814
+ `# Accessi (emilsoftware-utilities) - guida compatta per l'integrazione backend`,
1815
+ "",
1816
+ `Modulo NestJS/Express + Firebird per autenticazione JWT, autorizzazione per menu, utenti, 2FA e passwordless, SSO generico, service token e console.`,
1817
+ ""
1818
+ ];
1819
+ for (const { group, sections } of wikiGroups()) {
1820
+ lines.push(`## ${group}`);
1821
+ for (const section of sections) {
1822
+ lines.push(`### ${section.title}`);
1823
+ lines.push(section.summary);
1824
+ for (const block of section.blocks) {
1825
+ switch (block.kind) {
1826
+ case "p":
1827
+ lines.push(block.text);
1828
+ break;
1829
+ case "h":
1830
+ lines.push(`**${block.text}**`);
1831
+ break;
1832
+ case "list":
1833
+ block.items.forEach((item, index) => lines.push(`${block.ordered ? `${index + 1}.` : "-"} ${item}`));
1834
+ break;
1835
+ case "code":
1836
+ lines.push("```" + block.language);
1837
+ lines.push(block.code);
1838
+ lines.push("```");
1839
+ break;
1840
+ case "callout":
1841
+ lines.push(`[${block.tone.toUpperCase()}] ${block.title ? block.title + ": " : ""}${block.text}`);
1842
+ break;
1843
+ case "table":
1844
+ lines.push("| " + block.head.join(" | ") + " |");
1845
+ lines.push("| " + block.head.map(() => "---").join(" | ") + " |");
1846
+ block.rows.forEach((row) => lines.push("| " + row.join(" | ") + " |"));
1847
+ break;
1848
+ }
1849
+ }
1850
+ lines.push("");
1851
+ }
1852
+ }
1853
+ return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
1854
+ }
1855
+
1856
+ // src/accessi-module/Console/changelog.md
1857
+ 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- Import: gli entry point (`accessi/server`, `accessi/resource-server`, `accessi/client*`, `accessi/contracts*`, `accessi/openapi`, `accessi/console`, `accessi/ui`) sono la via consigliata. Root barrel e deep import restano supportati fino alla prossima major; mappa di migrazione in `docs/import-migration.md`.\n- Sicurezza HTTP: CSP configurabile con nonce per la console, `'unsafe-inline'` documentato solo per Swagger UI, header frame/referrer/cache personalizzabili; guida in `docs/security-headers.md`.\n- Autenticazione: modalita opzionale a cookie `HttpOnly` con CSRF double-submit, refresh da cookie e supporto console; Bearer resta il default. Guida in `docs/auth-cookies.md`.\n- Observability: metriche Prometheus di dominio per login, 2FA, refresh, rate limit, introspection, JWKS, revoche, ticket console e store Redis, con alert suggeriti in `docs/metrics.md`.\n- Audit: sink memory/Redis/HTTP/callback, redazione ricorsiva, retention, query ed export NDJSON admin in `docs/audit.md`.\n- Multi-tenancy: un database per tenant senza modifiche allo schema, cartella di file `<id>.env`, validazione isolamento, risoluzione host/path/header fidato e supervisore processi in `docs/multi-tenancy.md`.\n- Resource server: aggiunti `createAccessiAuthMiddleware`, `createAccessiAuthGuard`, `createAccessiTokenVerifier` e `createAccessiIntrospectionClient` per backend fratelli che non possiedono il database Accessi. Supporta HS256, RS/ES con chiave pubblica PEM e JWKS con cache.\n- Introspection: nuovo `POST /api/accessi/auth/introspect` protetto da service token con scope `accessi:introspect`. Restituisce `active`, profilo utente e grant correnti.\n- JWT: `jwtOptions` accetta `algorithm`, `issuer`, `audience`, `privateKey`, `publicKey`, `keyId`, `jwksPath` e `previousPublicKeys`. Default HS256 invariato; RS/ES sono consigliati per la topologia multi-servizio.\n- JWKS: con RS/ES Accessi pubblica `/accessi/.well-known/jwks.json` (path configurabile) con chiave corrente e chiavi precedenti per la rotazione. I resource server possono usare `jwks.url`.\n- Console ticket: introdotto `ConsoleEntryStore`. Default in memoria; `createRedisConsoleEntryStore` salva il JWT cifrato in Redis, usa TTL e consume atomico per il monouso multi-istanza. Configurazione in `consoleEntry.store` e `consoleEntry.ttlMs`.\n- Rate limit: introdotto `RateLimitStore`. `createRedisRateLimitStore` usa `INCR` + `PEXPIRE` atomici per contatori condivisi in cluster. Configurazione in `publicAuthRateLimit.store` e `serviceTokens.rateLimitStore`; default in memoria invariato.\n- Edge rate limit: aggiunto `createAccessiEdgeRateLimitMiddleware` e `AccessiOptions.edgeRateLimit` per una soglia grossolana per IP davanti alle rotte Accessi. Fail closed se lo store non risponde; supporta lo stesso store Redis.\n- Servizio standalone: aggiunti `createAccessiService`, `startAccessiService`, `/health/live`, `/health/ready`, `/health/metrics` e shutdown graceful. Aggiunti gli script `accessi:serve` e `accessi:serve:dist` per l'esecuzione come processo Node.\n- Configurazione: aggiunti `validateAccessiOptions` e `assertValidAccessiOptions`; il servizio standalone li applica al bootstrap.\n- HTTP: aggiunte opzioni `http.cors`, `http.trustProxy`, `http.securityHeaders`. La JWKS viene montata automaticamente anche con `AccessiModule.forRoot` tramite `HttpAdapterHost`.\n- SSO: aggiunto `POST /api/accessi/federated-auth/exchange` protetto da service token con scope `accessi:federated:exchange` per scambiare `{ provider, subject }` verificati da un backend fratello.\n- Audit: aggiunto `auditAccessi` per eventi di sicurezza strutturati; resource server ed edge rate limit registrano fallimenti di autenticazione, blocchi e rate limit non disponibile.\n- Contratto: aggiunto `npm run check:accessi-openapi`, eseguito nel build, che blocca path fuori prefisso, operationId duplicati/mancanti, $ref rotti, path legacy e rimozioni di operation/schema rispetto al fingerprint versionato.\n- Contratto: aggiunto `npm run accessi:update-openapi-fingerprint` per aggiornare intenzionalmente il fingerprint dopo una modifica breaking approvata.\n- Console: aggiunto `consoleApiBaseUrl` per servire UI e API su domini diversi; il valore viene iniettato in `window.__ACCESSI_API_BASE_URL__`.\n- Chiavi: aggiunto `npm run accessi:generate-jwt-keys` per generare coppia RS256/ES256, `keyId` e JWKS pubblica.\n- Sessioni: aggiunta la revoca per utente con `SessionRevocationStore` memory/Redis e `POST /api/accessi/auth/revoke-sessions`. Resource server e guard controllano il cutoff `iat`.\n- Refresh token: con `jwtOptions.refreshExpiresIn` login, 2FA e SSO emettono anche `refreshToken` (typ=refresh) e `POST /api/accessi/auth/refresh` restituisce una nuova coppia access+refresh. Gli access token hanno ora `jti`.\n- Refresh rotation: con `refreshTokenRotation.store` il refresh token e monouso e la rotazione e atomica tra istanze; due refresh concorrenti producono un solo successo.\n- Contratto: il fingerprint OpenAPI ora blocca anche la rimozione di operation/schema rispetto alla baseline versionata.\n- OpenAPI: aggiunto `mergeOpenApiDocuments` per unire il documento Accessi nel documento unico dell'host con conflitti espliciti.\n- Console: aggiunto `buildAccessiConsoleUrl` per linkare la console centrale senza mettere JWT nell'URL.\n- Rinomina: la configurazione e gli helper della documentazione usano `openapi`/`OpenApi` (`AccessiOptions.openapi`, `OpenApiSetupOptions`, `setupOpenApi`, `DEFAULT_ACCESSI_OPENAPI`); i vecchi nomi `swagger` non sono piu esportati.\n- Token Accessi: `TokenResult.expiresIn` riporta sempre la validita effettiva in secondi (es. `86400`), anche quando `jwtOptions.expiresIn` usa un formato leggibile come `24h`. I client possono quindi esporre `expires_in` anche per il token di login normale.\n- Console: nuova funzione **Login as** per superutenti e admin. Dalla lista utenti si genera un URL verso l'applicazione scelta con `?loginAsToken=<jwt>`; il token dura quanto la sessione Accessi standard (`jwtOptions.expiresIn`), registra l'attore nelle claim `act`/`loginAs` e resta soggetto alla gerarchia ADMIN > SUPER. Endpoint: `POST /api/accessi/user/login-as/:codiceUtente`.\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- Sicurezza: `GET /api/accessi/permission/grants/:codiceUtente` accetta solo un JWT Accessi valido e consente la lettura solo al proprietario o a chi pu\xF2 gestire quel target (gerarchia ADMIN > SUPER). Un superutente non pu\xF2 leggere i grant di un admin.\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";
1858
+
1859
+ // src/accessi-module/Console/help.ts
1860
+ var p2 = (text) => ({ kind: "p", text });
1861
+ var list2 = (items) => ({ kind: "list", items });
1862
+ var code2 = (language, codeText, title) => ({ kind: "code", language, title, code: codeText });
1863
+ var note2 = (tone, title, text) => ({ kind: "callout", tone, title, text });
1864
+ var table2 = (head, rows) => ({ kind: "table", head, rows });
1865
+ var HELP = {
1866
+ // ------------------------------------------------------------------ SSO
1867
+ "sso-provider": {
1868
+ title: "Provider SSO",
1869
+ blocks: [
1870
+ p2(`La chiave del provider censito in Accessi. Deve coincidere esattamente con quella usata dal backend quando valida il login SSO e chiama FederatedAuthService.authenticate({ provider, subject }).`),
1871
+ p2(`Se il backend usa la chiave azure-ad-acme-prod, qui va la stessa stringa: una differenza, anche di un carattere, fa fallire il collegamento.`),
1872
+ note2("info", "Chi crea il provider", `Prima si censisce il provider in "SSO \u2192 Provider" (superutente), poi lo si sceglie qui. Se l'elenco \xE8 vuoto, non ci sono provider attivi.`)
1873
+ ]
1874
+ },
1875
+ "sso-subject": {
1876
+ title: "ID utente del provider (subject)",
1877
+ blocks: [
1878
+ p2(`\xC8 l'identificatore STABILE della persona nel provider, non l'email. Il backend lo ottiene dopo aver validato il token del provider e lo passa ad Accessi; Accessi non vede mai il token esterno.`),
1879
+ p2(`Ecco il claim corretto per i provider pi\xF9 comuni:`),
1880
+ table2(
1881
+ ["Provider", "Claim da usare", "Note"],
1882
+ [
1883
+ ["Azure AD / Entra ID", "oid", "Object ID. Non usare preferred_username n\xE9 email."],
1884
+ ["Google", "sub", "ID numerico di 21 cifre. Non l indirizzo email."],
1885
+ ["Apple", "sub", "Identificatore stabile per la tua app; disponibile solo al primo login, va salvato subito."],
1886
+ ["Okta", "sub", "Claim standard OIDC."],
1887
+ ["Auth0", "sub", "Formato auth0|... oppure google-oauth2|... ."],
1888
+ ["Keycloak", "sub", "Claim standard OIDC."],
1889
+ ["Microsoft personale (MSA)", "sub", "Account outlook/live."],
1890
+ ["SAML 2.0", "NameID", "Identificatore persistente dell utente."],
1891
+ ["OIDC generico", "sub", "Claim standard OIDC."]
1892
+ ]
1893
+ ),
1894
+ note2("danger", "Mai usare l email", `L'email pu\xF2 cambiare e non \xE8 un identificatore stabile: usarla come subject crea duplicati e collegamenti errati.`),
1895
+ code2("ts", [
1896
+ "// Nel backend, dopo aver validato il token del provider:",
1897
+ "// Azure AD / Entra ID",
1898
+ "const subject = claims.oid;",
1899
+ "// Google / Okta / Keycloak / OIDC generico",
1900
+ "const subject = claims.sub;",
1901
+ "// Apple (disponibile al primo login, salvalo subito)",
1902
+ "const subject = claims.sub;",
1903
+ "// SAML",
1904
+ "const subject = assertion.nameId;"
1905
+ ].join("\n"), "Come ricavare il subject")
1906
+ ]
1907
+ },
1908
+ "sso-note": {
1909
+ title: "Nota",
1910
+ blocks: [
1911
+ p2(`Nota interna per gli amministratori, utile a ricordare perch\xE9 il collegamento esiste (per esempio "migrato da AD il 2026-01-10"). Non viene inviata al provider.`)
1912
+ ]
1913
+ },
1914
+ "provider-key": {
1915
+ title: "Chiave provider",
1916
+ blocks: [
1917
+ p2(`Identificativo tecnico del provider, usato dal backend nel payload SSO. Scegli una chiave descrittiva e stabile, per esempio azure-ad-acme-prod o google-workspace-acme.`),
1918
+ note2("warn", "Immutabile", `Dopo il censimento la chiave non si modifica: identit\xE0 e configurazione del backend la referenziano. Se serve cambiarla, disabilita il provider e creane uno nuovo.`)
1919
+ ]
1920
+ },
1921
+ "provider-description": {
1922
+ title: "Descrizione",
1923
+ blocks: [
1924
+ p2(`Nome leggibile mostrato agli amministratori (per esempio "Azure AD \u2013 Acme produzione"). Non \xE8 la chiave tecnica: pu\xF2 contenere spazi.`)
1925
+ ]
1926
+ },
1927
+ "provider-note": {
1928
+ title: "Nota",
1929
+ blocks: [
1930
+ p2(`Nota interna del catalogo. Non inserire segreti, issuer, client id o dati personali: quei valori restano nel backend.`)
1931
+ ]
1932
+ },
1933
+ "provider-active": {
1934
+ title: "Provider attivo",
1935
+ blocks: [
1936
+ p2(`Se disattivato, il provider non accetta nuovi login e nuovi collegamenti; le identit\xE0 e lo storico restano. Riattivalo quando il backend \xE8 pronto.`)
1937
+ ]
1938
+ },
1939
+ // --------------------------------------------------------- service token
1940
+ "token-label": {
1941
+ title: "Descrizione del token",
1942
+ blocks: [
1943
+ p2(`Nome del token, mostrato agli amministratori (per esempio "Integrazione IA \u2013 produzione"). Non \xE8 un segreto.`)
1944
+ ]
1945
+ },
1946
+ "token-scopes": {
1947
+ title: "Scope",
1948
+ blocks: [
1949
+ p2(`Etichette che il backend controlla per autorizzare le chiamate tecniche. Un token non \xE8 un superutente: abilita solo le rotte protette con ServiceTokenGuard e @RequireServiceTokenScopes.`),
1950
+ list2([
1951
+ `Formato: lettere, numeri, ':', '_' o '-', massimo 40 caratteri.`,
1952
+ `Esempi: ia, chat, tools, report:read.`,
1953
+ `Separa gli scope con una virgola.`
1954
+ ]),
1955
+ code2("ts", [
1956
+ "@UseGuards(ServiceTokenGuard)",
1957
+ "@RequireServiceTokenScopes('ia')",
1958
+ '@Post("internal/ask")',
1959
+ "ask() { /* il token deve avere lo scope ia */ }"
1960
+ ].join("\n"), "Come li usa il backend"),
1961
+ note2("warn", "Scope vuoto", `Senza scope il token non soddisfa nessun @RequireServiceTokenScopes, ma passa comunque le rotte protette senza scope richiesto.`)
1962
+ ]
1963
+ },
1964
+ "token-ttl": {
1965
+ title: "Durata (giorni)",
1966
+ blocks: [
1967
+ p2(`Giorni di validit\xE0 del token. Alla scadenza il token smette di funzionare.`),
1968
+ note2("danger", "Nessuna scadenza", `Se lasci vuoto questo campo e non imposti una data, il token non scade mai: sconsigliato in produzione.`)
1969
+ ]
1970
+ },
1971
+ // ----------------------------------------------------------------- utenti
1972
+ "user-email": {
1973
+ title: "Email",
1974
+ blocks: [
1975
+ p2(`Identificativo di accesso dell'utente e recapito per le email di reset o codici. Deve essere univoca.`)
1976
+ ]
1977
+ },
1978
+ "user-name": {
1979
+ title: "Nome e cognome",
1980
+ blocks: [
1981
+ p2(`Dati anagrafici mostrati in console e restituiti nel profilo. Non incidono sull'autenticazione.`)
1982
+ ]
1983
+ },
1984
+ "user-password-login": {
1985
+ title: "Login con password",
1986
+ blocks: [
1987
+ p2(`Se disattivato, il login con email e password viene rifiutato (PASSWORD_LOGIN_DISABLED). Restano validi i login con identit\xE0 SSO attive.`),
1988
+ note2("info", "Utente solo SSO", `\xC8 l'opzione tipica per gli utenti federati: nessuna password locale, accesso solo dal provider.`)
1989
+ ]
1990
+ },
1991
+ "user-2fa": {
1992
+ title: "Codice via email (2FA)",
1993
+ blocks: [
1994
+ p2(`Richiede un codice monouso inviato per email dopo la password o dopo il provider SSO. \xC8 per-utente e disattivata per impostazione predefinita.`),
1995
+ list2([
1996
+ `Codice a 6 cifre, valido 10 minuti, 5 tentativi, massimo 3 invii.`,
1997
+ `Non sono token: fino alla verifica non esiste alcun JWT.`
1998
+ ])
1999
+ ]
2000
+ },
2001
+ "user-passwordless": {
2002
+ title: "Accesso con il solo codice email",
2003
+ blocks: [
2004
+ p2(`Abilita il login senza password: si inserisce solo l'email e si usa il codice ricevuto. Richiede il codice email (2FA) attivo.`),
2005
+ note2("warn", "Non sono due fattori", `L'accesso con il solo codice \xE8 autenticazione tramite possesso della casella email: un solo fattore.`)
2006
+ ]
2007
+ },
2008
+ "user-default-page": {
2009
+ title: "Pagina di default",
2010
+ blocks: [
2011
+ p2(`Percorso o codice della pagina verso cui indirizzare l'utente dopo il login. Esempio: /dashboard oppure la chiave di una pagina gestita dal frontend.`),
2012
+ p2(`La console propone in autocomplete tutte le rotte presenti nel catalogo dei menu. Puoi filtrare la lista scrivendo oppure inserire un valore libero.`),
2013
+ p2(`E una stringa libera (massimo 50 caratteri): la libreria la conserva e la restituisce nel profilo; l'instradamento effettivo lo decide il frontend.`)
2014
+ ]
2015
+ },
2016
+ "user-cellulare": {
2017
+ title: "Cellulare",
2018
+ blocks: [
2019
+ p2(`Numero di cellulare dell'utente (UTENTI_CONFIG.CELLULARE). \xC8 il recapito canonico per SMS o notifiche applicative; Accessi non lo invia da solo.`)
2020
+ ]
2021
+ },
2022
+ "user-lingua": {
2023
+ title: "Codice lingua",
2024
+ blocks: [
2025
+ p2(`Lingua preferita dell'utente (UTENTI_CONFIG.CODLINGUA). Stringa breve come it, en, sl; viene restituita nel profilo e il frontend decide come usarla.`)
2026
+ ]
2027
+ },
2028
+ "user-avatar": {
2029
+ title: "Avatar",
2030
+ blocks: [
2031
+ p2(`Riferimento all'avatar (UTENTI_CONFIG.AVATAR, massimo 30 caratteri): tipicamente un nome file o una chiave, non un base64.`)
2032
+ ]
2033
+ },
2034
+ "user-scadenza-password": {
2035
+ title: "Scadenza password",
2036
+ blocks: [
2037
+ 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.`)
2038
+ ]
2039
+ },
2040
+ "user-gdpr": {
2041
+ title: "Consenso GDPR",
2042
+ blocks: [
2043
+ p2(`Lo stato mostra se il consenso \xE8 registrato. Il pulsante "Registra consenso GDPR" (endpoint set-gdpr) imposta FLGGDPR e DATGDPR e scrive la riga storica in UTENTI_GDPR.`),
2044
+ note2("info", "Completo e tracciato", `A differenza del flag impostato a mano, il flusso dedicato registra anche la data e lo storico dell'accettazione.`)
2045
+ ]
2046
+ },
2047
+ "user-json": {
2048
+ title: "Metadata JSON",
2049
+ blocks: [
2050
+ p2(`Campo libero JSON (UTENTI_CONFIG.JSON_METADATA) per dati applicativi del backend. Deve essere JSON valido se il backend lo interpreta.`)
2051
+ ]
2052
+ },
2053
+ "user-set-password": {
2054
+ title: "Imposta password",
2055
+ blocks: [
2056
+ p2(`Imposta direttamente una nuova password locale: viene hashata dal backend e la scadenza segue la policy. Nessuna email viene inviata.`),
2057
+ p2(`Requisiti di sicurezza:`),
2058
+ list2([
2059
+ `Almeno 8 caratteri (massimo 100).`,
2060
+ `Almeno una lettera maiuscola e una minuscola.`,
2061
+ `Almeno una cifra e un carattere speciale (es. ! ? @ # $ %).`,
2062
+ `Nessuno spazio e non deve essere una password comune.`
2063
+ ]),
2064
+ 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.`),
2065
+ note2("warn", "Sostituisce la password attuale", `L'utente dovr\xE0 usare subito la nuova password. Per fargliela scegliere, usa "Invia reset password".`)
2066
+ ]
2067
+ },
2068
+ "user-super": {
2069
+ title: "Superutente (FLGSUPER)",
2070
+ blocks: [
2071
+ 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.`),
2072
+ 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.`),
2073
+ 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.`)
2074
+ ]
2075
+ },
2076
+ "user-admin": {
2077
+ title: "Admin (FLGADMIN)",
2078
+ blocks: [
2079
+ 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.`),
2080
+ 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.`)
2081
+ ]
2082
+ },
2083
+ "user-state": {
2084
+ title: "Stato di registrazione",
2085
+ blocks: [
2086
+ table2(
2087
+ ["Stato", "Significato"],
2088
+ [
2089
+ ["Non definito / Inserito", "Utente non confermato."],
2090
+ ["Invitato (10)", "Richiede il rinnovo della password."],
2091
+ ["Confermato (20)", "Utente valido, pu\xF2 accedere."],
2092
+ ["Eliminato (50)", "Soft delete: login bloccato."],
2093
+ ["Bloccato (99)", "Login bloccato."]
2094
+ ]
2095
+ )
2096
+ ]
2097
+ },
2098
+ // ------------------------------------------------------------------ ruoli
2099
+ "role-description": {
2100
+ title: "Descrizione del ruolo",
2101
+ blocks: [
2102
+ p2(`Nome del ruolo (per esempio "Gestore ordini"). Deve avere almeno un menu selezionato: il salvataggio sostituisce integralmente i menu del ruolo.`)
2103
+ ]
2104
+ },
2105
+ "role-level": {
2106
+ title: "Livello di abilitazione",
2107
+ blocks: [
2108
+ table2(
2109
+ ["Livello", "Valore", "Uso tipico"],
2110
+ [
2111
+ ["Lettura", "10", "Sola consultazione."],
2112
+ ["Scrittura", "20", "Modifica dei dati."],
2113
+ ["Legacy (30)", "30", "Valore storico, mantenuto senza perdita."]
2114
+ ]
2115
+ ),
2116
+ p2(`Il controllo confronta tipoAbilitazione >= soglia: un livello 20 soddisfa anche una richiesta di livello 10.`)
2117
+ ]
2118
+ },
2119
+ // --------------------------------------------------------- catalogo menu
2120
+ "menu-code": {
2121
+ title: "Codice menu",
2122
+ blocks: [
2123
+ p2(`Chiave tecnica del menu, stabile e senza spazi (per esempio ORDINI o MNU001). \xC8 la chiave primaria: non si modifica dopo la creazione.`),
2124
+ note2("warn", "Immutabile", `Il codice \xE8 referenziato da ruoli e grant: per cambiarlo, elimina e ricrea il menu.`)
2125
+ ]
2126
+ },
2127
+ "menu-group-code": {
2128
+ title: "Codice gruppo",
2129
+ blocks: [
2130
+ p2(`Un solo carattere (per esempio A). Raggruppa i menu nella navigazione. Immutabile dopo la creazione.`)
2131
+ ]
2132
+ },
2133
+ "menu-type-code": {
2134
+ title: "Codice tipo menu",
2135
+ blocks: [
2136
+ p2(`Un solo carattere (per esempio X). Classifica i menu (amministrazione, operativo, ...). Immutabile dopo la creazione.`)
2137
+ ]
2138
+ },
2139
+ "menu-group-order": {
2140
+ title: "Ordine del gruppo",
2141
+ blocks: [
2142
+ p2(`Numero che determina l'ordine dei gruppi nella navigazione: valori pi\xF9 bassi compaiono prima.`)
2143
+ ]
2144
+ },
2145
+ "menu-order": {
2146
+ title: "Ordine del menu",
2147
+ blocks: [
2148
+ p2(`Numero che determina l'ordine dei menu all'interno del gruppo: valori pi\xF9 bassi compaiono prima.`)
2149
+ ]
2150
+ },
2151
+ "menu-page": {
2152
+ title: "Pagina",
2153
+ blocks: [
2154
+ p2(`Percorso o nome della pagina del frontend associata al menu (per esempio /ordini). \xC8 informativo: lo usano la UI host e i client.`)
2155
+ ]
2156
+ },
2157
+ "menu-icon": {
2158
+ title: "Icona",
2159
+ blocks: [
2160
+ p2(`Identificatore dell'icona usato dalla UI host (per esempio fa-box o un nome concordato). Accessi non impone un set di icone.`)
2161
+ ]
2162
+ },
2163
+ "menu-rifmenu": {
2164
+ title: "Menu di riferimento",
2165
+ blocks: [
2166
+ p2(`Codice di un altro menu che fa da padre, per costruire gerarchie di menu. Lascia vuoto per un menu di primo livello.`)
2167
+ ]
2168
+ },
2169
+ "menu-note": {
2170
+ title: "Nota",
2171
+ blocks: [
2172
+ p2(`Nota descrittiva mostrata agli amministratori. Non incide sui permessi.`)
2173
+ ]
2174
+ },
2175
+ "menu-enabled": {
2176
+ title: "Menu abilitato",
2177
+ blocks: [
2178
+ p2(`Se disattivato, il menu non compare nel catalogo attivo e non viene considerato nel calcolo dei grant. I collegamenti a ruoli e utenti restano.`)
2179
+ ]
2180
+ },
2181
+ "menu-group-enabled": {
2182
+ title: "Gruppo abilitato",
2183
+ blocks: [
2184
+ p2(`Disattiva l'intero gruppo: i menu al suo interno non sono considerati attivi, ma restano configurabili per una riattivazione futura.`)
2185
+ ]
2186
+ },
2187
+ // ------------------------------------------------------------ assegnazioni
2188
+ "role-assignment": {
2189
+ title: "Assegnazione dei ruoli",
2190
+ blocks: [
2191
+ p2(`Il salvataggio SOSTITUISCE integralmente i ruoli dell'utente: la lista selezionata diventa l'unico set di ruoli.`),
2192
+ list2([
2193
+ `Per aggiungere un ruolo, selezionalo mantenendo quelli gi\xE0 presenti.`,
2194
+ `Per rimuovere un ruolo, deselezionalo.`,
2195
+ `Non modificare i grant diretti: quelli si gestiscono nella scheda "Grant diretti".`
2196
+ ]),
2197
+ note2("warn", "Non \xE8 incrementale", `Inviare solo il nuovo ruolo rimuove gli altri. Invia sempre la lista completa desiderata.`)
2198
+ ]
2199
+ },
2200
+ "grant-direct": {
2201
+ title: "Grant diretti",
2202
+ blocks: [
2203
+ p2(`Permessi assegnati direttamente all'utente, indipendenti dai ruoli. Hanno precedenza sui permessi ereditati dai ruoli per lo stesso menu.`),
2204
+ list2([
2205
+ `Imposta il livello per concedere l'accesso.`,
2206
+ `Scegli "nessuno" per rimuovere il grant diretto.`,
2207
+ `Il salvataggio sostituisce l'intero set di grant diretti.`
2208
+ ])
2209
+ ]
2210
+ },
2211
+ "grant-level": {
2212
+ title: "Livello di grant",
2213
+ blocks: [
2214
+ table2(
2215
+ ["Voce", "Valore", "Significato"],
2216
+ [
2217
+ ["nessuno", "\u2014", "Nessun grant diretto per quel menu (il permesso pu\xF2 arrivare dai ruoli)."],
2218
+ ["Lettura", "10", "Sola consultazione."],
2219
+ ["Scrittura", "20", "Modifica dei dati."]
2220
+ ]
2221
+ ),
2222
+ p2(`Il confronto \xE8 tipoAbilitazione >= soglia richiesta dalla rotta.`)
2223
+ ]
2224
+ },
2225
+ "user-delete": {
2226
+ title: "Imposta stato eliminato",
2227
+ blocks: [
2228
+ 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.`),
2229
+ note2("info", "Reversibile", `Non \xE8 una cancellazione fisica: puoi ripristinare l'utente cambiando lo stato di registrazione.`),
2230
+ note2("info", "Chi pu\xF2 farlo", `Sia admin sia superutente possono eseguire il soft delete.`)
2231
+ ]
2232
+ },
2233
+ "user-delete-permanent": {
2234
+ title: "Elimina definitivamente",
2235
+ blocks: [
2236
+ p2(`Cancella l'utente e tutti i record collegati (configurazione, password, ruoli, abilitazioni, identit\xE0 SSO, challenge 2FA e GDPR). Operazione irreversibile.`),
2237
+ note2("danger", "Non reversibile", `I dati vengono rimossi fisicamente dal database; non \xE8 possibile ripristinare l'utente.`),
2238
+ 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.`)
2239
+ ]
2240
+ },
2241
+ "user-login-as": {
2242
+ title: "Login as",
2243
+ blocks: [
2244
+ p2(`Genera un accesso temporaneo all'applicazione come l'utente selezionato. Non richiede la password dell'utente e non modifica il suo profilo.`),
2245
+ note2("danger", "La durata \xE8 quella standard", `Il token dura quanto la sessione Accessi standard (jwtOptions.expiresIn). \xC8 una credenziale: chiunque ottenga l'URL pu\xF2 usarlo fino alla scadenza per entrare nell'applicazione come l'utente target. Non condividerlo, non salvarlo in log o chat e non incollarlo in documenti.`),
2246
+ note2("info", "Chi pu\xF2 usarlo", `Solo superutenti e admin. Vale la gerarchia ADMIN > SUPER: un superutente non pu\xF2 impersonare un admin.`),
2247
+ p2(`Il token viene aggiunto all'URL scelto come parametro di query loginAsToken. Da quel momento \xE8 l'applicazione di destinazione a decidere come validarlo e cosa consentire.`)
2248
+ ]
2249
+ },
2250
+ "login-as-url": {
2251
+ title: "URL dell applicazione",
2252
+ blocks: [
2253
+ p2(`Inserisci l'indirizzo assoluto dell'applicazione in cui vuoi entrare, per esempio https://app.example.com/dashboard.`),
2254
+ p2(`Il modulo aggiunge automaticamente ?loginAsToken=<token> conservando le query e il fragment gi\xE0 presenti. Sono ammessi solo URL http e https.`),
2255
+ note2("warn", "Nessun controllo sull applicazione", `Accessi non verifica che l'URL appartenga a un ambiente autorizzato: incollalo con attenzione per non inviare il token a un dominio sbagliato.`)
2256
+ ]
2257
+ },
2258
+ // ----------------------------------------------------- selezioni di menu
2259
+ "menu-group-select": {
2260
+ title: "Gruppo",
2261
+ blocks: [
2262
+ p2(`Gruppo di appartenenza del menu. Se l'elenco \xE8 vuoto, crea prima un gruppo in "Menu e gruppi \u2192 Gruppi".`)
2263
+ ]
2264
+ },
2265
+ "menu-type-select": {
2266
+ title: "Tipo menu",
2267
+ blocks: [
2268
+ p2(`Classificazione del menu (amministrazione, operativo, ...). \xC8 facoltativa: se non serve, lascia "Nessun tipo". I tipi si gestiscono in "Menu e gruppi \u2192 Tipi menu".`)
2269
+ ]
2270
+ },
2271
+ // ------------------------------------------------------------------ login
2272
+ "login-email": {
2273
+ title: "Email o utente",
2274
+ blocks: [
2275
+ p2(`Indirizzo email dell'utente Accessi. Se l'account \xE8 abilitato al solo codice, dopo questo passo riceverai un codice via email invece di inserire la password.`)
2276
+ ]
2277
+ },
2278
+ "login-password": {
2279
+ title: "Password",
2280
+ blocks: [
2281
+ p2(`Password locale dell'utente. Non serve per gli account passwordless, che accedono con il solo codice email.`)
2282
+ ]
2283
+ }
2284
+ };
2285
+
2286
+ // src/accessi-module/Console/console.ts
2287
+ var routeSegment = {
2288
+ users: "utenti",
2289
+ roles: "ruoli-e-grant",
2290
+ menuGroups: "menu-e-gruppi/gruppi",
2291
+ menuItems: "menu-e-gruppi/menu",
2292
+ menuTypes: "menu-e-gruppi/tipi-menu",
2293
+ menuScopes: "menu-e-gruppi/scope",
2294
+ ssoProviders: "sso/provider",
2295
+ ssoUsers: "sso/utenti",
2296
+ tokens: "token-di-servizio",
2297
+ wiki: "wiki",
2298
+ changelog: "changelog",
2299
+ sql: "sql"
2300
+ };
2301
+ var routeView = Object.fromEntries(Object.entries(routeSegment).map(([view, segment]) => [segment, view]));
2302
+ var parentDefaultView = {
2303
+ "menu-e-gruppi": "menuGroups",
2304
+ sso: "ssoProviders"
2305
+ };
2306
+ var consoleMarker = "/accessi/console";
2307
+ var consoleMarkerIndex = window.location.pathname.indexOf(consoleMarker);
2308
+ var consoleBasePath = consoleMarkerIndex >= 0 ? window.location.pathname.slice(0, consoleMarkerIndex + consoleMarker.length) : `${window.location.pathname.replace(/\/$/, "")}`;
2309
+ var USERS_PAGE_SIZE = 25;
2310
+ var token = sessionStorage.getItem("accessi-console-token");
2311
+ var cookieMode = typeof window !== "undefined" && window.__ACCESSI_AUTH_COOKIE__ === true;
2312
+ var currentUser = null;
2313
+ var federatedAuthenticationAvailable = false;
2314
+ var usersPage = 0;
2315
+ var usersTotal = 0;
2316
+ var booting = true;
2317
+ var pendingNetworkRequests = 0;
2318
+ var loadingRevealTimer;
2319
+ var loadingHideTimer;
2320
+ var loaderVisibleSince = 0;
2321
+ var byId = (id) => document.getElementById(id);
2322
+ var escapeHtml = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
2323
+ "&": "&amp;",
2324
+ "<": "&lt;",
2325
+ ">": "&gt;",
2326
+ '"': "&quot;",
2327
+ "'": "&#39;"
2328
+ })[char]);
2329
+ function actionIcon(paths) {
2330
+ return `<svg class="button-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
2331
+ }
2332
+ function result(response) {
2333
+ const body = response.data;
2334
+ if (body.Result === void 0) throw new Error(body.message ?? "Risposta Accessi priva di dati.");
2335
+ return body.Result;
2336
+ }
2337
+ var noticeTimers = /* @__PURE__ */ new Set();
2338
+ function showNotice(message, isError = false) {
2339
+ const container = byId("notice");
2340
+ if (!message) {
2341
+ noticeTimers.forEach((timer2) => window.clearTimeout(timer2));
2342
+ noticeTimers.clear();
2343
+ container.innerHTML = "";
2344
+ return;
2345
+ }
2346
+ const toast = document.createElement("div");
2347
+ const kind = isError ? "error" : "success";
2348
+ const icon = isError ? "!" : "\u2713";
2349
+ toast.className = `notice notice-${kind}`;
2350
+ toast.setAttribute("role", isError ? "alert" : "status");
2351
+ toast.innerHTML = `<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">&times;</button>`;
2352
+ container.appendChild(toast);
2353
+ let timer;
2354
+ const dismiss = () => {
2355
+ if (timer !== void 0) {
2356
+ window.clearTimeout(timer);
2357
+ noticeTimers.delete(timer);
2358
+ timer = void 0;
2359
+ }
2360
+ toast.remove();
2361
+ };
2362
+ toast.querySelector(".notice-close")?.addEventListener("click", dismiss);
2363
+ timer = window.setTimeout(dismiss, isError ? 9e3 : 5e3);
2364
+ noticeTimers.add(timer);
2365
+ while (container.childElementCount > 4) container.firstElementChild?.remove();
2366
+ }
2367
+ function menuMetadata(menu) {
2368
+ const details = [
2369
+ ["Codice", menu.codiceMenu],
2370
+ ["Ordine", String(menu.ordineMenu)],
2371
+ ["Tipo", menu.tipo],
2372
+ ["Pagina", menu.pagina],
2373
+ ["Icona", menu.icona],
2374
+ ["Stato", menu.enabled === false ? "Disabilitato" : "Abilitato"]
2375
+ ].filter(([, value]) => value);
2376
+ return `<dl class="entity-metadata">${details.map(([label, value]) => `<div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join("")}</dl>${menu.note ? `<p class="entity-note"><strong>Nota</strong>${escapeHtml(menu.note)}</p>` : ""}`;
2377
+ }
2378
+ function permissionSelectOptions(currentLevel) {
2379
+ return `<option value="10" ${currentLevel === 10 ? "selected" : ""}>Lettura</option><option value="20" ${currentLevel === 20 ? "selected" : ""}>Scrittura</option>${currentLevel === 30 ? '<option value="30" selected>Valore legacy (30)</option>' : ""}`;
2380
+ }
2381
+ function groupMetadata(group) {
2382
+ return `<dl class="entity-metadata"><div><dt>Codice</dt><dd>${escapeHtml(group.codiceGruppo)}</dd></div><div><dt>Ordine</dt><dd>${escapeHtml(group.ordineGruppo)}</dd></div><div><dt>Stato</dt><dd>${group.enabled === false ? "Disabilitato" : "Abilitato"}</dd></div><div><dt>Menu</dt><dd>${group.menus.length}</dd></div></dl>`;
2383
+ }
2384
+ function directGrantEditor(groups, grants) {
2385
+ return `<form id="grants" class="user-editor-section grants-editor" data-user-panel="grants" role="tabpanel" aria-labelledby="user-tab-grants"><h3>Grant diretti ${helpDot("grant-direct")}</h3><p class="form-help">Grant diretti, indipendenti dai ruoli. Seleziona <strong>nessuno</strong> per rimuoverli.</p><div class="grant-menu-groups">${groups.map((group) => `<fieldset class="grant-menu-group"><legend>${escapeHtml(group.descrizioneGruppo)}</legend>${groupMetadata(group)}<div class="grant-menu-options">${group.menus.map((menu) => {
2386
+ const grant = (grants ?? []).find((item) => item.codiceMenu === menu.codiceMenu);
2387
+ return `<div class="grant-menu-option ${menu.enabled === false ? "is-disabled" : ""}"><div class="grant-menu-info"><strong>${escapeHtml(menu.descrizioneMenu)}</strong>${menuMetadata(menu)}</div><label class="grant-level">Livello di grant ${helpDot("grant-level")}<select name="grant:${escapeHtml(menu.codiceMenu)}"><option value="">nessuno</option>${permissionSelectOptions(grant?.tipoAbilitazione)}</select></label></div>`;
2388
+ }).join("") || '<p class="form-help">Nessun menu configurato in questo gruppo.</p>'}</div></fieldset>`).join("")}</div><button>Salva grant</button></form>`;
2389
+ }
2390
+ function permissionLevelLabel(value) {
2391
+ return value === 10 ? "Lettura" : value === 20 ? "Scrittura" : value === 30 ? "Valore legacy (30)" : `Livello ${value ?? "non definito"}`;
2392
+ }
2393
+ function rolePermissionLevel(value) {
2394
+ return value === void 0 || value === null || value === "" ? permissionLevelLabel(void 0) : permissionLevelLabel(Number(value));
2395
+ }
2396
+ function roleAssignmentEditor(roles, assignedRoles, groups) {
2397
+ const catalogMenus = new Map(groups.flatMap((group) => group.menus.map((menu) => [menu.codiceMenu, menu])));
2398
+ return `<form id="roles" class="user-editor-section roles-editor" data-user-panel="roles" role="tabpanel" aria-labelledby="user-tab-roles"><h3>Ruoli ${helpDot("role-assignment")}</h3><p class="form-help">Salvare sostituisce i ruoli dell'utente (non i grant diretti).</p><div class="role-assignment-list">${roles.map((role) => {
2399
+ const roleMenus = role.menu ?? [];
2400
+ const assigned = (assignedRoles ?? []).some((current) => current.codiceRuolo === role.codiceRuolo);
2401
+ const groupedMenus = groups.map((group) => ({ group, entries: group.menus.map((menu) => ({ menu, assignment: roleMenus.find((item) => item.codiceMenu === menu.codiceMenu) })).filter((entry) => entry.assignment) })).filter((entry) => entry.entries.length > 0);
2402
+ const missingMenus = roleMenus.filter((item) => !catalogMenus.has(item.codiceMenu));
2403
+ return `<article class="role-assignment-card"><div class="role-assignment-heading"><label class="check"><input type="checkbox" name="role" value="${escapeHtml(role.codiceRuolo)}" ${assigned ? "checked" : ""}><span><strong>${escapeHtml(role.descrizioneRuolo)}</strong><small>Codice ruolo: ${escapeHtml(role.codiceRuolo)}</small></span></label><span class="role-menu-count">${roleMenus.length} menu</span></div><div class="role-permission-groups">${groupedMenus.map(({ group, entries }) => `<section class="role-permission-group"><div class="role-permission-group-heading"><strong>${escapeHtml(group.descrizioneGruppo)}</strong>${groupMetadata(group)}</div>${entries.map(({ menu, assignment }) => `<div class="role-permission-entry ${menu.enabled === false ? "is-disabled" : ""}"><div><strong>${escapeHtml(menu.descrizioneMenu)}</strong>${menuMetadata(menu)}</div><span class="permission-level">${escapeHtml(rolePermissionLevel(assignment?.tipoAbilitazione))}</span></div>`).join("")}</section>`).join("") || '<p class="form-help">Questo ruolo non contiene menu attivi nel catalogo.</p>'}${missingMenus.length ? `<section class="role-permission-group missing-role-menus"><strong>Menu non pi\xF9 presenti nel catalogo</strong>${missingMenus.map((menu) => `<div class="role-permission-entry"><div><strong>${escapeHtml(menu.codiceMenu)}</strong><p class="entity-note">Il menu \xE8 ancora associato al ruolo ma non \xE8 disponibile nel catalogo corrente.</p></div><span class="permission-level">${escapeHtml(rolePermissionLevel(menu.tipoAbilitazione))}</span></div>`).join("")}</section>` : ""}</div></article>`;
2404
+ }).join("")}</div><button>Salva ruoli</button></form>`;
2405
+ }
2406
+ function roleOverview(role, groups) {
2407
+ const roleMenus = role.menu ?? [];
2408
+ const assignments = new Map(roleMenus.map((item) => [item.codiceMenu, item]));
2409
+ const knownMenus = new Set(groups.flatMap((group) => group.menus.map((menu) => menu.codiceMenu)));
2410
+ const tree = groups.map((group) => ({ group, entries: group.menus.filter((menu) => assignments.has(menu.codiceMenu)) })).filter((node) => node.entries.length > 0);
2411
+ const orphans = roleMenus.filter((item) => !knownMenus.has(item.codiceMenu));
2412
+ const groupNodes = tree.map(({ group, entries }) => `<li class="role-tree-group ${group.enabled === false ? "is-disabled" : ""}"><div class="role-tree-group-head"><strong>${escapeHtml(group.descrizioneGruppo)}</strong><span class="role-tree-count">${entries.length}</span></div><ul class="role-tree-menus">${entries.map((menu) => `<li class="role-tree-menu ${menu.enabled === false ? "is-disabled" : ""}"><span class="role-tree-menu-name">${escapeHtml(menu.descrizioneMenu)}</span><span class="permission-level">${escapeHtml(rolePermissionLevel(assignments.get(menu.codiceMenu)?.tipoAbilitazione))}</span></li>`).join("")}</ul></li>`).join("");
2413
+ const orphanNode = orphans.length ? `<li class="role-tree-group is-orphan"><div class="role-tree-group-head"><strong>Menu non pi\xF9 nel catalogo</strong><span class="role-tree-count">${orphans.length}</span></div><ul class="role-tree-menus">${orphans.map((item) => `<li class="role-tree-menu"><span class="role-tree-menu-name"><code>${escapeHtml(item.codiceMenu)}</code></span><span class="permission-level">${escapeHtml(rolePermissionLevel(item.tipoAbilitazione))}</span></li>`).join("")}</ul></li>` : "";
2414
+ return `<article class="role-card">
2415
+ <div class="role-card-head">
2416
+ <div class="role-card-title">
2417
+ <strong>${escapeHtml(role.descrizioneRuolo)}</strong>
2418
+ <span class="role-card-meta">Codice ${escapeHtml(role.codiceRuolo)} &middot; ${roleMenus.length} menu</span>
2419
+ </div>
2420
+ <div class="role-card-actions">
2421
+ <button type="button" class="secondary" data-role="${escapeHtml(role.codiceRuolo)}">Modifica</button>
2422
+ <button type="button" class="danger-button" data-role-delete="${escapeHtml(role.codiceRuolo)}">Elimina</button>
2423
+ </div>
2424
+ </div>
2425
+ ${roleMenus.length ? `<details class="role-card-menus"><summary>Abilitazioni per menu (${roleMenus.length})</summary><ul class="role-tree">${groupNodes}${orphanNode}</ul></details>` : '<p class="role-card-empty">Nessun menu assegnato.</p>'}
2426
+ </article>`;
2427
+ }
2428
+ function roleMenuEditorTable(group, role) {
2429
+ const roleMenus = role?.menu ?? [];
2430
+ const options = group.menus.map((menu) => {
2431
+ const assignment = roleMenus.find((item) => item.codiceMenu === menu.codiceMenu);
2432
+ const level = assignment?.tipoAbilitazione ?? 10;
2433
+ return `<li class="role-menu-option ${menu.enabled === false ? "is-disabled" : ""}"><label class="check"><input type="checkbox" name="menu" value="${escapeHtml(menu.codiceMenu)}" ${assignment ? "checked" : ""}><span>${escapeHtml(menu.descrizioneMenu)}</span></label><label class="role-menu-level"><span class="sr-only">Livello per ${escapeHtml(menu.descrizioneMenu)}</span><select name="menu-level:${escapeHtml(menu.codiceMenu)}" ${assignment ? "" : "disabled"}>${permissionSelectOptions(level)}</select></label></li>`;
2434
+ }).join("");
2435
+ return `<section class="role-menu-group ${group.enabled === false ? "is-disabled" : ""}">
2436
+ <div class="role-menu-group-head"><strong>${escapeHtml(group.descrizioneGruppo)}</strong>${group.enabled === false ? '<span class="muted">Gruppo disabilitato</span>' : ""}</div>
2437
+ <ul class="role-menu-options">${options || '<li class="role-menu-empty muted">Nessun menu in questo gruppo.</li>'}</ul>
2438
+ </section>`;
2439
+ }
2440
+ function updateLoadingState(active) {
2441
+ pendingNetworkRequests = Math.max(0, pendingNetworkRequests + (active ? 1 : -1));
2442
+ if (booting) {
2443
+ return;
2444
+ }
2445
+ const overlay = byId("loading-overlay");
2446
+ if (pendingNetworkRequests > 0) {
2447
+ if (loadingHideTimer !== void 0) {
2448
+ window.clearTimeout(loadingHideTimer);
2449
+ loadingHideTimer = void 0;
2450
+ }
2451
+ if (loadingRevealTimer === void 0 && overlay.hidden) {
2452
+ loadingRevealTimer = window.setTimeout(() => {
2453
+ loadingRevealTimer = void 0;
2454
+ if (pendingNetworkRequests > 0) {
2455
+ overlay.hidden = false;
2456
+ loaderVisibleSince = Date.now();
2457
+ byId("app").setAttribute("aria-busy", "true");
2458
+ }
2459
+ }, 140);
2460
+ }
2461
+ return;
2462
+ }
2463
+ if (loadingRevealTimer !== void 0) {
2464
+ window.clearTimeout(loadingRevealTimer);
2465
+ loadingRevealTimer = void 0;
2466
+ }
2467
+ const hide = () => {
2468
+ loadingHideTimer = void 0;
2469
+ overlay.hidden = true;
2470
+ byId("app").removeAttribute("aria-busy");
2471
+ };
2472
+ const remainingVisibleMs = 280 - (Date.now() - loaderVisibleSince);
2473
+ if (!overlay.hidden && remainingVisibleMs > 0) {
2474
+ loadingHideTimer = window.setTimeout(hide, remainingVisibleMs);
2475
+ } else {
2476
+ hide();
2477
+ }
2478
+ }
2479
+ function finishBoot(view) {
2480
+ booting = false;
2481
+ const overlay = byId("loading-overlay");
2482
+ overlay.hidden = true;
2483
+ byId("app").removeAttribute("aria-busy");
2484
+ byId("login").hidden = view !== "login";
2485
+ byId("console").hidden = view !== "console";
2486
+ }
2487
+ function errorMessage(error) {
2488
+ if (!(error instanceof Error)) return "Operazione non riuscita.";
2489
+ const extra = error;
2490
+ const code3 = typeof extra.code === "string" ? extra.code : void 0;
2491
+ const details = Array.isArray(extra.details) ? extra.details.map((item) => String(item)) : [];
2492
+ const detailSuffix = details.length ? ` (${details.join("; ")})` : "";
2493
+ const message = code3 && !error.message.startsWith(`${code3}:`) ? `${code3}: ${error.message}` : error.message;
2494
+ return `${message}${detailSuffix}`;
2495
+ }
2496
+ function handleAction(action) {
2497
+ void action().catch((error) => showNotice(errorMessage(error), true));
2498
+ }
2499
+ function render(markup) {
2500
+ closeHelp(false);
2501
+ byId("view").innerHTML = markup;
2502
+ }
2503
+ function setActiveNavigation(view) {
2504
+ document.querySelectorAll("#nav [data-view]").forEach((link) => {
2505
+ if (link.dataset.view === view) {
2506
+ link.setAttribute("aria-current", "page");
2507
+ } else {
2508
+ link.removeAttribute("aria-current");
2509
+ }
2510
+ });
2511
+ document.querySelectorAll("#nav details[data-nav-group]").forEach((branch) => {
2512
+ if (branch.querySelector(`[data-view="${view}"]`)) branch.open = true;
2513
+ });
2514
+ }
2515
+ function routePath(view) {
2516
+ return `${consoleBasePath}/${routeSegment[view]}`;
2517
+ }
2518
+ function viewFromLocation() {
2519
+ const relativePath = window.location.pathname.slice(consoleBasePath.length).replace(/^\/+|\/+$/g, "");
2520
+ const decoded = decodeURIComponent(relativePath);
2521
+ return routeView[decoded] ?? parentDefaultView[decoded] ?? "users";
2522
+ }
2523
+ async function navigate(view, replace = false) {
2524
+ if (!isViewAllowed(view)) {
2525
+ view = "users";
2526
+ replace = true;
2527
+ }
2528
+ const destination = routePath(view);
2529
+ if (window.location.pathname !== destination) {
2530
+ window.history[replace ? "replaceState" : "pushState"]({ view }, "", destination);
2531
+ }
2532
+ if (view === "users") usersPage = 0;
2533
+ await show(view);
2534
+ }
2535
+ function requireConsoleAccess() {
2536
+ if (!currentUser?.flagSuper && !currentUser?.flagAdmin) {
2537
+ throw new Error("La console richiede un superutente o un admin Accessi.");
2538
+ }
2539
+ }
2540
+ function isViewAllowed(view) {
2541
+ if (!currentUser) return false;
2542
+ if (currentUser.flagAdmin) return true;
2543
+ return view === "users";
2544
+ }
2545
+ function applyRoleNavigation() {
2546
+ document.querySelectorAll("#nav [data-view]").forEach((link) => {
2547
+ const view = link.dataset.view;
2548
+ const item = link.closest("li") ?? link;
2549
+ item.hidden = !isViewAllowed(view);
2550
+ });
2551
+ document.querySelectorAll("#nav details[data-nav-group]").forEach((branch) => {
2552
+ const hasVisible = Array.from(branch.querySelectorAll("[data-view]")).some((link) => !link.closest("li").hidden);
2553
+ branch.hidden = !hasVisible;
2554
+ });
2555
+ }
2556
+ function formValues(form) {
2557
+ return Object.fromEntries(new FormData(form).entries());
2558
+ }
2559
+ function eventForm(event) {
2560
+ return event.currentTarget;
2561
+ }
2562
+ function federatedIdentityFields(providers) {
2563
+ return `<label>Provider ${helpDot("sso-provider")}<select name="provider" required><option value="">Seleziona un provider</option>${providers.filter((provider) => provider.active).map((provider) => `<option value="${escapeHtml(provider.provider)}">${escapeHtml(provider.description)} (${escapeHtml(provider.provider)})</option>`).join("")}</select><span class="form-help">Deve corrispondere alla configurazione del backend.</span></label>
2564
+ <label>ID utente del provider (subject) ${helpDot("sso-subject")}<input name="subject" required><span class="form-help">Identificatore stabile (es. <code>oid</code> Azure, <code>sub</code> Google/OIDC, <code>NameID</code> SAML). Mai l'email: apri la guida.</span></label>
2565
+ <label>Nota ${helpDot("sso-note")}<input name="note"><span class="form-help">Nota interna, non inviata al provider.</span></label>`;
2566
+ }
2567
+ function federatedIdentitySection(identities, providers) {
2568
+ return `<section class="user-editor-section sso-identity-section" data-user-panel="sso" role="tabpanel" aria-labelledby="user-tab-sso"><div class="section-heading"><div><p class="eyebrow">Autenticazione esterna</p><h3>Identit\xE0 SSO</h3></div><span class="muted">Collegamenti verificati dal backend</span></div><p class="form-help">Disabilita conserva lo storico; elimina rimuove il collegamento (l'utente resta).</p><div class="sso-identity-list">${identities.map((identity) => `<article class="sso-identity-item ${identity.active ? "" : "is-disabled"}"><div class="sso-identity-data"><strong>${escapeHtml(identity.provider)}</strong><dl class="entity-metadata"><div><dt>Subject</dt><dd>${escapeHtml(identity.subject)}</dd></div><div><dt>Stato</dt><dd>${identity.active ? "Abilitata" : "Disabilitata"}</dd></div></dl>${identity.note ? `<p class="entity-note"><strong>Nota</strong>${escapeHtml(identity.note)}</p>` : ""}</div><div class="sso-identity-actions"><button type="button" class="secondary" data-identity-toggle="${escapeHtml(identity.identityKey)}" data-active="${String(!identity.active)}">${identity.active ? "Disabilita" : "Abilita"}</button><button type="button" class="icon-button danger-button" data-identity-delete="${escapeHtml(identity.identityKey)}" aria-label="Elimina definitivamente il collegamento SSO ${escapeHtml(identity.provider)}" title="Elimina definitivamente il collegamento SSO"><span aria-hidden="true">&#128465;</span></button></div></article>`).join("") || '<p class="muted">Nessun collegamento SSO.</p>'}</div>${providers.some((provider) => provider.active) ? `<form id="link-sso" class="sso-link-form">${federatedIdentityFields(providers)}<button>Collega SSO verificato</button></form>` : '<p class="form-help">Non ci sono provider SSO attivi. Censiscine uno nella sezione SSO prima di creare collegamenti.</p>'}</section>`;
2569
+ }
2570
+ function loginAsPanel(user) {
2571
+ return `<form id="login-as" class="user-editor-section" data-user-panel="loginAs" role="tabpanel" aria-labelledby="user-tab-loginAs">
2572
+ <div class="section-heading"><div><p class="eyebrow">Accesso temporaneo</p><h3>Login as ${helpDot("user-login-as")}</h3></div><span class="muted">Solo superutenti e admin</span></div>
2573
+ <div class="login-as-info"><strong>Prima di generare l'URL</strong><ul><li>Il token ha la <strong>stessa durata della sessione Accessi standard</strong> (<code>jwtOptions.expiresIn</code>) e permette di entrare nell'applicazione come <strong>${escapeHtml(user.email)}</strong>.</li><li>\xC8 una credenziale: chiunque abbia l'URL pu\xF2 usarlo fino alla scadenza. Non condividerlo e non salvarlo.</li><li>Il token viene allegato come <code>?loginAsToken=</code> all'URL indicato. L'applicazione di destinazione decide come consumarlo.</li><li>L'operazione \xE8 consentita solo a superutenti e admin, nel rispetto della gerarchia ADMIN &gt; SUPER.</li></ul></div>
2574
+ <label>URL dell'applicazione ${helpDot("login-as-url")}<input name="url" type="url" placeholder="https://app.example.com/" maxlength="2048" required><span class="form-help">URL assoluto http/https. Eventuali query o fragment esistenti vengono conservati.</span></label>
2575
+ <button type="submit">Genera URL di accesso</button>
2576
+ <div id="login-as-result" class="token-reveal" hidden><code id="login-as-url"></code><a id="login-as-open" class="button-link" href="#" target="_blank" rel="noopener noreferrer">Apri</a><button id="login-as-copy" type="button">Copia URL</button></div>
2577
+ </form>`;
2578
+ }
2579
+ function userDetailTabs(hasSso, hasLoginAs) {
2580
+ const tabs = [
2581
+ ...hasSso ? [{ id: "sso", label: "Identit\xE0 SSO" }] : [],
2582
+ { id: "profile", label: "Profilo e accesso" },
2583
+ { id: "roles", label: "Ruoli" },
2584
+ { id: "grants", label: "Grant diretti" },
2585
+ ...hasLoginAs ? [{ id: "loginAs", label: "Login as" }] : []
2586
+ ];
2587
+ return `<div class="user-tabs" role="tablist" aria-label="Sezioni utente">${tabs.map((tab) => `<button id="user-tab-${tab.id}" type="button" role="tab" aria-controls="user-panel-${tab.id}" data-user-tab="${tab.id}">${tab.label}</button>`).join("")}</div>`;
2588
+ }
2589
+ function activateUserTab(tab) {
2590
+ document.querySelectorAll("[data-user-tab]").forEach((button) => {
2591
+ const selected = button.dataset.userTab === tab;
2592
+ button.setAttribute("aria-selected", String(selected));
2593
+ button.tabIndex = selected ? 0 : -1;
2594
+ });
2595
+ document.querySelectorAll("[data-user-panel]").forEach((panel) => {
2596
+ const selected = panel.dataset.userPanel === tab;
2597
+ panel.hidden = !selected;
2598
+ if (!panel.id) panel.id = `user-panel-${panel.dataset.userPanel}`;
2599
+ document.querySelector(`[data-user-tab="${panel.dataset.userPanel}"]`)?.setAttribute("aria-controls", panel.id);
2600
+ });
2601
+ }
2602
+ function selectedNumbers(form, name) {
2603
+ return Array.from(new FormData(form).getAll(name), (value) => Number(value));
2604
+ }
2605
+ async function loadUsers() {
2606
+ return result(await getUsers({ includeGrants: true }));
2607
+ }
2608
+ async function loadUsersPage() {
2609
+ const response = await getUsers({
2610
+ includeGrants: true,
2611
+ limit: USERS_PAGE_SIZE,
2612
+ offset: usersPage * USERS_PAGE_SIZE
2613
+ });
2614
+ const users = result(response);
2615
+ const headers = response.headers;
2616
+ const totalHeader = headers?.get?.("X-Total-Count") ?? null;
2617
+ const parsed = totalHeader !== null && totalHeader !== "" ? Number(totalHeader) : Number.NaN;
2618
+ const total = Number.isFinite(parsed) ? parsed : usersPage * USERS_PAGE_SIZE + users.length;
2619
+ return { users, total };
2620
+ }
2621
+ function pageNumbers(current, total) {
2622
+ if (total <= 7) {
2623
+ return Array.from({ length: total }, (_, index) => index + 1);
2624
+ }
2625
+ const wanted = /* @__PURE__ */ new Set([1, total, current, current - 1, current + 1]);
2626
+ const sorted = Array.from(wanted).filter((page) => page >= 1 && page <= total).sort((a, b) => a - b);
2627
+ const result2 = [];
2628
+ let previous = 0;
2629
+ for (const page of sorted) {
2630
+ if (previous !== 0 && page - previous > 1) {
2631
+ result2.push("gap");
2632
+ }
2633
+ result2.push(page);
2634
+ previous = page;
2635
+ }
2636
+ return result2;
2637
+ }
2638
+ function pagerMarkup(totalPages) {
2639
+ const context = `<span class="pager-context">${usersTotal} utent${usersTotal === 1 ? "e" : "i"}${totalPages > 1 ? ` \xB7 pagina ${usersPage + 1} di ${totalPages}` : ""}</span>`;
2640
+ if (totalPages <= 1) {
2641
+ return `<nav class="pager" aria-label="Paginazione utenti">${context}</nav>`;
2642
+ }
2643
+ const numbers = pageNumbers(usersPage + 1, totalPages).map((page) => page === "gap" ? '<span class="pager-gap" aria-hidden="true">&hellip;</span>' : `<button type="button" class="pager-item" data-goto="${page}"${page === usersPage + 1 ? ' aria-current="page"' : ""}>${page}</button>`).join("");
2644
+ const controls = `<span class="pager-controls"><button type="button" class="pager-item pager-nav" data-goto="prev"${usersPage === 0 ? " disabled" : ""} aria-label="Pagina precedente">&lsaquo;</button>` + numbers + `<button type="button" class="pager-item pager-nav" data-goto="next"${usersPage >= totalPages - 1 ? " disabled" : ""} aria-label="Pagina successiva">&rsaquo;</button></span>`;
2645
+ return `<nav class="pager" aria-label="Paginazione utenti">${controls}${context}</nav>`;
2646
+ }
2647
+ function rolesCell(codiceUtente, userGrants) {
2648
+ const labels = (userGrants?.ruoli ?? []).map((role) => role.descrizioneRuolo?.trim() || (role.codiceRuolo ? `#${role.codiceRuolo}` : "")).filter((label) => label.length > 0);
2649
+ if (labels.length === 0) {
2650
+ return '<span class="muted">&mdash;</span>';
2651
+ }
2652
+ const limit = 2;
2653
+ if (labels.length <= limit) {
2654
+ return `<span class="roles-list">${labels.map(escapeHtml).join(", ")}</span>`;
2655
+ }
2656
+ const hiddenCount = labels.length - limit;
2657
+ return `<span class="roles-cell" data-roles-cell="${codiceUtente}"><span class="roles-list" data-roles-collapsed>${labels.slice(0, limit).map(escapeHtml).join(", ")}</span><span class="roles-list" data-roles-full hidden>${labels.map(escapeHtml).join(", ")}</span><button type="button" class="link-button" data-roles-toggle="${codiceUtente}" data-roles-more="${hiddenCount}" aria-expanded="false">altre ${hiddenCount}</button></span>`;
2658
+ }
2659
+ function setRolesExpanded(cell, expanded) {
2660
+ cell.querySelector("[data-roles-collapsed]")?.toggleAttribute("hidden", expanded);
2661
+ cell.querySelector("[data-roles-full]")?.toggleAttribute("hidden", !expanded);
2662
+ const button = cell.querySelector("[data-roles-toggle]");
2663
+ if (button) {
2664
+ button.setAttribute("aria-expanded", String(expanded));
2665
+ button.textContent = expanded ? "mostra meno" : `altre ${button.dataset.rolesMore ?? ""}`;
2666
+ }
2667
+ }
2668
+ function bindRolesToggles() {
2669
+ document.querySelectorAll("[data-roles-toggle]").forEach((button) => {
2670
+ button.onclick = () => {
2671
+ const cell = button.closest("[data-roles-cell]");
2672
+ if (!cell) return;
2673
+ const willExpand = button.getAttribute("aria-expanded") !== "true";
2674
+ document.querySelectorAll("[data-roles-cell]").forEach((other) => {
2675
+ if (other !== cell) setRolesExpanded(other, false);
2676
+ });
2677
+ setRolesExpanded(cell, willExpand);
2678
+ };
2679
+ });
2680
+ }
2681
+ async function applyAdminBootstrapVisibility() {
2682
+ const panel = document.getElementById("bootstrap-admin-panel");
2683
+ if (!panel) return;
2684
+ try {
2685
+ const status = result(await getAdminBootstrapStatus());
2686
+ panel.hidden = status.enabled !== true;
2687
+ } catch {
2688
+ panel.hidden = true;
2689
+ }
2690
+ }
2691
+ async function detectFederatedAuthentication() {
2692
+ if (!currentUser) return;
2693
+ try {
2694
+ await getFederatedIdentities(currentUser.codiceUtente);
2695
+ federatedAuthenticationAvailable = true;
2696
+ } catch {
2697
+ federatedAuthenticationAvailable = false;
2698
+ }
2699
+ }
2700
+ async function loginView(event) {
2701
+ event.preventDefault();
2702
+ try {
2703
+ const values = formValues(event.currentTarget);
2704
+ byId("login-error").textContent = "";
2705
+ const response = result(await login({ email: values.email ?? "", password: values.password || void 0 }));
2706
+ await completeLoginStep(response);
2707
+ } catch (error) {
2708
+ byId("login-error").textContent = error instanceof Error ? error.message : "Login non riuscito.";
2709
+ }
2710
+ }
2711
+ var pendingChallenge = null;
2712
+ var resendTimer;
2713
+ function resetLogin() {
2714
+ pendingChallenge = null;
2715
+ if (resendTimer) clearInterval(resendTimer);
2716
+ byId("login-form").reset();
2717
+ byId("two-factor-form").reset();
2718
+ byId("login-form").hidden = false;
2719
+ byId("two-factor-form").hidden = true;
2720
+ byId("password-field").hidden = true;
2721
+ byId("password-field").querySelector("input").disabled = true;
2722
+ byId("restart-login").hidden = true;
2723
+ byId("login-submit").textContent = "Continua";
2724
+ byId("login-error").textContent = "";
2725
+ }
2726
+ function showTwoFactor(challenge) {
2727
+ pendingChallenge = challenge.challengeId;
2728
+ byId("login-form").hidden = true;
2729
+ const password = byId("password-field").querySelector("input");
2730
+ password.value = "";
2731
+ password.disabled = true;
2732
+ byId("two-factor-form").hidden = false;
2733
+ byId("restart-login").hidden = false;
2734
+ byId("two-factor-form").reset();
2735
+ byId("two-factor-form").querySelector("input").focus();
2736
+ const expires = challenge.expiresAt ? new Date(challenge.expiresAt).toLocaleTimeString() : "";
2737
+ byId("two-factor-status").textContent = expires ? `Codice inviato. Scade alle ${expires}.` : "Codice inviato. Controlla la tua email.";
2738
+ if (resendTimer) clearInterval(resendTimer);
2739
+ const availableAt = Date.now() + challenge.resendAfterSeconds * 1e3;
2740
+ const update = () => {
2741
+ const seconds = Math.max(0, Math.ceil((availableAt - Date.now()) / 1e3));
2742
+ const button = byId("resend-code");
2743
+ button.disabled = seconds > 0;
2744
+ button.textContent = seconds ? `Reinvia tra ${seconds}s` : "Invia un nuovo codice";
2745
+ if (!seconds && resendTimer) clearInterval(resendTimer);
2746
+ };
2747
+ update();
2748
+ resendTimer = setInterval(update, 1e3);
2749
+ }
2750
+ async function completeLoginStep(response) {
2751
+ if (response.challenge) {
2752
+ showTwoFactor(response.challenge);
2753
+ return;
2754
+ }
2755
+ if (response.passwordRequired) {
2756
+ byId("password-field").hidden = false;
2757
+ const input = byId("password-field").querySelector("input");
2758
+ input.disabled = false;
2759
+ input.required = true;
2760
+ input.focus();
2761
+ byId("restart-login").hidden = false;
2762
+ byId("login-submit").textContent = "Accedi";
2763
+ return;
2764
+ }
2765
+ if (response.token?.value) {
2766
+ token = response.token.value;
2767
+ sessionStorage.setItem("accessi-console-token", token);
2768
+ setAccessiConsoleToken(token);
2769
+ } else if (!cookieMode) {
2770
+ throw new Error("Risposta di autenticazione incompleta.");
2771
+ }
2772
+ resetLogin();
2773
+ await bootstrap();
2774
+ }
2775
+ async function bootstrap() {
2776
+ const hadToken = token !== null;
2777
+ try {
2778
+ setAccessiConsoleToken(token);
2779
+ if (!token && !cookieMode) throw new Error("Inserisci le credenziali amministrative.");
2780
+ const payload = result(await getUserByToken({ token: token ?? "" }));
2781
+ currentUser = payload.userData;
2782
+ requireConsoleAccess();
2783
+ applyRoleNavigation();
2784
+ await detectFederatedAuthentication();
2785
+ const roleLabel = currentUser.flagSuper && currentUser.flagAdmin ? "super + admin" : currentUser.flagAdmin ? "admin" : "superutente";
2786
+ byId("who").textContent = `${currentUser.email} - ${roleLabel}`;
2787
+ byId("sso-navigation").hidden = !federatedAuthenticationAvailable;
2788
+ finishBoot("console");
2789
+ await navigate(viewFromLocation(), window.location.pathname === consoleBasePath);
2790
+ } catch (error) {
2791
+ token = null;
2792
+ currentUser = null;
2793
+ setAccessiConsoleToken(null);
2794
+ federatedAuthenticationAvailable = false;
2795
+ sessionStorage.removeItem("accessi-console-token");
2796
+ finishBoot("login");
2797
+ byId("login-error").textContent = cookieMode && !hadToken ? "" : error instanceof Error ? error.message : "Sessione non valida.";
2798
+ }
2799
+ }
2800
+ async function exchangeConsoleEntry(ticket) {
2801
+ try {
2802
+ const session = result(await exchangeConsoleEntryTicket({ ticket }));
2803
+ const value = session.token?.value;
2804
+ if (typeof value === "string" && value !== "") {
2805
+ token = value;
2806
+ sessionStorage.setItem("accessi-console-token", value);
2807
+ setAccessiConsoleToken(value);
2808
+ } else if (!cookieMode) {
2809
+ throw new Error("Apertura automatica della console non riuscita.");
2810
+ }
2811
+ await bootstrap();
2812
+ } catch (error) {
2813
+ token = null;
2814
+ setAccessiConsoleToken(null);
2815
+ sessionStorage.removeItem("accessi-console-token");
2816
+ finishBoot("login");
2817
+ byId("login-error").textContent = error instanceof Error ? error.message : "Apertura automatica della console non riuscita.";
2818
+ }
2819
+ }
2820
+ async function showUsers() {
2821
+ const { users, total } = await loadUsersPage();
2822
+ usersTotal = total;
2823
+ const totalPages = Math.max(1, Math.ceil(total / USERS_PAGE_SIZE));
2824
+ if (usersPage >= totalPages) usersPage = totalPages - 1;
2825
+ render(`<div class="toolbar"><button id="new-local">Nuovo utente locale</button>${federatedAuthenticationAvailable ? '<button id="new-sso">Nuovo utente SSO</button>' : ""}<button id="reload-users" class="secondary">Aggiorna</button><button id="reset-legacy" class="secondary">Invia reset password legacy</button></div>
2826
+ <div class="pager-row pager-row-top">${pagerMarkup(totalPages)}</div>
2827
+ <table class="users-table"><thead><tr><th>Utente</th><th>Stato</th><th>Accesso</th><th>2FA</th><th>Ruoli</th><th aria-label="Azioni"></th></tr></thead><tbody>
2828
+ ${users.map(({ utente, userGrants }) => `<tr><td>${escapeHtml(utente.email)}<br><span class="muted">${escapeHtml(utente.nome)} ${escapeHtml(utente.cognome)}</span></td><td>${escapeHtml(utente.statoRegistrazione)}</td><td>${utente.passwordlessLoginEnabled ? "codice email" : utente.passwordLoginEnabled === false ? "solo SSO" : "password"}</td><td>${utente.flagDueFattori ? "Attiva" : "Disattiva"}</td><td class="roles-col">${rolesCell(utente.codiceUtente, userGrants)}</td><td><div class="row-actions"><button type="button" class="secondary" data-login-as="${utente.codiceUtente}"${canManageUser(utente) ? "" : ' disabled title="Non puoi impersonare un utente di rango superiore" aria-disabled="true"'}>Login as</button><button data-user="${utente.codiceUtente}"${canManageUser(utente) ? "" : ' disabled title="Non puoi gestire un utente di rango superiore" aria-disabled="true"'}>Gestisci</button></div></td></tr>`).join("") || '<tr><td colspan="6" class="muted">Nessun utente.</td></tr>'}
2829
+ </tbody></table><div class="pager-row pager-row-bottom">${pagerMarkup(totalPages)}</div>`);
2830
+ byId("new-local").onclick = () => showLocalUserForm();
2831
+ if (federatedAuthenticationAvailable) byId("new-sso").onclick = () => handleAction(showSsoUserForm);
2832
+ byId("reload-users").onclick = () => handleAction(() => show("users"));
2833
+ byId("reset-legacy").onclick = () => handleAction(async () => {
2834
+ if (!window.confirm("Inviare l email di reset a tutti gli utenti con password non ancora migrata? Richiede un servizio email configurato.")) return;
2835
+ await forcePasswordResetLegacy();
2836
+ showNotice("Email di reset inviate agli utenti con password legacy.");
2837
+ });
2838
+ document.querySelectorAll("[data-goto]").forEach((button) => {
2839
+ button.onclick = () => handleAction(async () => {
2840
+ const target = button.dataset.goto;
2841
+ const nextPage = target === "prev" ? usersPage - 1 : target === "next" ? usersPage + 1 : Number(target) - 1;
2842
+ if (!Number.isInteger(nextPage) || nextPage < 0 || nextPage >= totalPages || nextPage === usersPage) return;
2843
+ usersPage = nextPage;
2844
+ await show("users");
2845
+ });
2846
+ });
2847
+ document.querySelectorAll("[data-user]").forEach((button) => {
2848
+ button.onclick = () => handleAction(() => showUser(button.dataset.user ?? ""));
2849
+ });
2850
+ document.querySelectorAll("[data-login-as]").forEach((button) => {
2851
+ button.onclick = () => handleAction(async () => {
2852
+ await showUser(button.dataset.loginAs ?? "");
2853
+ activateUserTab("loginAs");
2854
+ });
2855
+ });
2856
+ bindRolesToggles();
2857
+ }
2858
+ function authenticationPolicyFields(user) {
2859
+ return `<fieldset><legend>Verifica dell'accesso (opzionale)</legend>
2860
+ <label class="check"><input name="flagDueFattori" type="checkbox" ${user.flagDueFattori ? "checked" : ""}> Richiedi un codice via email (2FA) ${helpDot("user-2fa")}</label>
2861
+ <p class="form-help">Se attiva, il codice \xE8 richiesto dopo password o SSO.</p>
2862
+ <label class="check"><input name="passwordlessLoginEnabled" type="checkbox" ${user.passwordlessLoginEnabled ? "checked" : ""} ${user.flagDueFattori ? "" : "disabled"}> Consenti accesso con il solo codice email ${helpDot("user-passwordless")}</label>
2863
+ <p class="form-help">Accesso senza password (richiede il codice email).</p>
2864
+ </fieldset>`;
2865
+ }
2866
+ function canManageSuperFlag() {
2867
+ return Boolean(currentUser?.flagSuper || currentUser?.flagAdmin);
2868
+ }
2869
+ function canManageAdminFlag() {
2870
+ return Boolean(currentUser?.flagAdmin);
2871
+ }
2872
+ function consoleUserRank(user) {
2873
+ if (user?.flagAdmin) return 2;
2874
+ if (user?.flagSuper) return 1;
2875
+ return 0;
2876
+ }
2877
+ function canManageUser(user) {
2878
+ if (currentUser && user.codiceUtente === currentUser.codiceUtente) return true;
2879
+ return consoleUserRank(currentUser) >= consoleUserRank(user);
2880
+ }
2881
+ function setUserManagementEnabled(enabled) {
2882
+ for (const id of ["disable-user", "purge-user", "save-state", "set-password", "save-profile", "set-gdpr"]) {
2883
+ const element = document.getElementById(id);
2884
+ if (element) element.disabled = !enabled;
2885
+ }
2886
+ for (const selector of ["#roles button", "#grants button", "#link-sso button"]) {
2887
+ document.querySelectorAll(selector).forEach((button) => {
2888
+ button.disabled = !enabled;
2889
+ });
2890
+ }
2891
+ for (const selector of ["#profile", "#password-form", "#roles", "#grants", "#link-sso"]) {
2892
+ document.querySelectorAll(
2893
+ `${selector} input, ${selector} select, ${selector} textarea`
2894
+ ).forEach((field) => {
2895
+ field.disabled = !enabled;
2896
+ });
2897
+ }
2898
+ document.querySelectorAll("[data-identity-toggle], [data-identity-delete]").forEach((button) => {
2899
+ button.disabled = !enabled;
2900
+ });
2901
+ }
2902
+ function privilegeFields(user) {
2903
+ const manageSuper = canManageSuperFlag();
2904
+ const manageAdmin = canManageAdminFlag();
2905
+ if (!manageSuper && !manageAdmin) return "";
2906
+ return `<fieldset><legend>Privilegi Accessi (opzionale)</legend>
2907
+ ${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>` : ""}
2908
+ ${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>` : ""}
2909
+ </fieldset>`;
2910
+ }
2911
+ function privilegePayload(form) {
2912
+ const fields = new FormData(form);
2913
+ const payload = {};
2914
+ if (canManageSuperFlag()) payload.flagSuper = fields.has("flagSuper");
2915
+ if (canManageAdminFlag()) payload.flagAdmin = fields.has("flagAdmin");
2916
+ return payload;
2917
+ }
2918
+ function optionalText(values, key) {
2919
+ if (!(key in values)) return void 0;
2920
+ const raw = (values[key] ?? "").trim();
2921
+ return raw === "" ? null : raw;
2922
+ }
2923
+ function passwordPolicyViolations(password) {
2924
+ const violations = [];
2925
+ if (password.length < 8) violations.push("almeno 8 caratteri");
2926
+ if (password.length > 100) violations.push("al massimo 100 caratteri");
2927
+ if (/\s/.test(password)) violations.push("nessuno spazio");
2928
+ if (!/[A-Z]/.test(password)) violations.push("una lettera maiuscola");
2929
+ if (!/[a-z]/.test(password)) violations.push("una lettera minuscola");
2930
+ if (!/\d/.test(password)) violations.push("una cifra");
2931
+ if (!/[^A-Za-z0-9]/.test(password)) violations.push("un carattere speciale");
2932
+ return violations;
2933
+ }
2934
+ function dateInputValue(value) {
2935
+ const text = typeof value === "string" ? value : "";
2936
+ const match = /^(\d{4}-\d{2}-\d{2})/.exec(text);
2937
+ return match?.[1] ?? "";
2938
+ }
2939
+ function pageRouteSuggestions(groups) {
2940
+ const routes = /* @__PURE__ */ new Map();
2941
+ for (const group of groups) {
2942
+ for (const menu of group.menus) {
2943
+ const value = (menu.pagina ?? "").trim();
2944
+ if (!value) continue;
2945
+ const suggestion = {
2946
+ value,
2947
+ menu: menu.descrizioneMenu || void 0,
2948
+ group: group.descrizioneGruppo || void 0,
2949
+ disabled: menu.enabled === false
2950
+ };
2951
+ const current = routes.get(value);
2952
+ if (!current || current.disabled && !suggestion.disabled) routes.set(value, suggestion);
2953
+ }
2954
+ }
2955
+ return [...routes.values()].sort((a, b) => a.value.localeCompare(b.value, "it"));
2956
+ }
2957
+ function pageDefaultField(user) {
2958
+ const value = user.paginaDefault ?? "";
2959
+ return `<label class="page-default-field">Pagina di default ${helpDot("user-default-page")}
2960
+ <div class="page-combobox" data-page-combobox>
2961
+ <div class="page-combobox-control">
2962
+ ${actionIcon('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>')}
2963
+ <input id="pagina-default" name="paginaDefault" type="text" maxlength="50" value="${escapeHtml(value)}" autocomplete="off" role="combobox" aria-autocomplete="list" aria-expanded="false" aria-controls="pagina-default-listbox" placeholder="Cerca una rotta o scrivi una pagina">
2964
+ <button type="button" class="page-combobox-clear" aria-label="Pulisci la pagina di default" ${value ? "" : "hidden"}>&times;</button>
2965
+ <button type="button" class="page-combobox-toggle" aria-label="Mostra le pagine disponibili" aria-haspopup="listbox">${actionIcon('<path d="m6 9 6 6 6-6"/>')}</button>
2966
+ </div>
2967
+ <ul id="pagina-default-listbox" class="page-combobox-list" role="listbox" aria-label="Pagine disponibili" hidden></ul>
2968
+ </div>
2969
+ <span class="form-help">Scegli una rotta dal catalogo dei menu oppure scrivi un valore libero.</span>
2970
+ </label>`;
2971
+ }
2972
+ function bindPageDefaultCombobox(routes) {
2973
+ const root = document.querySelector("[data-page-combobox]");
2974
+ if (!root) return;
2975
+ const input = root.querySelector('input[name="paginaDefault"]');
2976
+ const list3 = root.querySelector(".page-combobox-list");
2977
+ const clear = root.querySelector(".page-combobox-clear");
2978
+ const toggle = root.querySelector(".page-combobox-toggle");
2979
+ if (!input || !list3) return;
2980
+ let matches = [];
2981
+ let activeIndex = -1;
2982
+ const optionId = (index) => `pagina-default-option-${index}`;
2983
+ const setOpen = (open) => {
2984
+ list3.hidden = !open;
2985
+ input.setAttribute("aria-expanded", String(open));
2986
+ if (!open) {
2987
+ activeIndex = -1;
2988
+ input.removeAttribute("aria-activedescendant");
2989
+ }
2990
+ };
2991
+ const renderMatches = (query) => {
2992
+ const needle = query.trim().toLocaleLowerCase("it");
2993
+ matches = needle === "" ? routes : routes.filter((route) => [route.value, route.menu ?? "", route.group ?? ""].join(" ").toLocaleLowerCase("it").includes(needle));
2994
+ list3.innerHTML = matches.map((route, index) => `<li id="${optionId(index)}" class="page-combobox-option${route.disabled ? " is-disabled" : ""}" role="option" aria-selected="${route.value === input.value ? "true" : "false"}" data-index="${index}"><code>${escapeHtml(route.value)}</code><small>${escapeHtml([route.menu, route.group].filter(Boolean).join(" \xB7 ") || "Rotta del catalogo menu")}${route.disabled ? " \xB7 menu disabilitato" : ""}</small></li>`).join("") || '<li class="page-combobox-empty" role="presentation">Nessuna rotta corrisponde. Puoi comunque lasciare il valore scritto.</li>';
2995
+ activeIndex = -1;
2996
+ input.removeAttribute("aria-activedescendant");
2997
+ };
2998
+ const highlight = () => {
2999
+ list3.querySelectorAll(".page-combobox-option").forEach((option, index) => {
3000
+ option.classList.toggle("is-active", index === activeIndex);
3001
+ });
3002
+ const current = activeIndex >= 0 ? list3.querySelector(`#${optionId(activeIndex)}`) : null;
3003
+ if (current) {
3004
+ input.setAttribute("aria-activedescendant", optionId(activeIndex));
3005
+ current.scrollIntoView({ block: "nearest" });
3006
+ } else {
3007
+ input.removeAttribute("aria-activedescendant");
3008
+ }
3009
+ };
3010
+ const openList = () => {
3011
+ renderMatches("");
3012
+ setOpen(true);
3013
+ highlight();
3014
+ };
3015
+ const refresh = (query) => {
3016
+ renderMatches(query);
3017
+ setOpen(true);
3018
+ highlight();
3019
+ };
3020
+ const choose = (route) => {
3021
+ input.value = route.value;
3022
+ if (clear) clear.hidden = false;
3023
+ input.focus();
3024
+ setOpen(false);
3025
+ };
3026
+ input.addEventListener("focus", openList);
3027
+ input.addEventListener("input", () => {
3028
+ if (clear) clear.hidden = input.value.length === 0;
3029
+ refresh(input.value);
3030
+ });
3031
+ input.addEventListener("blur", () => {
3032
+ window.setTimeout(() => {
3033
+ if (!root.contains(document.activeElement)) setOpen(false);
3034
+ }, 0);
3035
+ });
3036
+ input.addEventListener("keydown", (event) => {
3037
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3038
+ event.preventDefault();
3039
+ if (list3.hidden) refresh(input.value);
3040
+ if (matches.length === 0) return;
3041
+ activeIndex = event.key === "ArrowDown" ? (activeIndex + 1) % matches.length : activeIndex <= 0 ? matches.length - 1 : activeIndex - 1;
3042
+ highlight();
3043
+ return;
3044
+ }
3045
+ if (event.key === "Enter" && !list3.hidden && activeIndex >= 0) {
3046
+ const route = matches[activeIndex];
3047
+ if (route) {
3048
+ event.preventDefault();
3049
+ choose(route);
3050
+ }
3051
+ return;
3052
+ }
3053
+ if (event.key === "Escape" && !list3.hidden) {
3054
+ event.preventDefault();
3055
+ setOpen(false);
3056
+ return;
3057
+ }
3058
+ if (event.key === "Tab") setOpen(false);
3059
+ });
3060
+ list3.addEventListener("mousedown", (event) => {
3061
+ if (event.target.closest("[data-index]")) event.preventDefault();
3062
+ });
3063
+ list3.addEventListener("click", (event) => {
3064
+ const option = event.target.closest("[data-index]");
3065
+ if (!option) return;
3066
+ const route = matches[Number(option.dataset.index)];
3067
+ if (route) choose(route);
3068
+ });
3069
+ if (clear) {
3070
+ clear.addEventListener("click", () => {
3071
+ input.value = "";
3072
+ clear.hidden = true;
3073
+ input.focus();
3074
+ openList();
3075
+ });
3076
+ }
3077
+ if (toggle) {
3078
+ toggle.addEventListener("click", () => {
3079
+ if (list3.hidden) {
3080
+ input.focus();
3081
+ openList();
3082
+ } else {
3083
+ setOpen(false);
3084
+ }
3085
+ });
3086
+ }
3087
+ }
3088
+ function extendedProfileFields(user) {
3089
+ return `<fieldset><legend>Anagrafica estesa</legend>
3090
+ <label>Cellulare ${helpDot("user-cellulare")}<input name="cellulare" maxlength="30" value="${escapeHtml(user.cellulare ?? "")}"></label>
3091
+ <label>Codice lingua ${helpDot("user-lingua")}<input name="codiceLingua" maxlength="2" value="${escapeHtml(user.codiceLingua ?? "")}"></label>
3092
+ <label>Avatar ${helpDot("user-avatar")}<input name="avatar" maxlength="30" value="${escapeHtml(user.avatar ?? "")}"></label>
3093
+ <label>Scadenza password ${helpDot("user-scadenza-password")}<input name="dataScadenzaPassword" type="date" value="${dateInputValue(user.dataScadenzaPassword)}"></label>
3094
+ <fieldset class="gdpr-status"><legend>GDPR ${helpDot("user-gdpr")}</legend><p class="form-help">${user.flagGdpr ? `Consenso registrato${dateInputValue(user.dataGdpr) ? ` il ${escapeHtml(dateInputValue(user.dataGdpr))}` : ""}.` : "Consenso non ancora registrato."}</p><button type="button" id="set-gdpr" class="secondary">Registra consenso GDPR</button></fieldset>
3095
+ <label>Metadata JSON ${helpDot("user-json")}<textarea name="jsonMetadata" rows="3">${escapeHtml(user.jsonMetadata ?? "")}</textarea></label>
3096
+ </fieldset>`;
3097
+ }
3098
+ function profilePayload(form, codiceUtente) {
3099
+ const values = formValues(form);
3100
+ const fields = new FormData(form);
3101
+ const payload = {
3102
+ codiceUtente,
3103
+ email: values.email ?? "",
3104
+ nome: optionalText(values, "nome"),
3105
+ cognome: optionalText(values, "cognome"),
3106
+ cellulare: optionalText(values, "cellulare"),
3107
+ codiceLingua: optionalText(values, "codiceLingua"),
3108
+ avatar: optionalText(values, "avatar"),
3109
+ paginaDefault: optionalText(values, "paginaDefault"),
3110
+ jsonMetadata: optionalText(values, "jsonMetadata"),
3111
+ dataScadenzaPassword: optionalText(values, "dataScadenzaPassword"),
3112
+ flagDueFattori: fields.has("flagDueFattori"),
3113
+ passwordlessLoginEnabled: fields.has("passwordlessLoginEnabled"),
3114
+ ...privilegePayload(form)
3115
+ };
3116
+ if (federatedAuthenticationAvailable) {
3117
+ payload.passwordLoginEnabled = fields.has("passwordLoginEnabled");
3118
+ }
3119
+ return payload;
3120
+ }
3121
+ function passwordFields() {
3122
+ return `<form id="password-form" class="user-editor-section" data-user-panel="profile" role="tabpanel" aria-labelledby="user-tab-profile">
3123
+ <h3>Password</h3>
3124
+ <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>
3125
+ <button type="submit" id="set-password" class="secondary">Imposta password</button>
3126
+ </form>`;
3127
+ }
3128
+ function showLocalUserForm() {
3129
+ render(`<button id="back">Indietro</button><h2>Nuovo utente locale</h2><p class="muted">Viene inviata l'e-mail per impostare la password.</p>
3130
+ <form id="local-user"><label>Email ${helpDot("user-email")}<input name="email" type="email" required><span class="form-help">Identificativo di accesso; ricever\xE0 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>`);
3131
+ byId("back").onclick = () => show("users");
3132
+ const localUserForm = document.getElementById("local-user");
3133
+ if (localUserForm) localUserForm.onsubmit = (event) => {
3134
+ event.preventDefault();
3135
+ handleAction(async () => {
3136
+ const form = eventForm(event);
3137
+ await createManagedUser({ ...formValues(form), ...privilegePayload(form) });
3138
+ showNotice("Utente creato; inviata la procedura di impostazione password.");
3139
+ await show("users");
3140
+ });
3141
+ };
3142
+ }
3143
+ async function showSsoUserForm() {
3144
+ const providers = result(await getFederatedProviders());
3145
+ if (!providers.some((provider) => provider.active)) {
3146
+ throw new Error("Prima di creare un utente SSO, censisci e abilita almeno un provider SSO.");
3147
+ }
3148
+ render(`<button id="back">Indietro</button><h2>Nuovo utente SSO</h2><p class="muted">Salva solo collegamenti gi\xE0 verificati dal backend.</p>
3149
+ <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>`);
3150
+ byId("back").onclick = () => show("ssoUsers");
3151
+ const ssoUserForm = document.getElementById("sso-user");
3152
+ if (ssoUserForm) ssoUserForm.onsubmit = (event) => {
3153
+ event.preventDefault();
3154
+ handleAction(async () => {
3155
+ const form = eventForm(event);
3156
+ const values = formValues(form);
3157
+ const request = {
3158
+ provider: values.provider ?? "",
3159
+ subject: values.subject ?? "",
3160
+ note: values.note || void 0,
3161
+ passwordLoginEnabled: new FormData(form).has("passwordLoginEnabled"),
3162
+ user: { email: values.email ?? "", nome: values.nome || void 0, cognome: values.cognome || void 0, ...privilegePayload(form) }
3163
+ };
3164
+ await createFederatedUser(request);
3165
+ showNotice("Utente SSO creato e collegamento registrato.");
3166
+ await show(currentUser?.flagAdmin ? "ssoUsers" : "users");
3167
+ });
3168
+ };
3169
+ }
3170
+ async function showSsoProviders() {
3171
+ if (!federatedAuthenticationAvailable) throw new Error("SSO non \xE8 abilitato in questa istanza Accessi.");
3172
+ const providerList = result(await getFederatedProviders());
3173
+ render(`<div class="toolbar"><button id="new-provider">Nuovo provider</button><button id="reload-providers" class="secondary">Aggiorna</button></div>
3174
+ <h2>Provider SSO</h2><p class="muted">La validazione di token, issuer e segreti resta nel backend. Un provider si elimina solo se nessuna identit\xE0 \xE8 collegata.</p>
3175
+ <div class="menu-table-wrap"><table><thead><tr><th>Provider</th><th>Descrizione</th><th>Stato</th><th></th></tr></thead><tbody>${providerList.map((provider) => `<tr class="${provider.active ? "" : "is-disabled"}"><td><code>${escapeHtml(provider.provider)}</code></td><td>${escapeHtml(provider.description)}</td><td>${provider.active ? "Attivo" : "Disabilitato"}</td><td class="row-actions"><button data-provider="${escapeHtml(provider.provider)}">Gestisci</button></td></tr>`).join("") || '<tr><td colspan="4" class="muted">Nessun provider censito.</td></tr>'}</tbody></table></div>`);
3176
+ byId("new-provider").onclick = () => showProviderForm();
3177
+ byId("reload-providers").onclick = () => handleAction(showSsoProviders);
3178
+ document.querySelectorAll("[data-provider]").forEach((button) => {
3179
+ button.onclick = () => showProviderForm(providerList.find((provider) => provider.provider === button.dataset.provider));
3180
+ });
3181
+ }
3182
+ async function showSsoUsers() {
3183
+ if (!federatedAuthenticationAvailable) throw new Error("SSO non \xE8 abilitato in questa istanza Accessi.");
3184
+ const users = await loadUsers();
3185
+ render(`<div class="toolbar"><button id="new-sso">Nuovo utente SSO</button><button id="reload-sso-users" class="secondary">Aggiorna</button></div>
3186
+ <h2>Utenti e identit\xE0 SSO</h2><p class="muted">Gestisci i collegamenti provider/subject gi\xE0 verificati dal backend.</p>
3187
+ <div class="menu-table-wrap"><table><thead><tr><th>Utente</th><th>Accesso</th><th></th></tr></thead><tbody>${users.map(({ utente }) => `<tr><td>${escapeHtml(utente.email)}</td><td>${utente.passwordLoginEnabled === false ? "solo SSO" : "password e SSO"}</td><td class="row-actions"><button data-sso-user="${utente.codiceUtente}">Gestisci identit\xE0</button></td></tr>`).join("") || '<tr><td colspan="3" class="muted">Nessun utente.</td></tr>'}</tbody></table></div>`);
3188
+ byId("new-sso").onclick = () => handleAction(showSsoUserForm);
3189
+ byId("reload-sso-users").onclick = () => handleAction(showSsoUsers);
3190
+ document.querySelectorAll("[data-sso-user]").forEach((button) => {
3191
+ button.onclick = () => handleAction(() => showUser(button.dataset.ssoUser ?? ""));
3192
+ });
3193
+ }
3194
+ function showProviderForm(provider) {
3195
+ const isExisting = provider !== void 0;
3196
+ render(`<button id="back" type="button">Indietro</button><div class="page-header"><div><p class="eyebrow">Catalogo SSO</p><h2>${isExisting ? "Gestisci provider" : "Nuovo provider"}</h2></div><span class="muted">Le configurazioni tecniche restano nel backend</span></div><form id="provider-form"><label>Chiave provider ${helpDot("provider-key")}<input name="provider" value="${escapeHtml(provider?.provider)}" ${isExisting ? "readonly" : ""} required><span class="form-help">Chiave usata dal backend nel payload SSO (es. <code>azure-ad-acme-prod</code>). Non modificabile dopo il censimento.</span></label><label>Descrizione ${helpDot("provider-description")}<input name="description" value="${escapeHtml(provider?.description)}" required><span class="form-help">Nome leggibile per gli amministratori.</span></label><label>Nota ${helpDot("provider-note")}<input name="note" value="${escapeHtml(provider?.note)}"><span class="form-help">Non inserire segreti o dati personali.</span></label>${isExisting ? `<label class="check"><input name="active" type="checkbox" ${provider.active ? "checked" : ""}> Provider attivo ${helpDot("provider-active")}</label><span class="form-help">Blocca nuovi login e collegamenti; lo storico resta.</span>` : ""}<button>${isExisting ? "Salva provider" : "Censisci provider"}</button>${isExisting ? '<button type="button" id="delete-provider" class="danger-button">Elimina provider</button>' : ""}</form>`);
3197
+ byId("back").onclick = () => show("ssoProviders");
3198
+ const deleteProviderButton = document.getElementById("delete-provider");
3199
+ if (deleteProviderButton && provider) {
3200
+ deleteProviderButton.onclick = () => handleAction(async () => {
3201
+ if (!window.confirm(`Eliminare il provider ${provider.provider}? Operazione possibile solo se nessuna identita e collegata.`)) return;
3202
+ await deleteFederatedProvider(provider.provider);
3203
+ showNotice("Provider SSO eliminato.");
3204
+ await show("ssoProviders");
3205
+ });
3206
+ }
3207
+ const providerForm = document.getElementById("provider-form");
3208
+ if (providerForm) providerForm.onsubmit = (event) => {
3209
+ event.preventDefault();
3210
+ handleAction(async () => {
3211
+ const form = eventForm(event);
3212
+ const values = formValues(form);
3213
+ if (provider) {
3214
+ const request = {
3215
+ description: values.description,
3216
+ note: values.note || void 0,
3217
+ active: new FormData(form).has("active")
3218
+ };
3219
+ await updateFederatedProvider(provider.provider, request);
3220
+ } else {
3221
+ const request = {
3222
+ provider: values.provider ?? "",
3223
+ description: values.description ?? "",
3224
+ note: values.note || void 0
3225
+ };
3226
+ await createFederatedProvider(request);
3227
+ }
3228
+ showNotice(isExisting ? "Provider SSO aggiornato." : "Provider SSO censito.");
3229
+ await show("ssoProviders");
3230
+ });
3231
+ };
3232
+ }
3233
+ function registrationStateOptions(current) {
3234
+ const states = [
3235
+ [0, "Non definito"],
3236
+ [5, "Inserito"],
3237
+ [10, "Invitato"],
3238
+ [20, "Confermato"],
3239
+ [50, "Eliminato"],
3240
+ [99, "Bloccato"]
3241
+ ];
3242
+ const currentValue = Number(current);
3243
+ return states.map(([value, label]) => `<option value="${value}" ${currentValue === value ? "selected" : ""}>${label} (${value})</option>`).join("");
3244
+ }
3245
+ async function showUser(rawCode) {
3246
+ const codiceUtente = Number(rawCode);
3247
+ const [users, roles, grants, groups, providers] = await Promise.all([
3248
+ getUsers({ codiceUtente, includeGrants: true }),
3249
+ getRoles(),
3250
+ getUserRolesAndGrants(codiceUtente),
3251
+ getGroupsWithMenus({ includeDisabled: true }),
3252
+ federatedAuthenticationAvailable ? getFederatedProviders() : Promise.resolve(null)
3253
+ ]);
3254
+ const userRow = result(users)[0];
3255
+ if (!userRow?.utente) throw new Error("Utente non trovato.");
3256
+ const user = userRow.utente;
3257
+ const allRoles = result(roles);
3258
+ const userGrants = result(grants);
3259
+ const menuGroups = result(groups);
3260
+ const providerList = providers ? result(providers) : [];
3261
+ const linked = federatedAuthenticationAvailable ? result(await getFederatedIdentities(codiceUtente)) : [];
3262
+ const identitySection = federatedAuthenticationAvailable ? federatedIdentitySection(linked, providerList) : "";
3263
+ const defaultUserTab = federatedAuthenticationAvailable ? "sso" : "profile";
3264
+ const manageable = canManageUser(user);
3265
+ render(`${manageable ? "" : '<div class="notice notice-error" role="alert"><span class="notice-icon" aria-hidden="true">!</span><span class="notice-text">Stai visualizzando un utente di rango superiore: scheda in sola lettura.</span></div>'}<div class="user-action-bar"><button id="back" type="button" class="secondary">Indietro</button></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>
3266
+ <form id="user-state-form" class="account-panel">
3267
+ <div class="account-panel-head"><div><p class="eyebrow">Amministrazione account</p><h3>Stato e sicurezza</h3><p class="form-help">Aggiorna lo stato di registrazione o disattiva l'account. Le azioni distruttive sono raccolte qui, separate dal profilo.</p></div></div>
3268
+ <div class="account-state-row">
3269
+ <label class="account-state-field">Stato registrazione ${helpDot("user-state")}<select id="user-state">${registrationStateOptions(user.statoRegistrazione)}</select></label>
3270
+ <button id="save-state" type="submit" class="secondary">${actionIcon('<path d="M20 6 9 17l-5-5"/>')}<span>Aggiorna stato</span></button>
3271
+ </div>
3272
+ <div class="account-danger-zone">
3273
+ <div class="account-danger-head"><strong>Azioni distruttive</strong><span class="form-help">La disattivazione conserva i dati; l'eliminazione definitiva \xE8 irreversibile.</span></div>
3274
+ <div class="account-danger-actions">
3275
+ <span class="action-with-help"><button id="disable-user" type="button" class="danger-button">${actionIcon('<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/>')}<span>Imposta stato eliminato</span></button>${helpDot("user-delete")}</span>
3276
+ ${currentUser?.flagAdmin ? `<span class="action-with-help"><button id="purge-user" type="button" class="danger-button">${actionIcon('<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><path d="M12 9v4"/><path d="M12 17h.01"/>')}<span>Elimina definitivamente</span></button>${helpDot("user-delete-permanent")}</span>` : ""}
3277
+ </div>
3278
+ </div>
3279
+ </form>
3280
+ ${userDetailTabs(federatedAuthenticationAvailable, manageable)}<div class="user-editor">
3281
+ ${identitySection}
3282
+ <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>${pageDefaultField(user)}${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\xE0 SSO attive.</span>` : ""}${authenticationPolicyFields(user)}${extendedProfileFields(user)}${privilegeFields(user)}<button id="save-profile" type="submit">Salva</button></form>
3283
+ ${passwordFields()}
3284
+ ${roleAssignmentEditor(allRoles, userGrants.ruoli, menuGroups)}
3285
+ ${directGrantEditor(menuGroups, userGrants.abilitazioni)}
3286
+ ${manageable ? loginAsPanel(user) : ""}
3287
+ </div>`);
3288
+ bindPageDefaultCombobox(pageRouteSuggestions(menuGroups));
3289
+ byId("back").onclick = () => show("users");
3290
+ const userTabButtons = Array.from(document.querySelectorAll("[data-user-tab]"));
3291
+ userTabButtons.forEach((button, index) => {
3292
+ button.onclick = () => activateUserTab(button.dataset.userTab);
3293
+ button.onkeydown = (event) => {
3294
+ const targetIndex = event.key === "ArrowRight" ? (index + 1) % userTabButtons.length : event.key === "ArrowLeft" ? (index - 1 + userTabButtons.length) % userTabButtons.length : event.key === "Home" ? 0 : event.key === "End" ? userTabButtons.length - 1 : -1;
3295
+ if (targetIndex < 0) return;
3296
+ event.preventDefault();
3297
+ const targetTab = userTabButtons[targetIndex];
3298
+ if (!targetTab) return;
3299
+ targetTab.focus();
3300
+ activateUserTab(targetTab.dataset.userTab);
3301
+ };
3302
+ });
3303
+ activateUserTab(defaultUserTab);
3304
+ const profileForm = document.getElementById("profile");
3305
+ if (profileForm) {
3306
+ const twoFactor = profileForm.elements.namedItem("flagDueFattori");
3307
+ const passwordless = profileForm.elements.namedItem("passwordlessLoginEnabled");
3308
+ twoFactor.onchange = () => {
3309
+ passwordless.disabled = !twoFactor.checked;
3310
+ if (!twoFactor.checked) passwordless.checked = false;
3311
+ };
3312
+ profileForm.onsubmit = (event) => {
3313
+ event.preventDefault();
3314
+ handleAction(async () => {
3315
+ const form = eventForm(event);
3316
+ const fields = new FormData(form);
3317
+ await updateUtente(codiceUtente, profilePayload(form, codiceUtente));
3318
+ 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))) {
3319
+ token = null;
3320
+ sessionStorage.removeItem("accessi-console-token");
3321
+ setAccessiConsoleToken(null);
3322
+ resetLogin();
3323
+ await bootstrap();
3324
+ byId("login-error").textContent = "Privilegi o policy aggiornati. Accedi di nuovo con la nuova configurazione.";
3325
+ return;
3326
+ }
3327
+ await showUser(String(codiceUtente));
3328
+ activateUserTab("profile");
3329
+ showNotice("Profilo e policy aggiornati.");
3330
+ });
3331
+ };
3332
+ }
3333
+ const passwordForm = document.getElementById("password-form");
3334
+ if (passwordForm) passwordForm.onsubmit = (event) => {
3335
+ event.preventDefault();
3336
+ handleAction(async () => {
3337
+ const newPassword = (document.querySelector('#password-form input[name="newPassword"]')?.value ?? "").trim();
3338
+ const violations = passwordPolicyViolations(newPassword);
3339
+ if (violations.length > 0) throw new Error(`La password non rispetta i requisiti: richiede ${violations.join(", ")}.`);
3340
+ if (!window.confirm(`Impostare una nuova password per ${user.email}?`)) return;
3341
+ await setUserPassword(codiceUtente, { newPassword });
3342
+ showNotice("Password aggiornata.");
3343
+ await showUser(String(codiceUtente));
3344
+ });
3345
+ };
3346
+ const loginAsForm = document.getElementById("login-as");
3347
+ if (loginAsForm) loginAsForm.onsubmit = (event) => {
3348
+ event.preventDefault();
3349
+ handleAction(async () => {
3350
+ const values = formValues(eventForm(event));
3351
+ const issued = result(await createLoginAsUrl(codiceUtente, { url: values.url ?? "" }));
3352
+ const box = byId("login-as-result");
3353
+ box.hidden = false;
3354
+ byId("login-as-url").textContent = issued.loginUrl;
3355
+ byId("login-as-open").href = issued.loginUrl;
3356
+ byId("login-as-copy").onclick = () => handleAction(async () => {
3357
+ await navigator.clipboard.writeText(issued.loginUrl);
3358
+ showNotice("URL di accesso copiato negli appunti.");
3359
+ });
3360
+ showNotice(`URL di accesso generato. Scade alle ${new Date(issued.expiresAt).toLocaleTimeString()}.`);
3361
+ });
3362
+ };
3363
+ const setGdprButton = document.getElementById("set-gdpr");
3364
+ if (setGdprButton) setGdprButton.onclick = () => handleAction(async () => {
3365
+ if (!window.confirm(`Registrare il consenso GDPR per ${user.email}?`)) return;
3366
+ await setGdpr(codiceUtente);
3367
+ showNotice("Consenso GDPR registrato.");
3368
+ await showUser(String(codiceUtente));
3369
+ });
3370
+ const rolesForm = document.getElementById("roles");
3371
+ if (rolesForm) rolesForm.onsubmit = (event) => {
3372
+ event.preventDefault();
3373
+ handleAction(async () => {
3374
+ await assignRolesToUser(codiceUtente, { roles: selectedNumbers(eventForm(event), "role") });
3375
+ showNotice("Ruoli aggiornati.");
3376
+ });
3377
+ };
3378
+ const grantsForm = document.getElementById("grants");
3379
+ if (grantsForm) grantsForm.onsubmit = (event) => {
3380
+ event.preventDefault();
3381
+ handleAction(async () => {
3382
+ const permissions = [];
3383
+ new FormData(eventForm(event)).forEach((value, key) => {
3384
+ if (key.startsWith("grant:") && value) permissions.push({ codiceMenu: key.slice(6), tipoAbilitazione: Number(value) });
3385
+ });
3386
+ await assignPermissionsToUser(codiceUtente, { permissions });
3387
+ showNotice("Grant aggiornati.");
3388
+ });
3389
+ };
3390
+ if (federatedAuthenticationAvailable) {
3391
+ const linkForm = document.getElementById("link-sso");
3392
+ if (linkForm) linkForm.onsubmit = (event) => {
3393
+ event.preventDefault();
3394
+ handleAction(async () => {
3395
+ const values = formValues(eventForm(event));
3396
+ await linkFederatedIdentity(codiceUtente, { provider: values.provider ?? "", subject: values.subject ?? "", note: values.note || void 0 });
3397
+ showNotice("Collegamento SSO registrato.");
3398
+ await showUser(String(codiceUtente));
3399
+ });
3400
+ };
3401
+ document.querySelectorAll("[data-identity-toggle]").forEach((button) => {
3402
+ button.onclick = () => handleAction(async () => {
3403
+ await updateFederatedIdentity(button.dataset.identityToggle ?? "", { active: button.dataset.active === "true" });
3404
+ showNotice("Collegamento SSO aggiornato.");
3405
+ await showUser(String(codiceUtente));
3406
+ });
3407
+ });
3408
+ document.querySelectorAll("[data-identity-delete]").forEach((button) => {
3409
+ button.onclick = () => handleAction(async () => {
3410
+ if (!window.confirm("Eliminare definitivamente questo collegamento SSO? L\u2019utente Accessi non verr\xE0 eliminato.")) return;
3411
+ await deleteFederatedIdentityPermanently(codiceUtente, button.dataset.identityDelete ?? "");
3412
+ showNotice("Collegamento SSO eliminato definitivamente.");
3413
+ await showUser(String(codiceUtente));
3414
+ });
3415
+ });
3416
+ }
3417
+ const userStateForm = document.getElementById("user-state-form");
3418
+ if (userStateForm) userStateForm.onsubmit = (event) => {
3419
+ event.preventDefault();
3420
+ handleAction(async () => {
3421
+ const stateSelect = document.getElementById("user-state");
3422
+ const statoRegistrazione = Number(stateSelect?.value ?? 0);
3423
+ await setStatoRegistrazione({ codiceUtente, statoRegistrazione });
3424
+ showNotice("Stato registrazione aggiornato.");
3425
+ await showUser(String(codiceUtente));
3426
+ });
3427
+ };
3428
+ byId("disable-user").onclick = () => handleAction(async () => {
3429
+ await deleteUser(codiceUtente);
3430
+ showNotice("Utente impostato come eliminato.");
3431
+ await show("users");
3432
+ });
3433
+ const purgeUser = document.getElementById("purge-user");
3434
+ if (purgeUser) purgeUser.onclick = () => handleAction(async () => {
3435
+ if (!window.confirm(`Eliminare DEFINITIVAMENTE l'utente ${user.email} e tutti i dati collegati? Operazione irreversibile.`)) return;
3436
+ await deleteUserPermanently(codiceUtente);
3437
+ showNotice("Utente eliminato definitivamente.");
3438
+ await show("users");
3439
+ });
3440
+ if (!manageable) setUserManagementEnabled(false);
3441
+ }
3442
+ async function showRoles() {
3443
+ const [rolesResponse, groupsResponse] = await Promise.all([getRoles(), getGroupsWithMenus({ includeDisabled: true })]);
3444
+ const roles = result(rolesResponse);
3445
+ const groups = result(groupsResponse);
3446
+ render(`<div class="toolbar"><button id="new-role">Nuovo ruolo</button></div><h2>Ruoli</h2><p class="form-help">Definisci i ruoli e i menu che concedono. L'assegnazione agli utenti si gestisce dalla scheda utente.</p><div class="role-overview-list">${roles.map((role) => roleOverview(role, groups)).join("") || '<p class="muted">Nessun ruolo configurato.</p>'}</div>`);
3447
+ const roleForm = (role) => {
3448
+ render(`<button id="back" type="button">Indietro</button><h2>${role ? "Modifica" : "Nuovo"} ruolo</h2><form id="role-form"><label>Descrizione ${helpDot("role-description")}<input name="descrizione" value="${escapeHtml(role?.descrizioneRuolo)}" required><span class="form-help">Nome del ruolo.</span></label><p class="form-help">Seleziona i menu e il livello ${helpDot("role-level")}; il salvataggio sostituisce la configurazione del ruolo.</p><div class="role-menu-groups">${groups.map((group) => roleMenuEditorTable(group, role)).join("")}</div><button>Salva ruolo</button></form>`);
3449
+ byId("back").onclick = () => show("roles");
3450
+ document.querySelectorAll('#role-form input[name="menu"]').forEach((checkbox) => {
3451
+ checkbox.onchange = () => {
3452
+ const option = checkbox.closest("li");
3453
+ const level = option?.querySelector('select[name^="menu-level:"]');
3454
+ if (level) level.disabled = !checkbox.checked;
3455
+ };
3456
+ });
3457
+ const roleFormElement = document.getElementById("role-form");
3458
+ if (roleFormElement) roleFormElement.onsubmit = (event) => {
3459
+ event.preventDefault();
3460
+ handleAction(async () => {
3461
+ const form = eventForm(event);
3462
+ const data = new FormData(form);
3463
+ const menu = Array.from(data.getAll("menu"), (codiceMenu) => ({ codiceMenu: String(codiceMenu), tipoAbilitazione: Number(data.get(`menu-level:${codiceMenu}`) ?? 10) }));
3464
+ if (menu.length === 0) throw new Error("Seleziona almeno un menu per il ruolo.");
3465
+ const request = { descrizioneRuolo: formValues(form).descrizione ?? "", menu };
3466
+ if (role?.codiceRuolo) await updateRole(role.codiceRuolo, request);
3467
+ else await createRole(request);
3468
+ showNotice(role?.codiceRuolo ? "Ruolo aggiornato." : "Ruolo creato.");
3469
+ await show("roles");
3470
+ });
3471
+ };
3472
+ };
3473
+ byId("new-role").onclick = () => roleForm();
3474
+ document.querySelectorAll("[data-role]").forEach((button) => button.onclick = () => roleForm(roles.find((role) => role.codiceRuolo === Number(button.dataset.role))));
3475
+ document.querySelectorAll("[data-role-delete]").forEach((button) => button.onclick = () => handleAction(async () => {
3476
+ if (!window.confirm("Eliminare definitivamente questo ruolo? Le assegnazioni agli utenti verranno rimosse.")) return;
3477
+ await deleteRole(Number(button.dataset.roleDelete));
3478
+ showNotice("Ruolo eliminato.");
3479
+ await show("roles");
3480
+ }));
3481
+ }
3482
+ function menuGroupsTable(groups) {
3483
+ return `<div class="menu-table-wrap"><table><thead><tr><th>Codice</th><th>Descrizione</th><th>Ordine</th><th>Menu</th><th>Stato</th><th></th></tr></thead><tbody>${groups.map((group) => `<tr class="${group.enabled === false ? "is-disabled" : ""}"><td><code>${escapeHtml(group.codiceGruppo)}</code></td><td><strong>${escapeHtml(group.descrizioneGruppo)}</strong></td><td>${escapeHtml(group.ordineGruppo)}</td><td>${group.menus.length}</td><td><label class="table-toggle"><input type="checkbox" data-group="${escapeHtml(group.codiceGruppo)}" ${group.enabled !== false ? "checked" : ""}><span>${group.enabled === false ? "Disabilitato" : "Abilitato"}</span></label></td><td class="row-actions"><button type="button" data-group-edit="${escapeHtml(group.codiceGruppo)}">Modifica</button><button type="button" class="danger-button" data-group-delete="${escapeHtml(group.codiceGruppo)}">Elimina</button></td></tr>`).join("") || '<tr><td colspan="6" class="muted">Nessun gruppo configurato.</td></tr>'}</tbody></table></div>`;
3484
+ }
3485
+ function menusTable(entries, menuTypes) {
3486
+ const typeLabel = (codice) => {
3487
+ if (!codice) return "Non indicato";
3488
+ return menuTypes.find((type) => type.codiceTipo === codice)?.descrizioneTipo || codice;
3489
+ };
3490
+ return `<div class="menu-table-wrap"><table><thead><tr><th>Stato</th><th>Menu</th><th>Codice</th><th>Gruppo</th><th>Ordine</th><th>Tipo</th><th>Pagina</th><th></th></tr></thead><tbody>${entries.map(({ menu, group }) => `<tr class="${menu.enabled === false ? "is-disabled" : ""}" data-menu-group="${escapeHtml(group.codiceGruppo)}"><td><label class="table-toggle"><input type="checkbox" data-menu="${escapeHtml(menu.codiceMenu)}" ${menu.enabled !== false ? "checked" : ""}><span>${menu.enabled === false ? "Disabilitato" : "Abilitato"}</span></label></td><td><strong>${escapeHtml(menu.descrizioneMenu)}</strong>${menu.note ? `<br><span class="muted">${escapeHtml(menu.note)}</span>` : ""}</td><td><code>${escapeHtml(menu.codiceMenu)}</code></td><td>${escapeHtml(group.descrizioneGruppo)}</td><td>${escapeHtml(menu.ordineMenu)}</td><td>${escapeHtml(typeLabel(menu.tipo))}</td><td>${menu.pagina ? `<code>${escapeHtml(menu.pagina)}</code>` : '<span class="muted">Non indicata</span>'}</td><td class="row-actions"><button type="button" data-menu-edit="${escapeHtml(menu.codiceMenu)}">Modifica</button><button type="button" class="danger-button" data-menu-delete="${escapeHtml(menu.codiceMenu)}">Elimina</button></td></tr>`).join("") || '<tr><td colspan="8" class="muted">Nessun menu configurato.</td></tr>'}</tbody></table></div>`;
3491
+ }
3492
+ function menuTypeTable(types) {
3493
+ return `<div class="menu-table-wrap"><table><thead><tr><th>Codice</th><th>Descrizione</th><th></th></tr></thead><tbody>${types.map((type) => `<tr><td><code>${escapeHtml(type.codiceTipo)}</code></td><td>${escapeHtml(type.descrizioneTipo || "\u2014")}</td><td class="row-actions"><button type="button" data-type-edit="${escapeHtml(type.codiceTipo)}">Modifica</button><button type="button" class="danger-button" data-type-delete="${escapeHtml(type.codiceTipo)}">Elimina</button></td></tr>`).join("") || '<tr><td colspan="3" class="muted">Nessun tipo menu.</td></tr>'}</tbody></table></div>`;
3494
+ }
3495
+ async function showMenuGroups() {
3496
+ const groups = result(await getGroupsWithMenus({ includeDisabled: true }));
3497
+ render(`<div class="toolbar"><button id="new-group">Nuovo gruppo</button><button id="reload-groups" class="secondary">Aggiorna</button></div>
3498
+ <h2>Gruppi di menu</h2><p class="form-help">Raggruppano i menu nella navigazione e ne definiscono l'ordine. Un gruppo si elimina solo se non contiene menu.</p>
3499
+ ${menuGroupsTable(groups)}`);
3500
+ byId("new-group").onclick = () => showMenuGroupForm();
3501
+ byId("reload-groups").onclick = () => handleAction(showMenuGroups);
3502
+ document.querySelectorAll("[data-group]").forEach((input) => {
3503
+ input.onchange = () => handleAction(async () => {
3504
+ await setGroupEnabled(input.dataset.group ?? "", { enabled: input.checked });
3505
+ showNotice("Gruppo aggiornato.");
3506
+ });
3507
+ });
3508
+ document.querySelectorAll("[data-group-edit]").forEach((button) => {
3509
+ button.onclick = () => showMenuGroupForm(groups.find((group) => group.codiceGruppo === button.dataset.groupEdit));
3510
+ });
3511
+ document.querySelectorAll("[data-group-delete]").forEach((button) => {
3512
+ button.onclick = () => handleAction(async () => {
3513
+ if (!window.confirm(`Eliminare il gruppo ${button.dataset.groupDelete}? Operazione possibile solo se non contiene menu.`)) return;
3514
+ await deleteMenuGroup(button.dataset.groupDelete ?? "");
3515
+ showNotice("Gruppo eliminato.");
3516
+ await showMenuGroups();
3517
+ });
3518
+ });
3519
+ }
3520
+ async function showMenuItems() {
3521
+ const [groupsResponse, typesResponse] = await Promise.all([
3522
+ getGroupsWithMenus({ includeDisabled: true }),
3523
+ getMenuTypes()
3524
+ ]);
3525
+ const groups = result(groupsResponse);
3526
+ const menuTypes = result(typesResponse);
3527
+ const entries = groups.flatMap((group) => group.menus.map((menu) => ({ menu, group })));
3528
+ render(`<div class="toolbar"><button id="new-menu">Nuovo menu</button><button id="reload-menus" class="secondary">Aggiorna</button><label class="inline-field">Filtra per gruppo<select id="menu-group-filter"><option value="">Tutti i gruppi</option>${groups.map((group) => `<option value="${escapeHtml(group.codiceGruppo)}">${escapeHtml(group.descrizioneGruppo)}</option>`).join("")}</select></label></div>
3529
+ <h2>Menu</h2><p class="form-help">Voci di navigazione. L'eliminazione rimuove anche i grant utente e le associazioni ai ruoli.</p>
3530
+ ${menusTable(entries, menuTypes)}`);
3531
+ byId("new-menu").onclick = () => showMenuForm(void 0, groups, menuTypes);
3532
+ byId("reload-menus").onclick = () => handleAction(showMenuItems);
3533
+ const groupFilter = document.getElementById("menu-group-filter");
3534
+ if (groupFilter) {
3535
+ groupFilter.onchange = () => {
3536
+ const selected = groupFilter.value;
3537
+ document.querySelectorAll("[data-menu-group]").forEach((row) => {
3538
+ row.hidden = selected !== "" && row.dataset.menuGroup !== selected;
3539
+ });
3540
+ };
3541
+ }
3542
+ document.querySelectorAll("[data-menu]").forEach((input) => {
3543
+ input.onchange = () => handleAction(async () => {
3544
+ await setMenuEnabled(input.dataset.menu ?? "", { enabled: input.checked });
3545
+ showNotice("Menu aggiornato.");
3546
+ });
3547
+ });
3548
+ document.querySelectorAll("[data-menu-edit]").forEach((button) => {
3549
+ button.onclick = () => showMenuForm(entries.find((entry) => entry.menu.codiceMenu === button.dataset.menuEdit)?.menu, groups, menuTypes);
3550
+ });
3551
+ document.querySelectorAll("[data-menu-delete]").forEach((button) => {
3552
+ button.onclick = () => handleAction(async () => {
3553
+ if (!window.confirm(`Eliminare il menu ${button.dataset.menuDelete}? Verranno rimossi anche i grant utente e le associazioni ai ruoli.`)) return;
3554
+ await deleteMenu(button.dataset.menuDelete ?? "");
3555
+ showNotice("Menu eliminato.");
3556
+ await showMenuItems();
3557
+ });
3558
+ });
3559
+ }
3560
+ async function showMenuScopes() {
3561
+ const scopes = result(await getScopes());
3562
+ render(`<div class="toolbar"><button id="new-scope">Nuovo scope</button><button id="reload-scopes" class="secondary">Aggiorna</button></div>
3563
+ <h2>Scope applicativi</h2><p class="form-help">Uno scope decide in quali applicazioni un menu e visibile. Un menu puo appartenere a piu scope.</p>
3564
+ <table class="entity-table"><thead><tr><th>Codice</th><th>Nome</th><th>Azioni</th></tr></thead><tbody>
3565
+ ${scopes.map((scope) => `<tr><td><code>${escapeHtml(scope.codice)}</code></td><td>${escapeHtml(scope.nome)}</td><td class="row-actions"><button type="button" class="secondary" data-scope-menus="${escapeHtml(scope.codice)}">Menu</button><button type="button" class="secondary" data-scope-edit="${escapeHtml(scope.codice)}">Rinomina</button><button type="button" class="danger-button" data-scope-delete="${escapeHtml(scope.codice)}">Elimina</button></td></tr>`).join("") || '<tr><td colspan="3">Nessuno scope configurato.</td></tr>'}
3566
+ </tbody></table>`);
3567
+ byId("new-scope").onclick = () => showScopeForm();
3568
+ byId("reload-scopes").onclick = () => handleAction(showMenuScopes);
3569
+ document.querySelectorAll("[data-scope-menus]").forEach((button) => {
3570
+ button.onclick = () => handleAction(() => showScopeMenus(button.dataset.scopeMenus ?? ""));
3571
+ });
3572
+ document.querySelectorAll("[data-scope-edit]").forEach((button) => {
3573
+ button.onclick = () => showScopeForm(scopes.find((scope) => scope.codice === button.dataset.scopeEdit));
3574
+ });
3575
+ document.querySelectorAll("[data-scope-delete]").forEach((button) => {
3576
+ button.onclick = () => handleAction(async () => {
3577
+ if (!window.confirm(`Eliminare lo scope ${button.dataset.scopeDelete}? Le associazioni ai menu verranno rimosse.`)) return;
3578
+ await deleteScope(button.dataset.scopeDelete ?? "");
3579
+ showNotice("Scope eliminato.");
3580
+ await showMenuScopes();
3581
+ });
3582
+ });
3583
+ }
3584
+ function showScopeForm(scope) {
3585
+ render(`<form id="scope-form"><h2>${scope ? "Rinomina scope" : "Nuovo scope"}</h2>
3586
+ <label>Codice<input name="codice" maxlength="20" required ${scope ? "readonly" : ""} value="${escapeHtml(scope?.codice ?? "")}"><span class="form-help">Es. STEX. Maiuscole, cifre e underscore.</span></label>
3587
+ <label>Nome<input name="nome" maxlength="100" required value="${escapeHtml(scope?.nome ?? "")}"><span class="form-help">Etichetta leggibile, es. SteX.</span></label>
3588
+ <div class="toolbar"><button>Salva</button><button type="button" class="secondary" id="cancel-scope">Annulla</button></div></form>`);
3589
+ const form = byId("scope-form");
3590
+ form.onsubmit = (event) => {
3591
+ event.preventDefault();
3592
+ handleAction(async () => {
3593
+ const values = formValues(form);
3594
+ const nome = values["nome"] ?? "";
3595
+ if (scope) {
3596
+ await updateScope(scope.codice, { nome });
3597
+ } else {
3598
+ await createScope({ codice: (values["codice"] ?? "").trim().toUpperCase(), nome });
3599
+ }
3600
+ showNotice("Scope salvato.");
3601
+ await showMenuScopes();
3602
+ });
3603
+ };
3604
+ byId("cancel-scope").onclick = () => handleAction(showMenuScopes);
3605
+ }
3606
+ async function showScopeMenus(codiceScope) {
3607
+ const [groupsResponse, assignedResponse] = await Promise.all([
3608
+ getGroupsWithMenus({ includeDisabled: true }),
3609
+ getMenuScopes(codiceScope)
3610
+ ]);
3611
+ const groups = result(groupsResponse);
3612
+ const assigned = new Set(result(assignedResponse));
3613
+ render(`<form id="scope-menus"><h2>Menu dello scope ${escapeHtml(codiceScope)}</h2><p class="form-help">Seleziona i menu visibili nelle applicazioni associate a questo scope. Il salvataggio sostituisce l'elenco.</p>
3614
+ ${groups.map((group) => `<fieldset class="grant-menu-group"><legend>${escapeHtml(group.descrizioneGruppo)}</legend>${group.menus.map((menu) => `<label class="check"><input type="checkbox" name="menu" value="${escapeHtml(menu.codiceMenu)}" ${assigned.has(menu.codiceMenu) ? "checked" : ""}><span>${escapeHtml(menu.descrizioneMenu)} <code>${escapeHtml(menu.codiceMenu)}</code></span></label>`).join("")}</fieldset>`).join("")}
3615
+ <div class="toolbar"><button>Salva</button><button type="button" class="secondary" id="cancel-scope-menus">Indietro</button></div></form>`);
3616
+ const form = byId("scope-menus");
3617
+ form.onsubmit = (event) => {
3618
+ event.preventDefault();
3619
+ handleAction(async () => {
3620
+ const selected = new FormData(form).getAll("menu").map((value) => String(value));
3621
+ await setScopeMenus(codiceScope, { menu: selected });
3622
+ showNotice("Menu dello scope aggiornati.");
3623
+ await showMenuScopes();
3624
+ });
3625
+ };
3626
+ byId("cancel-scope-menus").onclick = () => handleAction(showMenuScopes);
3627
+ }
3628
+ async function showMenuTypes() {
3629
+ const menuTypes = result(await getMenuTypes());
3630
+ render(`<div class="toolbar"><button id="new-menu-type">Nuovo tipo menu</button><button id="reload-menu-types" class="secondary">Aggiorna</button></div>
3631
+ <h2>Tipi menu</h2><p class="form-help">Classificano i menu (es. amministrazione, operativit\xE0). Un tipo si elimina solo se non \xE8 usato da alcun menu.</p>
3632
+ ${menuTypeTable(menuTypes)}`);
3633
+ byId("new-menu-type").onclick = () => showMenuTypeForm();
3634
+ byId("reload-menu-types").onclick = () => handleAction(showMenuTypes);
3635
+ document.querySelectorAll("[data-type-edit]").forEach((button) => {
3636
+ button.onclick = () => showMenuTypeForm(menuTypes.find((type) => type.codiceTipo === button.dataset.typeEdit));
3637
+ });
3638
+ document.querySelectorAll("[data-type-delete]").forEach((button) => {
3639
+ button.onclick = () => handleAction(async () => {
3640
+ if (!window.confirm(`Eliminare il tipo menu ${button.dataset.typeDelete}? Operazione possibile solo se nessun menu lo utilizza.`)) return;
3641
+ await deleteMenuType(button.dataset.typeDelete ?? "");
3642
+ showNotice("Tipo menu eliminato.");
3643
+ await showMenuTypes();
3644
+ });
3645
+ });
3646
+ }
3647
+ function showMenuGroupForm(group) {
3648
+ const isExisting = group !== void 0;
3649
+ render(`<button id="back" type="button">Indietro</button><h2>${isExisting ? "Modifica" : "Nuovo"} gruppo menu</h2>
3650
+ <form id="group-form">
3651
+ <label>Codice gruppo ${helpDot("menu-group-code")}<input name="codiceGruppo" maxlength="1" value="${escapeHtml(group?.codiceGruppo)}" ${isExisting ? "readonly" : ""} required><span class="form-help">Un solo carattere, es. <code>A</code>. Immutabile dopo la creazione.</span></label>
3652
+ <label>Descrizione<input name="descrizioneGruppo" maxlength="100" value="${escapeHtml(group?.descrizioneGruppo)}" required></label>
3653
+ <label>Ordine ${helpDot("menu-group-order")}<input name="ordineGruppo" type="number" value="${group?.ordineGruppo ?? ""}"><span class="form-help">Usato per ordinare i gruppi nella navigazione.</span></label>
3654
+ <label class="check"><input name="enabled" type="checkbox" ${group?.enabled !== false ? "checked" : ""}> Gruppo abilitato ${helpDot("menu-group-enabled")}</label>
3655
+ <button>${isExisting ? "Salva gruppo" : "Crea gruppo"}</button>
3656
+ </form>`);
3657
+ byId("back").onclick = () => handleAction(showMenuGroups);
3658
+ const form = document.getElementById("group-form");
3659
+ if (form) {
3660
+ form.onsubmit = (event) => {
3661
+ event.preventDefault();
3662
+ handleAction(async () => {
3663
+ const values = formValues(eventForm(event));
3664
+ const payload = {
3665
+ descrizioneGruppo: values.descrizioneGruppo || void 0,
3666
+ ordineGruppo: values.ordineGruppo ? Number(values.ordineGruppo) : void 0,
3667
+ enabled: new FormData(eventForm(event)).has("enabled")
3668
+ };
3669
+ if (isExisting) await updateMenuGroup(group.codiceGruppo, payload);
3670
+ else await createMenuGroup({ codiceGruppo: values.codiceGruppo ?? "", ...payload });
3671
+ showNotice(isExisting ? "Gruppo aggiornato." : "Gruppo creato.");
3672
+ await showMenuGroups();
3673
+ });
3674
+ };
3675
+ }
3676
+ }
3677
+ function showMenuForm(menu, groups, menuTypes) {
3678
+ const isExisting = menu !== void 0;
3679
+ const groupOptions = groups.map((group) => `<option value="${escapeHtml(group.codiceGruppo)}" ${menu?.codiceGruppo === group.codiceGruppo ? "selected" : ""}>${escapeHtml(group.codiceGruppo)} \u2013 ${escapeHtml(group.descrizioneGruppo)}</option>`).join("");
3680
+ const typeOptions = `<option value="">Nessun tipo</option>${menuTypes.map((type) => `<option value="${escapeHtml(type.codiceTipo)}" ${menu?.tipo === type.codiceTipo ? "selected" : ""}>${escapeHtml(type.codiceTipo)} \u2013 ${escapeHtml(type.descrizioneTipo || "")}</option>`).join("")}`;
3681
+ render(`<button id="back" type="button">Indietro</button><h2>${isExisting ? "Modifica" : "Nuovo"} menu</h2>
3682
+ <form id="menu-form">
3683
+ <label>Codice menu ${helpDot("menu-code")}<input name="codiceMenu" maxlength="20" value="${escapeHtml(menu?.codiceMenu)}" ${isExisting ? "readonly" : ""} required><span class="form-help">Chiave tecnica, es. <code>MNU001</code>. Immutabile dopo la creazione.</span></label>
3684
+ <label>Descrizione<input name="descrizioneMenu" maxlength="100" value="${escapeHtml(menu?.descrizioneMenu)}" required></label>
3685
+ <label>Gruppo ${helpDot("menu-group-select")}<select name="codiceGruppo" required>${groupOptions}</select><span class="form-help">Crea prima un gruppo se non ne esistono.</span></label>
3686
+ <label>Tipo menu ${helpDot("menu-type-select")}<select name="tipo">${typeOptions}</select></label>
3687
+ <label>Icona ${helpDot("menu-icon")}<input name="icona" maxlength="50" value="${escapeHtml(menu?.icona)}"><span class="form-help">Identificatore icona usato dalla UI host.</span></label>
3688
+ <label>Pagina ${helpDot("menu-page")}<input name="pagina" maxlength="50" value="${escapeHtml(menu?.pagina)}"><span class="form-help">Percorso o pagina associata al menu.</span></label>
3689
+ <label>Ordine ${helpDot("menu-order")}<input name="ordineMenu" type="number" value="${menu?.ordineMenu ?? ""}"></label>
3690
+ <label>Menu di riferimento ${helpDot("menu-rifmenu")}<input name="rifMenu" maxlength="20" value="${escapeHtml(menu?.rifMenu)}"><span class="form-help">Codice di un menu padre, opzionale.</span></label>
3691
+ <label>Nota ${helpDot("menu-note")}<textarea name="note" rows="3" maxlength="1000">${escapeHtml(menu?.note)}</textarea></label>
3692
+ <label class="check"><input name="enabled" type="checkbox" ${menu?.enabled !== false ? "checked" : ""}> Menu abilitato ${helpDot("menu-enabled")}</label>
3693
+ <button>${isExisting ? "Salva menu" : "Crea menu"}</button>
3694
+ </form>`);
3695
+ byId("back").onclick = () => handleAction(showMenuItems);
3696
+ const form = document.getElementById("menu-form");
3697
+ if (form) {
3698
+ form.onsubmit = (event) => {
3699
+ event.preventDefault();
3700
+ handleAction(async () => {
3701
+ const values = formValues(eventForm(event));
3702
+ const payload = {
3703
+ descrizioneMenu: values.descrizioneMenu || void 0,
3704
+ codiceGruppo: values.codiceGruppo ?? "",
3705
+ tipo: values.tipo || void 0,
3706
+ icona: values.icona || void 0,
3707
+ pagina: values.pagina || void 0,
3708
+ ordineMenu: values.ordineMenu ? Number(values.ordineMenu) : void 0,
3709
+ rifMenu: values.rifMenu || void 0,
3710
+ note: values.note || void 0,
3711
+ enabled: new FormData(eventForm(event)).has("enabled")
3712
+ };
3713
+ if (isExisting) await updateMenu(menu.codiceMenu, payload);
3714
+ else await createMenu({ codiceMenu: values.codiceMenu ?? "", ...payload });
3715
+ showNotice(isExisting ? "Menu aggiornato." : "Menu creato.");
3716
+ await showMenuItems();
3717
+ });
3718
+ };
3719
+ }
3720
+ }
3721
+ function showMenuTypeForm(type) {
3722
+ const isExisting = type !== void 0;
3723
+ render(`<button id="back" type="button">Indietro</button><h2>${isExisting ? "Modifica" : "Nuovo"} tipo menu</h2>
3724
+ <form id="menu-type-form">
3725
+ <label>Codice tipo ${helpDot("menu-type-code")}<input name="codiceTipo" maxlength="1" value="${escapeHtml(type?.codiceTipo)}" ${isExisting ? "readonly" : ""} required><span class="form-help">Un solo carattere, es. <code>A</code>. Immutabile dopo la creazione.</span></label>
3726
+ <label>Descrizione<input name="descrizioneTipo" maxlength="20" value="${escapeHtml(type?.descrizioneTipo)}"></label>
3727
+ <button>${isExisting ? "Salva tipo" : "Crea tipo"}</button>
3728
+ </form>`);
3729
+ byId("back").onclick = () => handleAction(showMenuTypes);
3730
+ const form = document.getElementById("menu-type-form");
3731
+ if (form) {
3732
+ form.onsubmit = (event) => {
3733
+ event.preventDefault();
3734
+ handleAction(async () => {
3735
+ const values = formValues(eventForm(event));
3736
+ if (isExisting) await updateMenuType(type.codiceTipo, { descrizioneTipo: values.descrizioneTipo || void 0 });
3737
+ else await createMenuType({ codiceTipo: values.codiceTipo ?? "", descrizioneTipo: values.descrizioneTipo || void 0 });
3738
+ showNotice(isExisting ? "Tipo menu aggiornato." : "Tipo menu creato.");
3739
+ await showMenuTypes();
3740
+ });
3741
+ };
3742
+ }
3743
+ }
3744
+ function scopeList(scopes) {
3745
+ return (scopes ?? []).join(", ") || "nessuno";
3746
+ }
3747
+ function serviceTokenExpiry(token2) {
3748
+ if (!token2.expiresAt) return "nessuna";
3749
+ const date = new Date(token2.expiresAt);
3750
+ const days = Math.ceil((date.getTime() - Date.now()) / 864e5);
3751
+ const label = escapeHtml(date.toLocaleString());
3752
+ if (days < 0) return `${label}<br><span class="error">scaduto</span>`;
3753
+ if (days <= 7) return `${label}<br><span class="error">scade tra ${days} g</span>`;
3754
+ return label;
3755
+ }
3756
+ function serviceTokenRows(tokens, includeRevoked) {
3757
+ const lastUsed = (token2) => token2.lastUsedAt ? `<span title="${escapeHtml(token2.lastUsedIp ?? "")}">${escapeHtml(new Date(token2.lastUsedAt).toLocaleString())}</span>${token2.lastUsedIp ? `<br><span class="muted">${escapeHtml(token2.lastUsedIp)}</span>` : ""}` : "mai";
3758
+ const status = (token2) => token2.revoked ? `Revocato${token2.revokedBy ? ` da #${escapeHtml(token2.revokedBy)}` : ""}${token2.revokedAt ? `<br><span class="muted">${escapeHtml(new Date(token2.revokedAt).toLocaleString())}</span>` : ""}` : "Attivo";
3759
+ return tokens.map((token2) => `<tr class="${token2.revoked ? "is-disabled" : ""}"><td><strong>${escapeHtml(token2.label)}</strong><br><code>${escapeHtml(token2.tokenId)}</code></td><td>${escapeHtml(scopeList(token2.scopes))}</td><td>${escapeHtml(token2.createdAt ? new Date(token2.createdAt).toLocaleString() : "")}</td><td>${serviceTokenExpiry(token2)}</td><td>${lastUsed(token2)}</td><td>${status(token2)}</td><td>${token2.revoked ? "" : `<button type="button" data-rotate="${escapeHtml(token2.tokenId)}">Ruota</button> <button type="button" class="danger-button" data-revoke="${escapeHtml(token2.tokenId)}">Revoca</button>`}</td></tr>`).join("") || `<tr><td colspan="7">Nessun token ${includeRevoked ? "" : "attivo"}.</td></tr>`;
3760
+ }
3761
+ async function showServiceTokens(includeRevoked = false) {
3762
+ const tokens = result(await getServiceTokens({ includeRevoked }));
3763
+ render(`<div class="toolbar"><button id="new-token">Nuovo token</button><button id="toggle-revoked" class="secondary">${includeRevoked ? "Nascondi revocati" : "Mostra revocati"}</button></div><h2>Token di servizio</h2><p class="form-help">Credenziali macchina-a-macchina per chiamate tecniche, non legate a un utente. Il segreto e mostrato <strong>una sola volta</strong> alla creazione: conservalo come variabile d'ambiente/secret. Autenticazione: <code>Authorization: Bearer &lt;token&gt;</code>.</p><table><thead><tr><th>Token</th><th>Scope</th><th>Creato</th><th>Scade</th><th>Ultimo uso</th><th>Stato</th><th></th></tr></thead><tbody>${serviceTokenRows(tokens, includeRevoked)}</tbody></table>`);
3764
+ byId("new-token").onclick = () => showServiceTokenForm();
3765
+ byId("toggle-revoked").onclick = () => handleAction(() => showServiceTokens(!includeRevoked));
3766
+ document.querySelectorAll("[data-revoke]").forEach((button) => {
3767
+ button.onclick = () => handleAction(async () => {
3768
+ if (!window.confirm("Revocare definitivamente questo token? Le integrazioni che lo usano smetteranno di funzionare.")) return;
3769
+ await revokeServiceToken(button.dataset.revoke ?? "");
3770
+ showNotice("Token di servizio revocato.");
3771
+ await showServiceTokens(includeRevoked);
3772
+ });
3773
+ });
3774
+ document.querySelectorAll("[data-rotate]").forEach((button) => {
3775
+ button.onclick = () => handleAction(async () => {
3776
+ const issued = result(await rotateServiceToken(button.dataset.rotate ?? ""));
3777
+ showNotice("Token di servizio ruotato.");
3778
+ showIssuedServiceToken(issued);
3779
+ });
3780
+ });
3781
+ }
3782
+ function showServiceTokenForm() {
3783
+ render(`<button id="back" type="button" class="secondary">Indietro</button><h2>Nuovo token di servizio</h2><form id="token-form"><label>Descrizione ${helpDot("token-label")}<input name="label" required maxlength="100"><span class="form-help">Identifica l'integrazione, ad esempio "IA - produzione".</span></label><label>Scope ${helpDot("token-scopes")}<input name="scopes" placeholder="ia, chat"><span class="form-help">Separati da virgola (lettere, numeri, ':', '_', '-'). Definiscono cosa pu\xF2 fare il token.</span></label><label>Durata in giorni ${helpDot("token-ttl")}<input name="ttlDays" type="number" min="1" step="1"><span class="form-help">Opzionale. Lascia vuoto per un token senza scadenza.</span></label><button>Crea token</button></form>`);
3784
+ byId("back").onclick = () => void show("tokens");
3785
+ const form = document.getElementById("token-form");
3786
+ if (form) form.onsubmit = (event) => {
3787
+ event.preventDefault();
3788
+ handleAction(async () => {
3789
+ const values = formValues(eventForm(event));
3790
+ const scopes = (values.scopes ?? "").split(",").map((scope) => scope.trim()).filter(Boolean);
3791
+ const ttlDays = values.ttlDays ? Number(values.ttlDays) : void 0;
3792
+ const issued = result(await createServiceToken({
3793
+ label: values.label ?? "",
3794
+ scopes: scopes.length ? scopes : void 0,
3795
+ ttlDays: Number.isFinite(ttlDays) ? ttlDays : void 0
3796
+ }));
3797
+ showNotice("Token di servizio creato.");
3798
+ showIssuedServiceToken(issued);
3799
+ });
3800
+ };
3801
+ }
3802
+ function showIssuedServiceToken(issued) {
3803
+ render(`<h2>Token creato</h2><p class="form-help">Copia ora il token: <strong>non sara piu mostrato</strong>. Salvalo come variabile d'ambiente/secret del servizio chiamante e non inserirlo nel body delle richieste.</p><div class="token-reveal"><code id="issued-token">${escapeHtml(issued.token)}</code><button id="copy-token" type="button">Copia</button></div><dl class="entity-metadata"><div><dt>Descrizione</dt><dd>${escapeHtml(issued.label)}</dd></div><div><dt>Scope</dt><dd>${escapeHtml(scopeList(issued.scopes))}</dd></div><div><dt>Scade</dt><dd>${issued.expiresAt ? escapeHtml(new Date(issued.expiresAt).toLocaleString()) : "nessuna"}</dd></div></dl><button id="done-token">Ho copiato, chiudi</button>`);
3804
+ byId("copy-token").onclick = () => handleAction(async () => {
3805
+ await navigator.clipboard.writeText(issued.token);
3806
+ showNotice("Token copiato negli appunti.");
3807
+ });
3808
+ byId("done-token").onclick = () => void show("tokens");
3809
+ }
3810
+ function renderWikiBlock(block) {
3811
+ switch (block.kind) {
3812
+ case "p":
3813
+ return `<p>${escapeHtml(block.text)}</p>`;
3814
+ case "h":
3815
+ return `<h3>${escapeHtml(block.text)}</h3>`;
3816
+ case "list": {
3817
+ const tag = block.ordered ? "ol" : "ul";
3818
+ return `<${tag}>${block.items.map((item) => `<li>${escapeHtml(item)}</li>`).join("")}</${tag}>`;
3819
+ }
3820
+ case "code": {
3821
+ const variant = block.variant ? ` is-${block.variant}` : "";
3822
+ const title = block.title ? `<span class="wiki-code-title">${escapeHtml(block.title)}</span>` : "";
3823
+ return `<figure class="wiki-code${variant}"><figcaption class="wiki-code-head">${title}<span class="wiki-code-lang">${escapeHtml(block.language)}</span><button type="button" class="wiki-copy" data-wiki-copy aria-label="Copia il codice">Copia</button></figcaption><pre><code>${escapeHtml(block.code)}</code></pre></figure>`;
3824
+ }
3825
+ case "callout":
3826
+ return `<div class="wiki-callout is-${block.tone}">${block.title ? `<strong>${escapeHtml(block.title)}</strong>` : ""}<p>${escapeHtml(block.text)}</p></div>`;
3827
+ case "table":
3828
+ return `<div class="wiki-table-wrap"><table><thead><tr>${block.head.map((cell) => `<th>${escapeHtml(cell)}</th>`).join("")}</tr></thead><tbody>${block.rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>`;
3829
+ default:
3830
+ return "";
3831
+ }
3832
+ }
3833
+ function bindCopyButtons(root) {
3834
+ root.querySelectorAll("[data-wiki-copy]").forEach((button) => {
3835
+ button.onclick = () => handleAction(async () => {
3836
+ const codeEl = button.closest(".wiki-code")?.querySelector("code");
3837
+ await navigator.clipboard.writeText(codeEl?.textContent ?? "");
3838
+ showNotice("Codice copiato negli appunti.");
3839
+ });
3840
+ });
3841
+ }
3842
+ function helpDot(key) {
3843
+ const title = HELP[key]?.title ?? key;
3844
+ return `<button type="button" class="help-dot" data-help="${escapeHtml(key)}" aria-haspopup="dialog" aria-expanded="false" aria-controls="accessi-help-popover" aria-label="Informazioni: ${escapeHtml(title)}" title="Informazioni">i</button>`;
3845
+ }
3846
+ var helpPopover = null;
3847
+ var helpAnchor = null;
3848
+ function ensureHelpPopover() {
3849
+ if (!helpPopover) {
3850
+ helpPopover = document.createElement("div");
3851
+ helpPopover.className = "help-popover";
3852
+ helpPopover.id = "accessi-help-popover";
3853
+ helpPopover.hidden = true;
3854
+ helpPopover.setAttribute("role", "dialog");
3855
+ helpPopover.setAttribute("aria-modal", "false");
3856
+ helpPopover.tabIndex = -1;
3857
+ document.body.appendChild(helpPopover);
3858
+ }
3859
+ return helpPopover;
3860
+ }
3861
+ function closeHelp(restoreFocus = true) {
3862
+ if (!helpPopover) return;
3863
+ const wasOpen = !helpPopover.hidden;
3864
+ helpPopover.hidden = true;
3865
+ const anchor = helpAnchor;
3866
+ if (anchor) anchor.setAttribute("aria-expanded", "false");
3867
+ helpAnchor = null;
3868
+ if (restoreFocus && wasOpen && anchor && document.contains(anchor)) anchor.focus();
3869
+ }
3870
+ function positionHelp() {
3871
+ if (!helpPopover || !helpAnchor || helpPopover.hidden) return;
3872
+ const rect = helpAnchor.getBoundingClientRect();
3873
+ const width = helpPopover.offsetWidth;
3874
+ const height = helpPopover.offsetHeight;
3875
+ const margin = 8;
3876
+ const left = Math.max(margin, Math.min(rect.right - width, window.innerWidth - width - margin));
3877
+ let top = rect.bottom + 6;
3878
+ if (top + height > window.innerHeight - margin) {
3879
+ const above = rect.top - height - 6;
3880
+ top = above >= margin ? above : Math.max(margin, window.innerHeight - height - margin);
3881
+ }
3882
+ helpPopover.style.left = `${left}px`;
3883
+ helpPopover.style.top = `${top}px`;
3884
+ }
3885
+ function openHelp(anchor) {
3886
+ const entry = HELP[anchor.dataset.help ?? ""];
3887
+ if (!entry) return;
3888
+ const popover = ensureHelpPopover();
3889
+ if (helpAnchor && helpAnchor !== anchor) helpAnchor.setAttribute("aria-expanded", "false");
3890
+ popover.innerHTML = `<div class="help-popover-head"><h4 id="accessi-help-title">${escapeHtml(entry.title)}</h4><button type="button" class="help-close" data-help-close aria-label="Chiudi">&times;</button></div><div class="help-popover-body">${entry.blocks.map(renderWikiBlock).join("")}</div>`;
3891
+ popover.setAttribute("aria-labelledby", "accessi-help-title");
3892
+ popover.hidden = false;
3893
+ helpAnchor = anchor;
3894
+ anchor.setAttribute("aria-expanded", "true");
3895
+ bindCopyButtons(popover);
3896
+ positionHelp();
3897
+ (popover.querySelector(".help-close") ?? popover).focus();
3898
+ }
3899
+ function wikiIndexMarkup(selectedId) {
3900
+ return wikiGroups().map(({ group, sections }) => `<div class="wiki-index-group"><span class="wiki-index-group-title">${escapeHtml(group)}</span><ul>${sections.map((section) => `<li><a href="#wiki-${escapeHtml(section.id)}" data-wiki="${escapeHtml(section.id)}"${section.id === selectedId ? ' aria-current="page"' : ""}>${escapeHtml(section.title)}</a></li>`).join("")}</ul></div>`).join("");
3901
+ }
3902
+ async function showWiki(sectionId) {
3903
+ const hashId = /^#wiki-([a-z0-9-]+)$/.exec(window.location.hash)?.[1];
3904
+ const requested = sectionId ?? hashId;
3905
+ const selected = WIKI_SECTIONS.find((section) => section.id === requested) ?? WIKI_SECTIONS[0];
3906
+ if (!selected) return;
3907
+ render(`<div class="wiki">
3908
+ <aside class="wiki-index">
3909
+ <div class="wiki-index-head"><strong>Indice</strong><button type="button" id="wiki-copy-ai">Copia per AI</button></div>
3910
+ ${wikiIndexMarkup(selected.id)}
3911
+ </aside>
3912
+ <article class="wiki-content">
3913
+ <header class="wiki-section-head"><h2>${escapeHtml(selected.title)}</h2><p>${escapeHtml(selected.summary)}</p></header>
3914
+ ${selected.blocks.map(renderWikiBlock).join("")}
3915
+ </article>
3916
+ </div>`);
3917
+ document.querySelectorAll("[data-wiki]").forEach((link) => {
3918
+ link.onclick = (event) => {
3919
+ event.preventDefault();
3920
+ const id = link.dataset.wiki ?? "";
3921
+ window.history.replaceState(null, "", `${routePath("wiki")}#wiki-${id}`);
3922
+ showWiki(id);
3923
+ };
3924
+ });
3925
+ bindCopyButtons(document);
3926
+ byId("wiki-copy-ai").onclick = () => handleAction(async () => {
3927
+ await navigator.clipboard.writeText(buildAiDigest());
3928
+ showNotice("Documentazione per AI copiata negli appunti.");
3929
+ });
3930
+ }
3931
+ function inlineMarkdown(text) {
3932
+ return escapeHtml(text).replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>").replace(/`([^`]+)`/g, "<code>$1</code>");
3933
+ }
3934
+ function renderMarkdown(markdown) {
3935
+ const lines = markdown.replace(/\r\n/g, "\n").split("\n");
3936
+ const html = [];
3937
+ let inList = false;
3938
+ let inCode = false;
3939
+ const closeList = () => {
3940
+ if (inList) {
3941
+ html.push("</ul>");
3942
+ inList = false;
3943
+ }
3944
+ };
3945
+ for (const line of lines) {
3946
+ if (line.startsWith("```")) {
3947
+ closeList();
3948
+ html.push(inCode ? "</code></pre>" : '<pre class="markdown-code"><code>');
3949
+ inCode = !inCode;
3950
+ continue;
3951
+ }
3952
+ if (inCode) {
3953
+ html.push(escapeHtml(line));
3954
+ continue;
3955
+ }
3956
+ const heading = /^(#{1,4})\s+(.*)$/.exec(line);
3957
+ if (heading) {
3958
+ closeList();
3959
+ const level = (heading[1] ?? "").length;
3960
+ html.push(`<h${level}>${inlineMarkdown(heading[2] ?? "")}</h${level}>`);
3961
+ continue;
3962
+ }
3963
+ const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
3964
+ if (bullet) {
3965
+ if (!inList) {
3966
+ html.push("<ul>");
3967
+ inList = true;
3968
+ }
3969
+ html.push(`<li>${inlineMarkdown(bullet[1] ?? "")}</li>`);
3970
+ continue;
3971
+ }
3972
+ if (line.trim() === "") {
3973
+ closeList();
3974
+ continue;
3975
+ }
3976
+ closeList();
3977
+ html.push(`<p>${inlineMarkdown(line)}</p>`);
3978
+ }
3979
+ if (inCode) html.push("</code></pre>");
3980
+ closeList();
3981
+ return html.join("");
3982
+ }
3983
+ async function showChangelog() {
3984
+ render(`<article class="wiki-content markdown-body changelog-page">
3985
+ <header class="wiki-section-head"><h2>Changelog</h2><p>Novita e modifiche rilevanti del modulo Accessi.</p></header>
3986
+ ${renderMarkdown(changelog_default)}
3987
+ </article>`);
3988
+ }
3989
+ function downloadTextFile(fileName, content) {
3990
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
3991
+ const url = URL.createObjectURL(blob);
3992
+ const anchor = document.createElement("a");
3993
+ anchor.href = url;
3994
+ anchor.download = fileName;
3995
+ document.body.appendChild(anchor);
3996
+ anchor.click();
3997
+ anchor.remove();
3998
+ URL.revokeObjectURL(url);
3999
+ }
4000
+ async function showSql() {
4001
+ const schema = result(await getSchema());
4002
+ const copy = (text, notice) => () => handleAction(async () => {
4003
+ await navigator.clipboard.writeText(text);
4004
+ showNotice(notice);
4005
+ });
4006
+ render(`<div class="toolbar"><button id="sql-copy-all">Copia tutto</button><button id="sql-download" class="secondary">Scarica .sql</button><button id="sql-reload" class="secondary">Aggiorna</button></div>
4007
+ <h2>SQL dello schema</h2>
4008
+ <p class="form-help">DDL generato dallo schema Accessi <strong>${escapeHtml(schema.version)}</strong>. Usalo per creare le entita su una nuova installazione (database vuoto).</p>
4009
+ <div class="entity-summary">
4010
+ <span><strong>${schema.presentTables.length}</strong> tabelle presenti</span>
4011
+ <span><strong>${schema.presentGenerators.length}</strong> generatori</span>
4012
+ <span><strong>${schema.extraTables.length}</strong> tabelle extra fuori schema Accessi</span>
4013
+ </div>
4014
+ ${schema.extraTables.length ? `<p class="form-help">Tabelle presenti non gestite da Accessi: <code>${schema.extraTables.map((name) => escapeHtml(name)).join(", ")}</code></p>` : ""}
4015
+ ${schema.sections.map((section) => `<section class="sql-section"><div class="sql-section-head"><h3>${escapeHtml(section.title)}</h3><button type="button" class="secondary" data-sql-copy="${escapeHtml(section.id)}">Copia sezione</button></div><pre class="markdown-code"><code>${escapeHtml(section.sql)}</code></pre></section>`).join("")}`);
4016
+ byId("sql-copy-all").onclick = copy(schema.script, "Script SQL completo copiato.");
4017
+ byId("sql-download").onclick = () => downloadTextFile(`accessi-schema-${schema.version}.sql`, schema.script);
4018
+ byId("sql-reload").onclick = () => handleAction(showSql);
4019
+ document.querySelectorAll("[data-sql-copy]").forEach((button) => {
4020
+ const section = schema.sections.find((entry) => entry.id === button.dataset.sqlCopy);
4021
+ if (section) button.onclick = copy(section.sql, `Sezione "${section.title}" copiata.`);
4022
+ });
4023
+ }
4024
+ async function show(view) {
4025
+ try {
4026
+ const views = {
4027
+ users: showUsers,
4028
+ roles: showRoles,
4029
+ menuGroups: showMenuGroups,
4030
+ menuItems: showMenuItems,
4031
+ menuTypes: showMenuTypes,
4032
+ menuScopes: showMenuScopes,
4033
+ ssoProviders: showSsoProviders,
4034
+ ssoUsers: showSsoUsers,
4035
+ tokens: showServiceTokens,
4036
+ wiki: showWiki,
4037
+ changelog: showChangelog,
4038
+ sql: showSql
4039
+ };
4040
+ setActiveNavigation(view);
4041
+ const renderView = views[view];
4042
+ if (!renderView) throw new Error("Sezione console non valida.");
4043
+ await renderView();
4044
+ } catch (error) {
4045
+ render("");
4046
+ showNotice(error instanceof Error ? error.message : "Operazione non riuscita.", true);
4047
+ }
4048
+ }
4049
+ var loginForm = document.getElementById("login-form");
4050
+ if (loginForm) loginForm.onsubmit = loginView;
4051
+ byId("restart-login").onclick = resetLogin;
4052
+ var bootstrapAdminForm = document.getElementById("bootstrap-admin-form");
4053
+ if (bootstrapAdminForm) {
4054
+ bootstrapAdminForm.onsubmit = async (event) => {
4055
+ event.preventDefault();
4056
+ const status = document.getElementById("bootstrap-admin-status");
4057
+ try {
4058
+ if (status) status.textContent = "";
4059
+ const values = formValues(bootstrapAdminForm);
4060
+ const fields = new FormData(bootstrapAdminForm);
4061
+ const created = result(await bootstrapAdmin({
4062
+ token: values.token ?? "",
4063
+ email: values.email ?? "",
4064
+ nome: values.nome || void 0,
4065
+ cognome: values.cognome || void 0,
4066
+ password: values.password || void 0,
4067
+ superUser: fields.has("superUser")
4068
+ }));
4069
+ const generated = created.password ? ` Password generata (copiala ora): ${created.password}` : "";
4070
+ if (status) status.textContent = `Utente admin creato: ${created.email}.${generated} Ora accedi dal form principale.`;
4071
+ bootstrapAdminForm.reset();
4072
+ } catch (error) {
4073
+ if (status) status.textContent = error instanceof Error ? error.message : "Creazione non riuscita.";
4074
+ }
4075
+ };
4076
+ }
4077
+ byId("two-factor-form").onsubmit = async (event) => {
4078
+ event.preventDefault();
4079
+ if (!pendingChallenge) return;
4080
+ try {
4081
+ byId("login-error").textContent = "";
4082
+ const values = formValues(event.currentTarget);
4083
+ await completeLoginStep(result(await verifyTwoFactor({ challengeId: pendingChallenge, code: values.code ?? "" })));
4084
+ } catch (error) {
4085
+ byId("login-error").textContent = error instanceof Error ? error.message : "Verifica non riuscita.";
4086
+ }
4087
+ };
4088
+ byId("resend-code").onclick = async () => {
4089
+ if (!pendingChallenge) return;
4090
+ try {
4091
+ byId("login-error").textContent = "";
4092
+ await completeLoginStep(result(await resendTwoFactor({ challengeId: pendingChallenge })));
4093
+ } catch (error) {
4094
+ byId("login-error").textContent = error instanceof Error ? error.message : "Reinvio non riuscito.";
4095
+ }
4096
+ };
4097
+ byId("logout").onclick = () => {
4098
+ void (async () => {
4099
+ if (cookieMode) {
4100
+ await revokeToken({}).catch(() => void 0);
4101
+ }
4102
+ resetLogin();
4103
+ token = null;
4104
+ currentUser = null;
4105
+ setAccessiConsoleToken(null);
4106
+ sessionStorage.removeItem("accessi-console-token");
4107
+ window.history.replaceState(null, "", consoleBasePath);
4108
+ void bootstrap();
4109
+ })();
4110
+ };
4111
+ byId("nav").onclick = (event) => {
4112
+ const link = event.target.closest("a[data-view]");
4113
+ if (!link) return;
4114
+ event.preventDefault();
4115
+ handleAction(() => navigate(link.dataset.view));
4116
+ };
4117
+ window.addEventListener("popstate", () => {
4118
+ if (token && currentUser) handleAction(() => show(viewFromLocation()));
4119
+ });
4120
+ window.addEventListener(ACCESSI_UNAUTHORIZED_EVENT, () => {
4121
+ if (!token && !cookieMode) return;
4122
+ token = null;
4123
+ currentUser = null;
4124
+ setAccessiConsoleToken(null);
4125
+ sessionStorage.removeItem("accessi-console-token");
4126
+ finishBoot("login");
4127
+ resetLogin();
4128
+ byId("login-error").textContent = "Sessione scaduta o non piu valida. Accedi di nuovo.";
4129
+ });
4130
+ window.addEventListener("accessi-console-network", (event) => {
4131
+ updateLoadingState(event.detail.active);
4132
+ });
4133
+ document.addEventListener("click", (event) => {
4134
+ const target = event.target;
4135
+ const dot = target.closest("[data-help]");
4136
+ if (dot) {
4137
+ event.preventDefault();
4138
+ if (helpAnchor === dot && helpPopover && !helpPopover.hidden) {
4139
+ closeHelp();
4140
+ } else {
4141
+ openHelp(dot);
4142
+ }
4143
+ return;
4144
+ }
4145
+ if (target.closest("[data-help-close]")) {
4146
+ closeHelp();
4147
+ return;
4148
+ }
4149
+ if (helpPopover && !helpPopover.hidden && !target.closest(".help-popover")) closeHelp();
4150
+ });
4151
+ window.addEventListener("keydown", (event) => {
4152
+ if (event.key === "Escape") closeHelp();
4153
+ });
4154
+ window.addEventListener("resize", positionHelp);
4155
+ window.addEventListener("scroll", positionHelp, true);
4156
+ void applyAdminBootstrapVisibility();
4157
+ var entryTicket = /^#entry=([a-f0-9]{64})$/i.exec(window.location.hash)?.[1];
4158
+ var ssoChallenge = /^#two-factor=([a-f0-9]{64})$/.exec(window.location.hash)?.[1];
4159
+ if (entryTicket) {
4160
+ window.history.replaceState(null, "", window.location.pathname);
4161
+ void exchangeConsoleEntry(entryTicket);
4162
+ } else if (ssoChallenge) {
4163
+ token = null;
4164
+ setAccessiConsoleToken(null);
4165
+ sessionStorage.removeItem("accessi-console-token");
4166
+ window.history.replaceState(null, "", window.location.pathname);
4167
+ finishBoot("login");
4168
+ showTwoFactor({ challengeId: ssoChallenge, resendAfterSeconds: 60 });
4169
+ } else if (token || cookieMode) {
4170
+ void bootstrap();
4171
+ } else {
4172
+ finishBoot("login");
4173
+ }