emilsoftware-utilities 2.0.0-dev.23 → 2.0.0-dev.25

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 (261) hide show
  1. package/README.MD +147 -0
  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 +8 -2
  6. package/dist/DailyFileTransport.js +9 -2
  7. package/dist/DailyFileTransport.js.map +1 -1
  8. package/dist/DatabaseUpdater.js +8 -3
  9. package/dist/DatabaseUpdater.js.map +1 -1
  10. package/dist/ExecutionTimeLogger.d.ts +2 -2
  11. package/dist/ExecutionTimeLogger.js +3 -2
  12. package/dist/ExecutionTimeLogger.js.map +1 -1
  13. package/dist/Logger.d.ts +22 -9
  14. package/dist/Logger.js +131 -54
  15. package/dist/Logger.js.map +1 -1
  16. package/dist/Orm.d.ts +26 -6
  17. package/dist/Orm.js +144 -40
  18. package/dist/Orm.js.map +1 -1
  19. package/dist/Utilities.d.ts +12 -7
  20. package/dist/Utilities.js +57 -22
  21. package/dist/Utilities.js.map +1 -1
  22. package/dist/accessi-module/AccessiModule.d.ts +9 -0
  23. package/dist/accessi-module/AccessiModule.js +12 -4
  24. package/dist/accessi-module/AccessiModule.js.map +1 -1
  25. package/dist/accessi-module/Console/console.css +529 -281
  26. package/dist/accessi-module/Console/console.js +2081 -85
  27. package/dist/accessi-module/Console/console.js.map +1 -1
  28. package/dist/accessi-module/Console/generated/accessiApi.d.ts +167 -1
  29. package/dist/accessi-module/Console/generated/accessiApi.js +156 -2
  30. package/dist/accessi-module/Console/generated/accessiApi.js.map +1 -1
  31. package/dist/accessi-module/Console/generated/model/abilitazioneMenu.d.ts +20 -8
  32. package/dist/accessi-module/Console/generated/model/abilitazioneMenu.js.map +1 -1
  33. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.d.ts +23 -0
  34. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.js +3 -0
  35. package/dist/accessi-module/Console/generated/model/createFilterTypeRequest.js.map +1 -0
  36. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.d.ts +14 -0
  37. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.js +15 -0
  38. package/dist/accessi-module/Console/generated/model/createFilterTypeRequestFlgEnabled.js.map +1 -0
  39. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.d.ts +27 -0
  40. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.js +9 -0
  41. package/dist/accessi-module/Console/generated/model/createMenuGroupRequest.js.map +1 -0
  42. package/dist/accessi-module/Console/generated/model/createMenuRequest.d.ts +52 -0
  43. package/dist/accessi-module/Console/generated/model/createMenuRequest.js +9 -0
  44. package/dist/accessi-module/Console/generated/model/createMenuRequest.js.map +1 -0
  45. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.d.ts +18 -0
  46. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.js +9 -0
  47. package/dist/accessi-module/Console/generated/model/createMenuTypeRequest.js.map +1 -0
  48. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.d.ts +16 -0
  49. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.js +9 -0
  50. package/dist/accessi-module/Console/generated/model/createServiceTokenRequest.js.map +1 -0
  51. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.d.ts +14 -0
  52. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.js +3 -0
  53. package/dist/accessi-module/Console/generated/model/createServiceTokenResponse.js.map +1 -0
  54. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.d.ts +14 -0
  55. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.js +3 -0
  56. package/dist/accessi-module/Console/generated/model/getMenuTypesResponse.js.map +1 -0
  57. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.d.ts +12 -0
  58. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.js +9 -0
  59. package/dist/accessi-module/Console/generated/model/getServiceTokensParams.js.map +1 -0
  60. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.d.ts +14 -0
  61. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.js +3 -0
  62. package/dist/accessi-module/Console/generated/model/getServiceTokensResponse.js.map +1 -0
  63. package/dist/accessi-module/Console/generated/model/getUsersParams.d.ts +8 -0
  64. package/dist/accessi-module/Console/generated/model/getUsersParams.js.map +1 -1
  65. package/dist/accessi-module/Console/generated/model/index.d.ts +18 -0
  66. package/dist/accessi-module/Console/generated/model/index.js +18 -0
  67. package/dist/accessi-module/Console/generated/model/index.js.map +1 -1
  68. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.d.ts +30 -0
  69. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.js +9 -0
  70. package/dist/accessi-module/Console/generated/model/issuedServiceTokenDto.js.map +1 -0
  71. package/dist/accessi-module/Console/generated/model/menuTypeEntity.d.ts +12 -0
  72. package/dist/accessi-module/Console/generated/model/menuTypeEntity.js +9 -0
  73. package/dist/accessi-module/Console/generated/model/menuTypeEntity.js.map +1 -0
  74. package/dist/accessi-module/Console/generated/model/serviceTokenDto.d.ts +28 -0
  75. package/dist/accessi-module/Console/generated/model/serviceTokenDto.js +9 -0
  76. package/dist/accessi-module/Console/generated/model/serviceTokenDto.js.map +1 -0
  77. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.d.ts +21 -0
  78. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.js +3 -0
  79. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequest.js.map +1 -0
  80. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.d.ts +14 -0
  81. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.js +15 -0
  82. package/dist/accessi-module/Console/generated/model/updateFilterTypeRequestFlgEnabled.js.map +1 -0
  83. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.d.ts +22 -0
  84. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.js +9 -0
  85. package/dist/accessi-module/Console/generated/model/updateMenuGroupRequest.js.map +1 -0
  86. package/dist/accessi-module/Console/generated/model/updateMenuRequest.d.ts +47 -0
  87. package/dist/accessi-module/Console/generated/model/updateMenuRequest.js +9 -0
  88. package/dist/accessi-module/Console/generated/model/updateMenuRequest.js.map +1 -0
  89. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.d.ts +13 -0
  90. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.js +9 -0
  91. package/dist/accessi-module/Console/generated/model/updateMenuTypeRequest.js.map +1 -0
  92. package/dist/accessi-module/Console/help.d.ts +13 -0
  93. package/dist/accessi-module/Console/help.js +348 -0
  94. package/dist/accessi-module/Console/help.js.map +1 -0
  95. package/dist/accessi-module/Console/index.html +89 -21
  96. package/dist/accessi-module/Console/wiki.d.ts +52 -0
  97. package/dist/accessi-module/Console/wiki.js +689 -0
  98. package/dist/accessi-module/Console/wiki.js.map +1 -0
  99. package/dist/accessi-module/Controllers/AccessiConsoleController.d.ts +8 -1
  100. package/dist/accessi-module/Controllers/AccessiConsoleController.js +52 -15
  101. package/dist/accessi-module/Controllers/AccessiConsoleController.js.map +1 -1
  102. package/dist/accessi-module/Controllers/ConfiguratorController.d.ts +1 -3
  103. package/dist/accessi-module/Controllers/ConfiguratorController.js +6 -8
  104. package/dist/accessi-module/Controllers/ConfiguratorController.js.map +1 -1
  105. package/dist/accessi-module/Controllers/EmailController.d.ts +1 -1
  106. package/dist/accessi-module/Controllers/EmailController.js +1 -1
  107. package/dist/accessi-module/Controllers/EmailController.js.map +1 -1
  108. package/dist/accessi-module/Controllers/FiltriController.d.ts +5 -4
  109. package/dist/accessi-module/Controllers/FiltriController.js +92 -4
  110. package/dist/accessi-module/Controllers/FiltriController.js.map +1 -1
  111. package/dist/accessi-module/Controllers/PermissionController.d.ts +11 -0
  112. package/dist/accessi-module/Controllers/PermissionController.js +273 -8
  113. package/dist/accessi-module/Controllers/PermissionController.js.map +1 -1
  114. package/dist/accessi-module/Controllers/ServiceTokenController.d.ts +23 -0
  115. package/dist/accessi-module/Controllers/ServiceTokenController.js +176 -0
  116. package/dist/accessi-module/Controllers/ServiceTokenController.js.map +1 -0
  117. package/dist/accessi-module/Controllers/UserController.d.ts +2 -2
  118. package/dist/accessi-module/Controllers/UserController.js +25 -9
  119. package/dist/accessi-module/Controllers/UserController.js.map +1 -1
  120. package/dist/accessi-module/Dtos/Abilitazione.js.map +1 -1
  121. package/dist/accessi-module/Dtos/AbilitazioneMenu.js +16 -8
  122. package/dist/accessi-module/Dtos/AbilitazioneMenu.js.map +1 -1
  123. package/dist/accessi-module/Dtos/AssignPermissionsToUserRequest.js.map +1 -1
  124. package/dist/accessi-module/Dtos/AssignRolesToUserRequest.js.map +1 -1
  125. package/dist/accessi-module/Dtos/AuthenticatedUserPayloadDto.js.map +1 -1
  126. package/dist/accessi-module/Dtos/BaseResponse.js.map +1 -1
  127. package/dist/accessi-module/Dtos/CatalogDtos.d.ts +66 -0
  128. package/dist/accessi-module/Dtos/CatalogDtos.js +336 -0
  129. package/dist/accessi-module/Dtos/CatalogDtos.js.map +1 -0
  130. package/dist/accessi-module/Dtos/ConfirmResetPasswordRequest.js.map +1 -1
  131. package/dist/accessi-module/Dtos/FiltriUtente.js.map +1 -1
  132. package/dist/accessi-module/Dtos/GetGroupsWithMenusResponse.js.map +1 -1
  133. package/dist/accessi-module/Dtos/GetMenusResponse.js +1 -1
  134. package/dist/accessi-module/Dtos/GetMenusResponse.js.map +1 -1
  135. package/dist/accessi-module/Dtos/GetRolesResponse.js.map +1 -1
  136. package/dist/accessi-module/Dtos/GetUserByTokenRequest.js.map +1 -1
  137. package/dist/accessi-module/Dtos/GetUserByTokenResponse.js.map +1 -1
  138. package/dist/accessi-module/Dtos/GetUsersResponse.d.ts +1 -1
  139. package/dist/accessi-module/Dtos/GetUsersResponse.js.map +1 -1
  140. package/dist/accessi-module/Dtos/LoginRequest.js.map +1 -1
  141. package/dist/accessi-module/Dtos/LoginResponse.d.ts +1 -1
  142. package/dist/accessi-module/Dtos/LoginResponse.js +1 -1
  143. package/dist/accessi-module/Dtos/LoginResponse.js.map +1 -1
  144. package/dist/accessi-module/Dtos/MenuItem.js +1 -0
  145. package/dist/accessi-module/Dtos/Permission.js.map +1 -1
  146. package/dist/accessi-module/Dtos/RegisterRequest.js +2 -1
  147. package/dist/accessi-module/Dtos/RegisterRequest.js.map +1 -1
  148. package/dist/accessi-module/Dtos/RegisterResponse.js.map +1 -1
  149. package/dist/accessi-module/Dtos/Role.js.map +1 -1
  150. package/dist/accessi-module/Dtos/RuoliMenu.js.map +1 -1
  151. package/dist/accessi-module/Dtos/SendResetPasswordEmailRequest.js.map +1 -1
  152. package/dist/accessi-module/Dtos/ServiceToken.d.ts +34 -0
  153. package/dist/accessi-module/Dtos/ServiceToken.js +128 -0
  154. package/dist/accessi-module/Dtos/ServiceToken.js.map +1 -0
  155. package/dist/accessi-module/Dtos/SetStatoRegistrazione.js.map +1 -1
  156. package/dist/accessi-module/Dtos/TipoFiltro.js.map +1 -1
  157. package/dist/accessi-module/Dtos/TokenResult.js.map +1 -1
  158. package/dist/accessi-module/Dtos/TwoFactorDtos.js.map +1 -1
  159. package/dist/accessi-module/Dtos/UpdateEnabledStatusRequest.js.map +1 -1
  160. package/dist/accessi-module/Dtos/UserDto.js +4 -2
  161. package/dist/accessi-module/Dtos/UserDto.js.map +1 -1
  162. package/dist/accessi-module/Dtos/UserGrantsDto.js.map +1 -1
  163. package/dist/accessi-module/Dtos/UserGrantsResponse.js.map +1 -1
  164. package/dist/accessi-module/Dtos/index.d.ts +2 -0
  165. package/dist/accessi-module/Dtos/index.js +2 -0
  166. package/dist/accessi-module/Dtos/index.js.map +1 -1
  167. package/dist/accessi-module/Services/AuthService/AuthService.d.ts +2 -2
  168. package/dist/accessi-module/Services/AuthService/AuthService.js +36 -36
  169. package/dist/accessi-module/Services/AuthService/AuthService.js.map +1 -1
  170. package/dist/accessi-module/Services/EmailService/EmailService.d.ts +2 -1
  171. package/dist/accessi-module/Services/EmailService/EmailService.js +9 -6
  172. package/dist/accessi-module/Services/EmailService/EmailService.js.map +1 -1
  173. package/dist/accessi-module/Services/FiltriService/FiltriService.d.ts +10 -3
  174. package/dist/accessi-module/Services/FiltriService/FiltriService.js +64 -6
  175. package/dist/accessi-module/Services/FiltriService/FiltriService.js.map +1 -1
  176. package/dist/accessi-module/Services/PermissionService/PermissionService.d.ts +44 -1
  177. package/dist/accessi-module/Services/PermissionService/PermissionService.js +418 -110
  178. package/dist/accessi-module/Services/PermissionService/PermissionService.js.map +1 -1
  179. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.d.ts +75 -0
  180. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.js +265 -0
  181. package/dist/accessi-module/Services/ServiceTokenService/ServiceTokenService.js.map +1 -0
  182. package/dist/accessi-module/Services/TwoFactorService/TwoFactorService.js +23 -27
  183. package/dist/accessi-module/Services/TwoFactorService/TwoFactorService.js.map +1 -1
  184. package/dist/accessi-module/Services/UserService/UserService.d.ts +10 -3
  185. package/dist/accessi-module/Services/UserService/UserService.js +170 -84
  186. package/dist/accessi-module/Services/UserService/UserService.js.map +1 -1
  187. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.d.ts +9 -0
  188. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js +76 -28
  189. package/dist/accessi-module/database-updates/AccessiDatabaseUpdater.js.map +1 -1
  190. package/dist/accessi-module/database-updates/accessiSchema.d.ts +1 -1
  191. package/dist/accessi-module/database-updates/accessiSchema.js +9 -1
  192. package/dist/accessi-module/database-updates/accessiSchema.js.map +1 -1
  193. package/dist/accessi-module/database-updates/optionalColumns.d.ts +2 -0
  194. package/dist/accessi-module/database-updates/optionalColumns.js +33 -1
  195. package/dist/accessi-module/database-updates/optionalColumns.js.map +1 -1
  196. package/dist/accessi-module/database-updates/runAccessiDbUpdate.js +7 -2
  197. package/dist/accessi-module/database-updates/runAccessiDbUpdate.js.map +1 -1
  198. package/dist/accessi-module/federated-auth/FederatedAuthController.d.ts +1 -0
  199. package/dist/accessi-module/federated-auth/FederatedAuthController.js +26 -0
  200. package/dist/accessi-module/federated-auth/FederatedAuthController.js.map +1 -1
  201. package/dist/accessi-module/federated-auth/FederatedAuthService.d.ts +6 -0
  202. package/dist/accessi-module/federated-auth/FederatedAuthService.js +28 -1
  203. package/dist/accessi-module/federated-auth/FederatedAuthService.js.map +1 -1
  204. package/dist/accessi-module/index.d.ts +4 -1
  205. package/dist/accessi-module/index.js +8 -2
  206. package/dist/accessi-module/index.js.map +1 -1
  207. package/dist/accessi-module/jwt/jwt.strategy.js +8 -5
  208. package/dist/accessi-module/jwt/jwt.strategy.js.map +1 -1
  209. package/dist/accessi-module/middleware/accessiRequirements.d.ts +1 -1
  210. package/dist/accessi-module/middleware/accessiRequirements.js.map +1 -1
  211. package/dist/accessi-module/middleware/authenticateGen.js +11 -7
  212. package/dist/accessi-module/middleware/authenticateGen.js.map +1 -1
  213. package/dist/accessi-module/security/accessControl.js +8 -3
  214. package/dist/accessi-module/security/accessControl.js.map +1 -1
  215. package/dist/accessi-module/security/authenticatedToken.d.ts +1 -1
  216. package/dist/accessi-module/security/authenticatedToken.js +2 -1
  217. package/dist/accessi-module/security/authenticatedToken.js.map +1 -1
  218. package/dist/accessi-module/security/passwordResetToken.d.ts +9 -3
  219. package/dist/accessi-module/security/passwordResetToken.js +9 -5
  220. package/dist/accessi-module/security/passwordResetToken.js.map +1 -1
  221. package/dist/accessi-module/security/publicAuthRateLimit.d.ts +1 -1
  222. package/dist/accessi-module/security/publicAuthRateLimit.js +28 -8
  223. package/dist/accessi-module/security/publicAuthRateLimit.js.map +1 -1
  224. package/dist/accessi-module/security/serviceTokenGuard.d.ts +25 -0
  225. package/dist/accessi-module/security/serviceTokenGuard.js +85 -0
  226. package/dist/accessi-module/security/serviceTokenGuard.js.map +1 -0
  227. package/dist/accessi-module/security/serviceTokenRateLimit.d.ts +8 -0
  228. package/dist/accessi-module/security/serviceTokenRateLimit.js +49 -0
  229. package/dist/accessi-module/security/serviceTokenRateLimit.js.map +1 -0
  230. package/dist/allegati-module/AllegatiModule.d.ts +11 -0
  231. package/dist/allegati-module/AllegatiModule.js +11 -1
  232. package/dist/allegati-module/AllegatiModule.js.map +1 -1
  233. package/dist/allegati-module/Controllers/AllegatiController.d.ts +2 -1
  234. package/dist/allegati-module/Controllers/AllegatiController.js +23 -15
  235. package/dist/allegati-module/Controllers/AllegatiController.js.map +1 -1
  236. package/dist/allegati-module/Dtos/AllegatoDto.js.map +1 -1
  237. package/dist/allegati-module/Dtos/BaseResponse.js.map +1 -1
  238. package/dist/allegati-module/Dtos/UploadSingleFileRequest.js.map +1 -1
  239. package/dist/allegati-module/Dtos/responses/DownloadAllegatoResponseDto.js.map +1 -1
  240. package/dist/allegati-module/Dtos/responses/GetListResponse.js.map +1 -1
  241. package/dist/allegati-module/Dtos/responses/UploadAllegatoResponseDto.js.map +1 -1
  242. package/dist/allegati-module/Services/AllegatiService/AllegatiService.d.ts +10 -3
  243. package/dist/allegati-module/Services/AllegatiService/AllegatiService.js +101 -52
  244. package/dist/allegati-module/Services/AllegatiService/AllegatiService.js.map +1 -1
  245. package/dist/allegati-module/security/allegatiAuthorizationGuard.d.ts +14 -0
  246. package/dist/allegati-module/security/allegatiAuthorizationGuard.js +70 -0
  247. package/dist/allegati-module/security/allegatiAuthorizationGuard.js.map +1 -0
  248. package/dist/autobind.d.ts +10 -3
  249. package/dist/autobind.js +24 -9
  250. package/dist/autobind.js.map +1 -1
  251. package/dist/emilsoftware-utilities-2.0.0-dev.25.tgz +0 -0
  252. package/dist/firebird-compat.d.ts +13 -0
  253. package/dist/firebird-compat.js +3 -2
  254. package/dist/firebird-compat.js.map +1 -1
  255. package/dist/scripts/generateAccessiWiki.d.ts +1 -0
  256. package/dist/scripts/generateAccessiWiki.js +131 -0
  257. package/dist/scripts/generateAccessiWiki.js.map +1 -0
  258. package/dist/swagger/SwaggerConfig.js +6 -3
  259. package/dist/swagger/SwaggerConfig.js.map +1 -1
  260. package/package.json +19 -8
  261. package/dist/emilsoftware-utilities-2.0.0-dev.23.tgz +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServiceTokenService.js","sourceRoot":"","sources":["../../../../src/accessi-module/Services/ServiceTokenService/ServiceTokenService.ts"],"names":[],"mappings":"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","sourcesContent":["import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';\nimport { createHash, randomBytes, timingSafeEqual } from 'crypto';\nimport { autobind } from '../../../autobind';\nimport { Orm } from '../../../Orm';\nimport { RestUtilities } from '../../../Utilities';\nimport type { AccessiOptions } from '../../AccessiModule';\n\n/** Prefisso riconoscibile dei token di servizio; il formato completo e `st_<id>.<segreto>`. */\nexport const SERVICE_TOKEN_PREFIX = 'st_';\nconst TOKEN_ID_PATTERN = /^st_[a-f0-9]{32}$/;\nconst SCOPE_PATTERN = /^[a-z0-9:_-]{1,40}$/i;\nconst LAST_USED_REFRESH_MS = 60_000;\n\n/** Metadati pubblici di un token (mai il segreto). */\nexport interface ServiceTokenMetadata {\n tokenId: string;\n label: string;\n scopes: string[];\n createdAt?: string;\n expiresAt?: string;\n lastUsedAt?: string;\n lastUsedIp?: string;\n revoked: boolean;\n revokedAt?: string;\n revokedBy?: number;\n}\n\n/** Risultato dell'emissione: contiene il segreto in chiaro, mostrato una sola volta. */\nexport interface IssuedServiceToken extends ServiceTokenMetadata {\n token: string;\n}\n\n/** Risultato della verifica di un token presentato. */\nexport interface VerifiedServiceToken {\n tokenId: string;\n label: string;\n scopes: string[];\n}\n\nexport interface IssueServiceTokenInput {\n label: string;\n scopes?: string[];\n expiresAt?: Date | string | null;\n /** Giorni di validita; ignorato se `expiresAt` e valorizzato. */\n ttlDays?: number;\n}\n\n/** Genera un nuovo token di servizio: identificativo pubblico + segreto ad alta entropia. */\nexport function generateServiceToken(): { token: string; tokenId: string; secret: string } {\n const tokenId = `${SERVICE_TOKEN_PREFIX}${randomBytes(16).toString('hex')}`;\n const secret = randomBytes(32).toString('base64url');\n return { token: `${tokenId}.${secret}`, tokenId, secret };\n}\n\n/** Estrae identificativo e segreto; restituisce `null` se il formato non e valido. */\nexport function parseServiceToken(token: unknown): { tokenId: string; secret: string } | null {\n if (typeof token !== 'string') {\n return null;\n }\n\n const separator = token.indexOf('.');\n if (separator <= 0) {\n return null;\n }\n\n const tokenId = token.slice(0, separator);\n const secret = token.slice(separator + 1);\n if (!TOKEN_ID_PATTERN.test(tokenId) || secret.length < 16) {\n return null;\n }\n\n return { tokenId, secret };\n}\n\n/** Hash del segreto per lo storage at-rest. Il segreto e ad alta entropia (256 bit), non una password. */\nexport function hashServiceTokenSecret(secret: string): string {\n return createHash('sha256').update(secret, 'utf8').digest('hex');\n}\n\n/** Normalizza e valida gli scope: trim, dedup e controllo del formato. */\nexport function normalizeServiceTokenScopes(scopes: string[] | undefined): string[] {\n if (scopes === undefined) {\n return [];\n }\n if (!Array.isArray(scopes)) {\n throw new BadRequestException('Gli scope devono essere un array di stringhe.');\n }\n\n const normalized: string[] = [];\n for (const rawScope of scopes) {\n if (typeof rawScope !== 'string' || !SCOPE_PATTERN.test(rawScope.trim())) {\n throw new BadRequestException(`Scope non valido: ${String(rawScope)}. Usare lettere, numeri, ':', '_' o '-'.`);\n }\n const scope = rawScope.trim().toLowerCase();\n if (!normalized.includes(scope)) {\n normalized.push(scope);\n }\n }\n return normalized;\n}\n\n/** True se l'insieme di scope soddisfa quello richiesto (uno scope vuoto non richiede nulla). */\nexport function serviceTokenHasScope(scopes: string[], required: string | undefined): boolean {\n if (!required) {\n return true;\n }\n return scopes.includes(required.toLowerCase());\n}\n\nfunction toIso(value: unknown): string | undefined {\n if (value === undefined || value === null) {\n return undefined;\n }\n const date = value instanceof Date ? value : new Date(String(value));\n return Number.isNaN(date.getTime()) ? undefined : date.toISOString();\n}\n\nfunction resolveExpiration(input: IssueServiceTokenInput, defaultTtlDays?: number): Date | null {\n if (input.expiresAt !== undefined && input.expiresAt !== null && input.expiresAt !== '') {\n const expiresAt = input.expiresAt instanceof Date ? input.expiresAt : new Date(String(input.expiresAt));\n if (Number.isNaN(expiresAt.getTime())) {\n throw new BadRequestException('Data di scadenza non valida.');\n }\n if (expiresAt.getTime() <= Date.now()) {\n throw new BadRequestException('La data di scadenza deve essere nel futuro.');\n }\n return expiresAt;\n }\n\n const ttlDays = input.ttlDays ?? defaultTtlDays;\n if (typeof ttlDays === 'number' && Number.isFinite(ttlDays) && ttlDays > 0) {\n return new Date(Date.now() + ttlDays * 24 * 60 * 60 * 1000);\n }\n\n return null;\n}\n\n/** Gestisce il ciclo di vita dei token di servizio (emissione, elenco, revoca, rotazione, verifica). */\n@autobind\n@Injectable()\nexport class ServiceTokenService {\n constructor(@Inject('ACCESSI_OPTIONS') private readonly accessiOptions: AccessiOptions) {}\n\n private get databaseOptions() {\n return this.accessiOptions.databaseOptions;\n }\n\n private toMetadata(row: Record<string, unknown>): ServiceTokenMetadata {\n const scopesRaw = typeof row.scopes === 'string' ? row.scopes.trim() : '';\n return {\n tokenId: String(row.tokenId ?? ''),\n label: typeof row.label === 'string' ? row.label : '',\n scopes: scopesRaw === '' ? [] : scopesRaw.split(/\\s+/),\n createdAt: toIso(row.createdAt),\n expiresAt: toIso(row.expiresAt),\n lastUsedAt: toIso(row.lastUsedAt),\n lastUsedIp: typeof row.lastUsedIp === 'string' && row.lastUsedIp !== '' ? row.lastUsedIp : undefined,\n revoked: row.revokedAt !== undefined && row.revokedAt !== null,\n revokedAt: toIso(row.revokedAt),\n revokedBy: row.revokedBy == null ? undefined : Number(row.revokedBy),\n };\n }\n\n private async readRow(tokenId: string): Promise<Record<string, unknown> | undefined> {\n const rows = await Orm.query(\n this.databaseOptions,\n `SELECT TOKEN_ID, TOKEN_HASH, LABEL, SCOPES, CREATED_BY, CREATED_AT, EXPIRES_AT, LAST_USED_AT, LAST_USED_IP, REVOKED_AT, REVOKED_BY\n FROM ACCESSI_SERVICE_TOKEN WHERE TOKEN_ID = ?`,\n [tokenId],\n false,\n );\n return (rows.map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>)[0];\n }\n\n /** Crea un nuovo token. Il segreto in chiaro e restituito una sola volta e non e recuperabile. */\n async issue(input: IssueServiceTokenInput, createdBy?: number): Promise<IssuedServiceToken> {\n const label = typeof input?.label === 'string' ? input.label.trim() : '';\n if (label.length < 1 || label.length > 100) {\n throw new BadRequestException('La descrizione del token deve essere compresa tra 1 e 100 caratteri.');\n }\n\n const scopes = normalizeServiceTokenScopes(input.scopes);\n const expiresAt = resolveExpiration(input, this.accessiOptions.serviceTokens?.defaultTtlDays);\n const { token, tokenId, secret } = generateServiceToken();\n\n await Orm.execute(\n this.databaseOptions,\n `INSERT INTO ACCESSI_SERVICE_TOKEN (TOKEN_ID, TOKEN_HASH, LABEL, SCOPES, CREATED_BY, EXPIRES_AT)\n VALUES (?, ?, ?, ?, ?, ?)`,\n [tokenId, hashServiceTokenSecret(secret), label, scopes.join(' '), createdBy ?? null, expiresAt],\n );\n\n const row = await this.readRow(tokenId);\n return { ...this.toMetadata(row ?? { tokenId, label, scopes: scopes.join(' ') }), token };\n }\n\n /** Elenca i token (metadati soltanto, mai il segreto). */\n async list(includeRevoked = false): Promise<ServiceTokenMetadata[]> {\n const rows = await Orm.query(\n this.databaseOptions,\n `SELECT TOKEN_ID, LABEL, SCOPES, CREATED_BY, CREATED_AT, EXPIRES_AT, LAST_USED_AT, LAST_USED_IP, REVOKED_AT, REVOKED_BY\n FROM ACCESSI_SERVICE_TOKEN ${includeRevoked ? '' : 'WHERE REVOKED_AT IS NULL'}\n ORDER BY CREATED_AT DESC, TOKEN_ID DESC`,\n [],\n false,\n );\n return (rows.map(RestUtilities.convertKeysToCamelCase) as Array<Record<string, unknown>>).map((row) => this.toMetadata(row));\n }\n\n /** Revoca un token rendendolo immediatamente inutilizzabile. Idempotente su token gia revocati. */\n async revoke(tokenId: string, revokedBy?: number): Promise<void> {\n const parsed = parseServiceToken(tokenId);\n const normalizedId = parsed?.tokenId ?? tokenId;\n const row = await this.readRow(normalizedId);\n if (!row) {\n throw new NotFoundException(`Token di servizio ${normalizedId} non trovato.`);\n }\n if (row.revokedAt !== undefined && row.revokedAt !== null) {\n return;\n }\n await Orm.execute(\n this.databaseOptions,\n 'UPDATE ACCESSI_SERVICE_TOKEN SET REVOKED_AT = CURRENT_TIMESTAMP, REVOKED_BY = ? WHERE TOKEN_ID = ?',\n [revokedBy ?? null, normalizedId],\n );\n }\n\n /** Revoca il token indicato ed emette un sostituto con gli stessi metadati. */\n async rotate(tokenId: string, createdBy?: number): Promise<IssuedServiceToken> {\n const parsed = parseServiceToken(tokenId);\n const normalizedId = parsed?.tokenId ?? tokenId;\n const row = await this.readRow(normalizedId);\n if (!row) {\n throw new NotFoundException(`Token di servizio ${normalizedId} non trovato.`);\n }\n\n const metadata = this.toMetadata(row);\n await this.revoke(normalizedId, createdBy);\n\n return this.issue(\n {\n label: metadata.label,\n scopes: metadata.scopes,\n expiresAt: metadata.expiresAt ?? null,\n },\n createdBy,\n );\n }\n\n /**\n * Verifica un token presentato: formato, esistenza, revoca, scadenza e hash a tempo costante.\n * Aggiorna `LAST_USED_AT` (best-effort, al massimo una volta al minuto).\n */\n async verify(token: string, context?: { ip?: string }): Promise<VerifiedServiceToken | undefined> {\n const parsed = parseServiceToken(token);\n if (!parsed) {\n return undefined;\n }\n\n const row = await this.readRow(parsed.tokenId);\n if (!row) {\n return undefined;\n }\n\n if (row.revokedAt !== undefined && row.revokedAt !== null) {\n return undefined;\n }\n\n const expiresAt = toIso(row.expiresAt);\n if (expiresAt && new Date(expiresAt).getTime() <= Date.now()) {\n return undefined;\n }\n\n const storedHash = typeof row.tokenHash === 'string' ? row.tokenHash : '';\n if (!storedHash || !this.safeHashEquals(hashServiceTokenSecret(parsed.secret), storedHash)) {\n return undefined;\n }\n\n const metadata = this.toMetadata(row);\n await this.touchLastUsed(parsed.tokenId, row.lastUsedAt, context?.ip).catch(() => undefined);\n\n return { tokenId: metadata.tokenId, label: metadata.label, scopes: metadata.scopes };\n }\n\n private safeHashEquals(candidate: string, stored: string): boolean {\n const candidateBuffer = Buffer.from(candidate, 'hex');\n const storedBuffer = Buffer.from(stored, 'hex');\n if (candidateBuffer.length !== storedBuffer.length || candidateBuffer.length === 0) {\n return false;\n }\n return timingSafeEqual(candidateBuffer, storedBuffer);\n }\n\n private async touchLastUsed(tokenId: string, lastUsedAt: unknown, ip?: string): Promise<void> {\n const lastUsed = toIso(lastUsedAt);\n if (lastUsed && Date.now() - new Date(lastUsed).getTime() < LAST_USED_REFRESH_MS) {\n return;\n }\n await Orm.execute(\n this.databaseOptions,\n 'UPDATE ACCESSI_SERVICE_TOKEN SET LAST_USED_AT = CURRENT_TIMESTAMP, LAST_USED_IP = ? WHERE TOKEN_ID = ?',\n [ip ?? null, tokenId],\n false,\n );\n }\n}\n"]}
@@ -46,41 +46,37 @@ let TwoFactorService = class TwoFactorService {
46
46
  return new common_1.HttpException({ code: 'ACCESSI_2FA_RATE_LIMITED', message: 'Attendi prima di richiedere un altro codice.' }, 429);
47
47
  }
48
48
  hash(id, code) {
49
- return (0, crypto_1.createHmac)('sha256', (0, passwordResetToken_1.getAccessiJwtSecret)(this.options)).update(`accessi-2fa:${id}:${code}`).digest('hex');
49
+ return (0, crypto_1.createHmac)('sha256', (0, passwordResetToken_1.getAccessiJwtSecret)(this.options, '2fa')).update(`accessi-2fa:${id}:${code}`).digest('hex');
50
50
  }
51
51
  row(result) {
52
- var _a, _b, _c;
53
- const row = Array.isArray(result) ? result[0] : result;
52
+ var _a, _b, _c, _d, _e, _f;
53
+ const row = (Array.isArray(result) ? result[0] : result);
54
54
  if (!row)
55
55
  return undefined;
56
56
  const raw = Utilities_1.RestUtilities.convertKeysToCamelCase(row);
57
+ const asString = (value) => typeof value === 'string' ? value : undefined;
58
+ const codiceUtente = Number(raw.codute);
59
+ if (!codiceUtente)
60
+ return undefined;
57
61
  const converted = {
58
- codiceUtente: Number(raw.codute), email: (_a = raw.email) === null || _a === void 0 ? void 0 : _a.trim(), mode: (_b = raw.authmode) === null || _b === void 0 ? void 0 : _b.trim(), identityKey: (_c = raw.idnkey) === null || _c === void 0 ? void 0 : _c.trim(),
59
- codeHash: raw.codehash, expiresAt: raw.expiresAt, attempts: Number(raw.attempts), sends: Number(raw.sends), sentAt: raw.sentAt,
62
+ codiceUtente,
63
+ email: (_b = (_a = asString(raw.email)) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '',
64
+ mode: (_d = (_c = asString(raw.authmode)) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : 'password',
65
+ identityKey: (_e = asString(raw.idnkey)) === null || _e === void 0 ? void 0 : _e.trim(),
66
+ codeHash: (_f = asString(raw.codehash)) !== null && _f !== void 0 ? _f : '',
67
+ expiresAt: raw.expiresAt,
68
+ attempts: Number(raw.attempts),
69
+ sends: Number(raw.sends),
70
+ sentAt: raw.sentAt,
60
71
  };
61
- return converted.codiceUtente ? converted : undefined;
72
+ return converted;
62
73
  }
63
74
  transaction(operation) {
64
75
  return __awaiter(this, void 0, void 0, function* () {
65
- const db = yield Orm_1.Orm.connect(this.options.databaseOptions);
66
- let transaction;
67
- try {
68
- transaction = yield Orm_1.Orm.startTransaction(db);
69
- const query = (sql, params = []) => new Promise((resolve, reject) => {
70
- transaction.query(sql, params, (error, result) => error ? reject(error) : resolve(result));
71
- });
72
- const result = yield operation(query);
73
- yield Orm_1.Orm.commitTransaction(transaction);
74
- return result;
75
- }
76
- catch (error) {
77
- if (transaction)
78
- yield Orm_1.Orm.rollbackTransaction(transaction).catch(() => undefined);
79
- throw error;
80
- }
81
- finally {
82
- yield new Promise(resolve => db.detach(() => resolve()));
83
- }
76
+ return Orm_1.Orm.withTransaction(this.options.databaseOptions, (transaction) => __awaiter(this, void 0, void 0, function* () {
77
+ const query = (sql, params = []) => Orm_1.Orm.transactionQuery(transaction, sql, params);
78
+ return operation(query);
79
+ }));
84
80
  });
85
81
  }
86
82
  response(id, expiresAt) {
@@ -115,8 +111,8 @@ let TwoFactorService = class TwoFactorService {
115
111
  throw this.limited();
116
112
  yield query('DELETE FROM ACCESSI_2FA WHERE EXPIRES_AT < DATEADD(-1 DAY TO CURRENT_TIMESTAMP)');
117
113
  const created = yield query(`INSERT INTO ACCESSI_2FA (CHALLENGE_ID, CODUTE, EMAIL, AUTHMODE, IDNKEY, CODEHASH, EXPIRES_AT) VALUES (?, ?, ?, ?, ?, ?, DATEADD(${LIFETIME_SECONDS} SECOND TO CURRENT_TIMESTAMP)) RETURNING EXPIRES_AT`, [id, proof.codiceUtente, proof.email, proof.mode, (_e = proof.identityKey) !== null && _e !== void 0 ? _e : null, this.hash(id, code)]);
118
- const row = Array.isArray(created) ? created[0] : created;
119
- return (_f = row.EXPIRES_AT) !== null && _f !== void 0 ? _f : row.expires_at;
114
+ const row = (Array.isArray(created) ? created[0] : created);
115
+ return ((_f = row === null || row === void 0 ? void 0 : row.EXPIRES_AT) !== null && _f !== void 0 ? _f : row === null || row === void 0 ? void 0 : row.expires_at);
120
116
  }));
121
117
  yield this.deliver(id, code, proof.email);
122
118
  return this.response(id, expiresAt);
@@ -1 +1 @@
1
- {"version":3,"file":"TwoFactorService.js","sourceRoot":"","sources":["../../../../src/accessi-module/Services/TwoFactorService/TwoFactorService.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpException, Inject, Injectable, ServiceUnavailableException, UnauthorizedException } from '@nestjs/common';\nimport { createHmac, randomBytes, randomInt, timingSafeEqual } from 'crypto';\nimport { Orm } from '../../../Orm';\nimport { RestUtilities } from '../../../Utilities';\nimport type { AccessiOptions } from '../../AccessiModule';\nimport { TwoFactorChallengeDto } from '../../Dtos/TwoFactorDtos';\nimport { getAccessiJwtSecret } from '../../security/passwordResetToken';\nimport { EmailService } from '../EmailService/EmailService';\n\nexport type TwoFactorMode = 'password' | 'passwordless' | 'federated';\nexport interface TwoFactorProof {\n codiceUtente: number;\n email: string;\n mode: TwoFactorMode;\n identityKey?: string;\n}\ntype ChallengeRow = TwoFactorProof & {\n codeHash: string;\n expiresAt: Date;\n attempts: number;\n sends: number;\n sentAt: Date;\n};\ntype Query = (sql: string, params?: unknown[]) => Promise<any>;\n\nconst LIFETIME_SECONDS = 600;\nconst MAX_ATTEMPTS = 5;\nconst RESEND_SECONDS = 60;\nconst MAX_SENDS = 3;\nconst RETURN_FIELDS = 'CODUTE, EMAIL, AUTHMODE, IDNKEY, CODEHASH, EXPIRES_AT, ATTEMPTS, SENDS, SENT_AT';\n\n/** Database-backed challenges: no OTP, password or authentication token is logged or stored in clear text. */\n@Injectable()\nexport class TwoFactorService {\n constructor(\n @Inject('ACCESSI_OPTIONS') private readonly options: AccessiOptions,\n private readonly emailService: EmailService,\n ) {}\n\n private invalid(): UnauthorizedException {\n return new UnauthorizedException({ code: 'ACCESSI_2FA_INVALID', message: 'Codice non valido o scaduto. Ripeti l accesso se hai esaurito i tentativi.' });\n }\n\n private limited(): HttpException {\n return new HttpException({ code: 'ACCESSI_2FA_RATE_LIMITED', message: 'Attendi prima di richiedere un altro codice.' }, 429);\n }\n\n private hash(id: string, code: string): string {\n return createHmac('sha256', getAccessiJwtSecret(this.options)).update(`accessi-2fa:${id}:${code}`).digest('hex');\n }\n\n private row(result: any): ChallengeRow | undefined {\n const row = Array.isArray(result) ? result[0] : result;\n if (!row) return undefined;\n const raw = RestUtilities.convertKeysToCamelCase(row);\n const converted: ChallengeRow = {\n codiceUtente: Number(raw.codute), email: raw.email?.trim(), mode: raw.authmode?.trim(), identityKey: raw.idnkey?.trim(),\n codeHash: raw.codehash, expiresAt: raw.expiresAt, attempts: Number(raw.attempts), sends: Number(raw.sends), sentAt: raw.sentAt,\n };\n return converted.codiceUtente ? converted : undefined;\n }\n\n private async transaction<T>(operation: (query: Query) => Promise<T>): Promise<T> {\n const db = await Orm.connect(this.options.databaseOptions);\n let transaction: Awaited<ReturnType<typeof Orm.startTransaction>> | undefined;\n try {\n transaction = await Orm.startTransaction(db);\n const query: Query = (sql, params = []) => new Promise((resolve, reject) => {\n transaction!.query(sql, params, (error, result) => error ? reject(error) : resolve(result));\n });\n const result = await operation(query);\n await Orm.commitTransaction(transaction);\n return result;\n } catch (error) {\n if (transaction) await Orm.rollbackTransaction(transaction).catch(() => undefined);\n throw error;\n } finally {\n await new Promise<void>(resolve => db.detach(() => resolve()));\n }\n }\n\n private response(id: string, expiresAt: Date): TwoFactorChallengeDto {\n return { twoFactorRequired: true, challengeId: id, expiresAt: new Date(expiresAt).toISOString(), resendAfterSeconds: RESEND_SECONDS, method: 'email' };\n }\n\n private async deliver(id: string, code: string, email: string): Promise<void> {\n try {\n await this.emailService.sendTwoFactorCode(email, code, LIFETIME_SECONDS / 60);\n } catch {\n // Invalidate only the code whose delivery failed, preserving a concurrent newer code.\n await Orm.execute(this.options.databaseOptions,\n 'UPDATE ACCESSI_2FA SET ATTEMPTS = ? WHERE CHALLENGE_ID = ? AND CODEHASH = ?',\n [MAX_ATTEMPTS, id, this.hash(id, code)], false).catch(() => undefined);\n throw new ServiceUnavailableException({ code: 'ACCESSI_2FA_DELIVERY_FAILED', message: 'Invio del codice non riuscito. Riprova tra poco.' });\n }\n }\n\n async issue(proof: TwoFactorProof): Promise<TwoFactorChallengeDto> {\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(proof.email ?? '')) {\n throw new ServiceUnavailableException({ code: 'ACCESSI_2FA_EMAIL_REQUIRED', message: 'Per il codice di accesso serve un indirizzo email valido. Contatta un amministratore.' });\n }\n const id = randomBytes(32).toString('hex');\n const code = String(randomInt(1000000)).padStart(6, '0');\n const expiresAt = await this.transaction(async query => {\n // Serialize issuance for this account across backend instances.\n await query('SELECT CODUTE FROM UTENTI WHERE CODUTE = ? WITH LOCK', [proof.codiceUtente]);\n const recent = await query('SELECT COUNT(*) AS TOTAL FROM ACCESSI_2FA WHERE CODUTE = ? AND CREATED_AT > DATEADD(-15 MINUTE TO CURRENT_TIMESTAMP)', [proof.codiceUtente]);\n if (Number(recent?.[0]?.TOTAL ?? recent?.[0]?.total ?? 0) >= 5) throw this.limited();\n await query('DELETE FROM ACCESSI_2FA WHERE EXPIRES_AT < DATEADD(-1 DAY TO CURRENT_TIMESTAMP)');\n const created = await query(\n `INSERT INTO ACCESSI_2FA (CHALLENGE_ID, CODUTE, EMAIL, AUTHMODE, IDNKEY, CODEHASH, EXPIRES_AT) VALUES (?, ?, ?, ?, ?, ?, DATEADD(${LIFETIME_SECONDS} SECOND TO CURRENT_TIMESTAMP)) RETURNING EXPIRES_AT`,\n [id, proof.codiceUtente, proof.email, proof.mode, proof.identityKey ?? null, this.hash(id, code)],\n );\n const row = Array.isArray(created) ? created[0] : created;\n return row.EXPIRES_AT ?? row.expires_at;\n });\n await this.deliver(id, code, proof.email);\n return this.response(id, expiresAt);\n }\n\n async describe(id: string): Promise<TwoFactorProof> {\n if (!/^[a-f0-9]{64}$/.test(id)) throw this.invalid();\n const row = this.row(await Orm.query(this.options.databaseOptions,\n `SELECT ${RETURN_FIELDS} FROM ACCESSI_2FA WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ?`, [id, MAX_ATTEMPTS], false));\n if (!row) throw this.invalid();\n return row;\n }\n\n async resend(id: string): Promise<TwoFactorChallengeDto> {\n if (!/^[a-f0-9]{64}$/.test(id)) throw this.invalid();\n const code = String(randomInt(1000000)).padStart(6, '0');\n const row = await this.transaction(async query => {\n const result = this.row(await query(\n `UPDATE ACCESSI_2FA SET CODEHASH = ?, SENDS = SENDS + 1, SENT_AT = CURRENT_TIMESTAMP WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ? AND SENDS < ? AND SENT_AT <= DATEADD(-${RESEND_SECONDS} SECOND TO CURRENT_TIMESTAMP) RETURNING ${RETURN_FIELDS}`,\n [this.hash(id, code), id, MAX_ATTEMPTS, MAX_SENDS],\n ));\n if (!result) throw this.limited();\n return result;\n });\n await this.deliver(id, code, row.email);\n return this.response(id, row.expiresAt);\n }\n\n async consume(id: string, code: string): Promise<TwoFactorProof> {\n if (!/^[a-f0-9]{64}$/.test(id) || !/^\\d{6}$/.test(code)) throw this.invalid();\n const proof = await this.transaction(async query => {\n // The conditional write serializes verification and persists failed attempts.\n const row = this.row(await query(\n `UPDATE ACCESSI_2FA SET ATTEMPTS = ATTEMPTS + 1 WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ? RETURNING ${RETURN_FIELDS}`,\n [id, MAX_ATTEMPTS],\n ));\n if (!row) return null;\n const expected = Buffer.from(row.codeHash.trim(), 'hex');\n const actual = Buffer.from(this.hash(id, code), 'hex');\n if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return null;\n await query('UPDATE ACCESSI_2FA SET ATTEMPTS = ? WHERE CHALLENGE_ID = ?', [MAX_ATTEMPTS, id]);\n return row;\n });\n // Throw after commit, otherwise a wrong code would roll back the attempt counter.\n if (!proof) throw this.invalid();\n return proof;\n }\n}\n"]}
1
+ {"version":3,"file":"TwoFactorService.js","sourceRoot":"","sources":["../../../../src/accessi-module/Services/TwoFactorService/TwoFactorService.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAuH;AACvH,mCAA6E;AAC7E,sCAAmC;AACnC,kDAAmD;AAGnD,0EAAwE;AACxE,+DAA4D;AAkB5D,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,cAAc,GAAG,EAAE,CAAC;AAC1B,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,MAAM,aAAa,GAAG,iFAAiF,CAAC;AAExG,8GAA8G;AAEvG,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAC3B,YAC8C,OAAuB,EAClD,YAA0B;QADC,YAAO,GAAP,OAAO,CAAgB;QAClD,iBAAY,GAAZ,YAAY,CAAc;IAC1C,CAAC;IAEI,OAAO;QACb,OAAO,IAAI,8BAAqB,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,OAAO,EAAE,4EAA4E,EAAE,CAAC,CAAC;IAC3J,CAAC;IAEO,OAAO;QACb,OAAO,IAAI,sBAAa,CAAC,EAAE,IAAI,EAAE,0BAA0B,EAAE,OAAO,EAAE,8CAA8C,EAAE,EAAE,GAAG,CAAC,CAAC;IAC/H,CAAC;IAEO,IAAI,CAAC,EAAU,EAAE,IAAY;QACnC,OAAO,IAAA,mBAAU,EAAC,QAAQ,EAAE,IAAA,wCAAmB,EAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1H,CAAC;IAEO,GAAG,CAAC,MAAe;;QACzB,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAwC,CAAC;QAChG,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAE3B,MAAM,GAAG,GAAG,yBAAa,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAsB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACvG,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,CAAC,YAAY;YAAE,OAAO,SAAS,CAAC;QAEpC,MAAM,SAAS,GAAiB;YAC9B,YAAY;YACZ,KAAK,EAAE,MAAA,MAAA,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,0CAAE,IAAI,EAAE,mCAAI,EAAE;YACxC,IAAI,EAAE,MAAC,MAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,0CAAE,IAAI,EAAgC,mCAAI,UAAU;YACjF,WAAW,EAAE,MAAA,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,0CAAE,IAAI,EAAE;YACzC,QAAQ,EAAE,MAAA,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,mCAAI,EAAE;YACtC,SAAS,EAAE,GAAG,CAAC,SAAiB;YAChC,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;YACxB,MAAM,EAAE,GAAG,CAAC,MAAc;SAC3B,CAAC;QACF,OAAO,SAAS,CAAC;IACnB,CAAC;IAEa,WAAW,CAAI,SAAuC;;YAClE,OAAO,SAAG,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAO,WAAW,EAAE,EAAE;gBAC7E,MAAM,KAAK,GAAU,CAAC,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE,CAAC,SAAG,CAAC,gBAAgB,CAAC,WAAW,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC1F,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC,CAAA,CAAC,CAAC;QACL,CAAC;KAAA;IAEO,QAAQ,CAAC,EAAU,EAAE,SAAe;QAC1C,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACzJ,CAAC;IAEa,OAAO,CAAC,EAAU,EAAE,IAAY,EAAE,KAAa;;YAC3D,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,gBAAgB,GAAG,EAAE,CAAC,CAAC;YAChF,CAAC;YAAC,WAAM,CAAC;gBACP,sFAAsF;gBACtF,MAAM,SAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAC5C,6EAA6E,EAC7E,CAAC,YAAY,EAAE,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBACzE,MAAM,IAAI,oCAA2B,CAAC,EAAE,IAAI,EAAE,6BAA6B,EAAE,OAAO,EAAE,kDAAkD,EAAE,CAAC,CAAC;YAC9I,CAAC;QACH,CAAC;KAAA;IAEK,KAAK,CAAC,KAAqB;;;YAC/B,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,MAAA,KAAK,CAAC,KAAK,mCAAI,EAAE,CAAC,EAAE,CAAC;gBAC1D,MAAM,IAAI,oCAA2B,CAAC,EAAE,IAAI,EAAE,4BAA4B,EAAE,OAAO,EAAE,uFAAuF,EAAE,CAAC,CAAC;YAClL,CAAC;YACD,MAAM,EAAE,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAA,kBAAS,EAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACzD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAM,KAAK,EAAC,EAAE;;gBACrD,gEAAgE;gBAChE,MAAM,KAAK,CAAC,sDAAsD,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC;gBAC1F,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,sHAAsH,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAA+C,CAAC;gBACvN,IAAI,MAAM,CAAC,MAAA,MAAA,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,0CAAE,KAAK,mCAAI,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,0CAAE,KAAK,mCAAI,CAAC,CAAC,IAAI,CAAC;oBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;gBACrF,MAAM,KAAK,CAAC,iFAAiF,CAAC,CAAC;gBAC/F,MAAM,OAAO,GAAG,MAAM,KAAK,CACzB,mIAAmI,gBAAgB,qDAAqD,EACxM,CAAC,EAAE,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,MAAA,KAAK,CAAC,WAAW,mCAAI,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAClG,CAAC;gBACF,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAwC,CAAC;gBACnG,OAAO,CAAC,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,UAAU,mCAAI,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,UAAU,CAAS,CAAC;YACtD,CAAC,CAAA,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;KAAA;IAEK,QAAQ,CAAC,EAAU;;YACvB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,SAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,EAC/D,UAAU,aAAa,8FAA8F,EAAE,CAAC,EAAE,EAAE,YAAY,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;YACrJ,IAAI,CAAC,GAAG;gBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,OAAO,GAAG,CAAC;QACb,CAAC;KAAA;IAEK,MAAM,CAAC,EAAU;;YACrB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAA,kBAAS,EAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YACzD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAM,KAAK,EAAC,EAAE;gBAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CACjC,wMAAwM,cAAc,2CAA2C,aAAa,EAAE,EAChR,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,YAAY,EAAE,SAAS,CAAC,CACnD,CAAC,CAAC;gBACH,IAAI,CAAC,MAAM;oBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,OAAO,MAAM,CAAC;YAChB,CAAC,CAAA,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;KAAA;IAEK,OAAO,CAAC,EAAU,EAAE,IAAY;;YACpC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAC9E,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,CAAM,KAAK,EAAC,EAAE;gBACjD,8EAA8E;gBAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAC9B,uIAAuI,aAAa,EAAE,EACtJ,CAAC,EAAE,EAAE,YAAY,CAAC,CACnB,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG;oBAAE,OAAO,IAAI,CAAC;gBACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,KAAK,CAAC,CAAC;gBACzD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;gBACvD,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,CAAC,IAAA,wBAAe,EAAC,QAAQ,EAAE,MAAM,CAAC;oBAAE,OAAO,IAAI,CAAC;gBACzF,MAAM,KAAK,CAAC,4DAA4D,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9F,OAAO,GAAG,CAAC;YACb,CAAC,CAAA,CAAC,CAAC;YACH,kFAAkF;YAClF,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,OAAO,KAAK,CAAC;QACf,CAAC;KAAA;CACF,CAAA;AAjIY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,iBAAiB,CAAC,CAAA;6CACK,2BAAY;GAHlC,gBAAgB,CAiI5B","sourcesContent":["import { HttpException, Inject, Injectable, ServiceUnavailableException, UnauthorizedException } from '@nestjs/common';\nimport { createHmac, randomBytes, randomInt, timingSafeEqual } from 'crypto';\nimport { Orm } from '../../../Orm';\nimport { RestUtilities } from '../../../Utilities';\nimport type { AccessiOptions } from '../../AccessiModule';\nimport { TwoFactorChallengeDto } from '../../Dtos/TwoFactorDtos';\nimport { getAccessiJwtSecret } from '../../security/passwordResetToken';\nimport { EmailService } from '../EmailService/EmailService';\n\nexport type TwoFactorMode = 'password' | 'passwordless' | 'federated';\nexport interface TwoFactorProof {\n codiceUtente: number;\n email: string;\n mode: TwoFactorMode;\n identityKey?: string;\n}\ntype ChallengeRow = TwoFactorProof & {\n codeHash: string;\n expiresAt: Date;\n attempts: number;\n sends: number;\n sentAt: Date;\n};\ntype Query = (sql: string, params?: unknown[]) => Promise<unknown>;\n\nconst LIFETIME_SECONDS = 600;\nconst MAX_ATTEMPTS = 5;\nconst RESEND_SECONDS = 60;\nconst MAX_SENDS = 3;\nconst RETURN_FIELDS = 'CODUTE, EMAIL, AUTHMODE, IDNKEY, CODEHASH, EXPIRES_AT, ATTEMPTS, SENDS, SENT_AT';\n\n/** Database-backed challenges: no OTP, password or authentication token is logged or stored in clear text. */\n@Injectable()\nexport class TwoFactorService {\n constructor(\n @Inject('ACCESSI_OPTIONS') private readonly options: AccessiOptions,\n private readonly emailService: EmailService,\n ) {}\n\n private invalid(): UnauthorizedException {\n return new UnauthorizedException({ code: 'ACCESSI_2FA_INVALID', message: 'Codice non valido o scaduto. Ripeti l accesso se hai esaurito i tentativi.' });\n }\n\n private limited(): HttpException {\n return new HttpException({ code: 'ACCESSI_2FA_RATE_LIMITED', message: 'Attendi prima di richiedere un altro codice.' }, 429);\n }\n\n private hash(id: string, code: string): string {\n return createHmac('sha256', getAccessiJwtSecret(this.options, '2fa')).update(`accessi-2fa:${id}:${code}`).digest('hex');\n }\n\n private row(result: unknown): ChallengeRow | undefined {\n const row = (Array.isArray(result) ? result[0] : result) as Record<string, unknown> | undefined;\n if (!row) return undefined;\n\n const raw = RestUtilities.convertKeysToCamelCase(row);\n const asString = (value: unknown): string | undefined => typeof value === 'string' ? value : undefined;\n const codiceUtente = Number(raw.codute);\n if (!codiceUtente) return undefined;\n\n const converted: ChallengeRow = {\n codiceUtente,\n email: asString(raw.email)?.trim() ?? '',\n mode: (asString(raw.authmode)?.trim() as TwoFactorMode | undefined) ?? 'password',\n identityKey: asString(raw.idnkey)?.trim(),\n codeHash: asString(raw.codehash) ?? '',\n expiresAt: raw.expiresAt as Date,\n attempts: Number(raw.attempts),\n sends: Number(raw.sends),\n sentAt: raw.sentAt as Date,\n };\n return converted;\n }\n\n private async transaction<T>(operation: (query: Query) => Promise<T>): Promise<T> {\n return Orm.withTransaction(this.options.databaseOptions, async (transaction) => {\n const query: Query = (sql, params = []) => Orm.transactionQuery(transaction, sql, params);\n return operation(query);\n });\n }\n\n private response(id: string, expiresAt: Date): TwoFactorChallengeDto {\n return { twoFactorRequired: true, challengeId: id, expiresAt: new Date(expiresAt).toISOString(), resendAfterSeconds: RESEND_SECONDS, method: 'email' };\n }\n\n private async deliver(id: string, code: string, email: string): Promise<void> {\n try {\n await this.emailService.sendTwoFactorCode(email, code, LIFETIME_SECONDS / 60);\n } catch {\n // Invalidate only the code whose delivery failed, preserving a concurrent newer code.\n await Orm.execute(this.options.databaseOptions,\n 'UPDATE ACCESSI_2FA SET ATTEMPTS = ? WHERE CHALLENGE_ID = ? AND CODEHASH = ?',\n [MAX_ATTEMPTS, id, this.hash(id, code)], false).catch(() => undefined);\n throw new ServiceUnavailableException({ code: 'ACCESSI_2FA_DELIVERY_FAILED', message: 'Invio del codice non riuscito. Riprova tra poco.' });\n }\n }\n\n async issue(proof: TwoFactorProof): Promise<TwoFactorChallengeDto> {\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(proof.email ?? '')) {\n throw new ServiceUnavailableException({ code: 'ACCESSI_2FA_EMAIL_REQUIRED', message: 'Per il codice di accesso serve un indirizzo email valido. Contatta un amministratore.' });\n }\n const id = randomBytes(32).toString('hex');\n const code = String(randomInt(1000000)).padStart(6, '0');\n const expiresAt = await this.transaction(async query => {\n // Serialize issuance for this account across backend instances.\n await query('SELECT CODUTE FROM UTENTI WHERE CODUTE = ? WITH LOCK', [proof.codiceUtente]);\n const recent = await query('SELECT COUNT(*) AS TOTAL FROM ACCESSI_2FA WHERE CODUTE = ? AND CREATED_AT > DATEADD(-15 MINUTE TO CURRENT_TIMESTAMP)', [proof.codiceUtente]) as Array<Record<string, unknown>> | undefined;\n if (Number(recent?.[0]?.TOTAL ?? recent?.[0]?.total ?? 0) >= 5) throw this.limited();\n await query('DELETE FROM ACCESSI_2FA WHERE EXPIRES_AT < DATEADD(-1 DAY TO CURRENT_TIMESTAMP)');\n const created = await query(\n `INSERT INTO ACCESSI_2FA (CHALLENGE_ID, CODUTE, EMAIL, AUTHMODE, IDNKEY, CODEHASH, EXPIRES_AT) VALUES (?, ?, ?, ?, ?, ?, DATEADD(${LIFETIME_SECONDS} SECOND TO CURRENT_TIMESTAMP)) RETURNING EXPIRES_AT`,\n [id, proof.codiceUtente, proof.email, proof.mode, proof.identityKey ?? null, this.hash(id, code)],\n );\n const row = (Array.isArray(created) ? created[0] : created) as Record<string, unknown> | undefined;\n return (row?.EXPIRES_AT ?? row?.expires_at) as Date;\n });\n await this.deliver(id, code, proof.email);\n return this.response(id, expiresAt);\n }\n\n async describe(id: string): Promise<TwoFactorProof> {\n if (!/^[a-f0-9]{64}$/.test(id)) throw this.invalid();\n const row = this.row(await Orm.query(this.options.databaseOptions,\n `SELECT ${RETURN_FIELDS} FROM ACCESSI_2FA WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ?`, [id, MAX_ATTEMPTS], false));\n if (!row) throw this.invalid();\n return row;\n }\n\n async resend(id: string): Promise<TwoFactorChallengeDto> {\n if (!/^[a-f0-9]{64}$/.test(id)) throw this.invalid();\n const code = String(randomInt(1000000)).padStart(6, '0');\n const row = await this.transaction(async query => {\n const result = this.row(await query(\n `UPDATE ACCESSI_2FA SET CODEHASH = ?, SENDS = SENDS + 1, SENT_AT = CURRENT_TIMESTAMP WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ? AND SENDS < ? AND SENT_AT <= DATEADD(-${RESEND_SECONDS} SECOND TO CURRENT_TIMESTAMP) RETURNING ${RETURN_FIELDS}`,\n [this.hash(id, code), id, MAX_ATTEMPTS, MAX_SENDS],\n ));\n if (!result) throw this.limited();\n return result;\n });\n await this.deliver(id, code, row.email);\n return this.response(id, row.expiresAt);\n }\n\n async consume(id: string, code: string): Promise<TwoFactorProof> {\n if (!/^[a-f0-9]{64}$/.test(id) || !/^\\d{6}$/.test(code)) throw this.invalid();\n const proof = await this.transaction(async query => {\n // The conditional write serializes verification and persists failed attempts.\n const row = this.row(await query(\n `UPDATE ACCESSI_2FA SET ATTEMPTS = ATTEMPTS + 1 WHERE CHALLENGE_ID = ? AND EXPIRES_AT > CURRENT_TIMESTAMP AND ATTEMPTS < ? RETURNING ${RETURN_FIELDS}`,\n [id, MAX_ATTEMPTS],\n ));\n if (!row) return null;\n const expected = Buffer.from(row.codeHash.trim(), 'hex');\n const actual = Buffer.from(this.hash(id, code), 'hex');\n if (expected.length !== actual.length || !timingSafeEqual(expected, actual)) return null;\n await query('UPDATE ACCESSI_2FA SET ATTEMPTS = ? WHERE CHALLENGE_ID = ?', [MAX_ATTEMPTS, id]);\n return row;\n });\n // Throw after commit, otherwise a wrong code would roll back the attempt counter.\n if (!proof) throw this.invalid();\n return proof;\n }\n}\n"]}
@@ -9,10 +9,9 @@ import { FiltriService } from '../FiltriService/FiltriService';
9
9
  import { PermissionService } from '../PermissionService/PermissionService';
10
10
  export declare class UserService {
11
11
  private readonly accessiOptions;
12
- private readonly emailService;
13
12
  private readonly permissionService;
14
13
  private readonly filtriService;
15
- constructor(accessiOptions: AccessiOptions, emailService: EmailService, permissionService: PermissionService, filtriService: FiltriService);
14
+ constructor(accessiOptions: AccessiOptions, _emailService: EmailService, permissionService: PermissionService, filtriService: FiltriService);
16
15
  private validateApplicationFields;
17
16
  private normalizeDatabaseBoolean;
18
17
  private normalizeUserFlags;
@@ -32,10 +31,19 @@ export declare class UserService {
32
31
  getUsers(filters?: {
33
32
  email?: string;
34
33
  codiceUtente?: number;
34
+ limit?: number;
35
+ offset?: number;
35
36
  }, options?: {
36
37
  includeExtensionFields: boolean;
37
38
  includeGrants: boolean;
38
39
  }): Promise<GetUsersResult[]>;
40
+ /** Conteggio utenti con gli stessi filtri di `getUsers`, per la paginazione (header X-Total-Count). */
41
+ countUsers(filters?: {
42
+ email?: string;
43
+ codiceUtente?: number;
44
+ }): Promise<number>;
45
+ private loadExtensionFieldsForUsers;
46
+ private chunkArray;
39
47
  /** Cerca il codice interno per email normalizzata. Il chiamante deve gestire il caso senza righe. */
40
48
  getCodiceUtenteByEmail(email: string): Promise<{
41
49
  codiceUtente: number;
@@ -43,7 +51,6 @@ export declare class UserService {
43
51
  /** Recupera il profilo locale usato dal login, compresi filtri Accessi eventualmente presenti. */
44
52
  getUserByEmail(email: string): Promise<UserDto | null>;
45
53
  insertUserFilters(codiceUtente: number, filterData: RegisterRequest): Promise<void>;
46
- private executeInTransaction;
47
54
  /**
48
55
  * Crea utente, configurazione e filtri. La registrazione pubblica resta in stato `INVIO` e non puo
49
56
  * impostare privilegi, ruoli o grant; soltanto flussi backend fidati possono usare `allowPrivilegedFields`.
@@ -20,6 +20,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
20
20
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21
21
  });
22
22
  };
23
+ var __rest = (this && this.__rest) || function (s, e) {
24
+ var t = {};
25
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
26
+ t[p] = s[p];
27
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
28
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
29
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
30
+ t[p[i]] = s[p[i]];
31
+ }
32
+ return t;
33
+ };
23
34
  Object.defineProperty(exports, "__esModule", { value: true });
24
35
  exports.UserService = void 0;
25
36
  const common_1 = require("@nestjs/common");
@@ -33,9 +44,8 @@ const EmailService_1 = require("../EmailService/EmailService");
33
44
  const FiltriService_1 = require("../FiltriService/FiltriService");
34
45
  const PermissionService_1 = require("../PermissionService/PermissionService");
35
46
  let UserService = class UserService {
36
- constructor(accessiOptions, emailService, permissionService, filtriService) {
47
+ constructor(accessiOptions, _emailService, permissionService, filtriService) {
37
48
  this.accessiOptions = accessiOptions;
38
- this.emailService = emailService;
39
49
  this.permissionService = permissionService;
40
50
  this.filtriService = filtriService;
41
51
  }
@@ -89,7 +99,7 @@ let UserService = class UserService {
89
99
  }
90
100
  const query = `SELECT FLGADMINCONFIG AS flag_admin_configurator FROM UTENTI_CONFIG WHERE CODUTE = ?`;
91
101
  const result = yield Orm_1.Orm.query(this.accessiOptions.databaseOptions, query, [codiceUtente]);
92
- if (!result || result === 0) {
102
+ if (result.length === 0) {
93
103
  return false;
94
104
  }
95
105
  const mapped = result.map(Utilities_1.RestUtilities.convertKeysToCamelCase);
@@ -141,11 +151,20 @@ let UserService = class UserService {
141
151
  */
142
152
  getUsers(filters, options) {
143
153
  return __awaiter(this, void 0, void 0, function* () {
154
+ var _a, _b, _c, _d, _e;
144
155
  try {
145
156
  const configColumns = yield (0, optionalColumns_1.getTableColumns)(this.accessiOptions, 'UTENTI_CONFIG');
146
157
  const filterColumns = yield (0, optionalColumns_1.getTableColumns)(this.accessiOptions, 'FILTRI');
158
+ // Paginazione opzionale con limiti sani: il default resta "tutti gli utenti".
159
+ const limit = Number.isInteger(filters === null || filters === void 0 ? void 0 : filters.limit) && ((_a = filters === null || filters === void 0 ? void 0 : filters.limit) !== null && _a !== void 0 ? _a : 0) > 0
160
+ ? Math.min(filters === null || filters === void 0 ? void 0 : filters.limit, 1000)
161
+ : undefined;
162
+ const offset = Number.isInteger(filters === null || filters === void 0 ? void 0 : filters.offset) && ((_b = filters === null || filters === void 0 ? void 0 : filters.offset) !== null && _b !== void 0 ? _b : 0) > 0
163
+ ? Math.trunc(filters === null || filters === void 0 ? void 0 : filters.offset)
164
+ : 0;
165
+ const pagination = `${limit !== undefined ? ` FIRST ${limit}` : ''}${offset > 0 ? ` SKIP ${offset}` : ''}`;
147
166
  let query = `
148
- SELECT
167
+ SELECT${pagination}
149
168
  U.CODUTE as codice_utente,
150
169
  U.USRNAME as email,
151
170
  U.FLGGDPR as flag_gdpr,
@@ -194,19 +213,25 @@ let UserService = class UserService {
194
213
  let users = (yield Orm_1.Orm.query(this.accessiOptions.databaseOptions, query, queryParams));
195
214
  users = users.map(Utilities_1.RestUtilities.convertKeysToCamelCase).map(user => this.normalizeUserFlags(user));
196
215
  const usersResponse = [];
216
+ // I campi estensione sono caricati una volta per tabella (non una per utente) in query IN
217
+ // suddivise in blocchi per non superare i limiti di parametri del driver.
218
+ const extensionFieldsByUser = (options === null || options === void 0 ? void 0 : options.includeExtensionFields)
219
+ ? yield this.loadExtensionFieldsForUsers(users.map((user) => user.codiceUtente))
220
+ : undefined;
221
+ // I grant sono caricati in batch (query IN) invece che utente per utente.
222
+ const grantsByUser = (options === null || options === void 0 ? void 0 : options.includeGrants)
223
+ ? yield this.permissionService.getUsersRolesAndGrants(users.map((user) => user.codiceUtente))
224
+ : undefined;
197
225
  for (const user of users) {
198
226
  let userGrants;
199
227
  if (options === null || options === void 0 ? void 0 : options.includeGrants) {
200
- userGrants = yield this.permissionService.getUserRolesAndGrants(user.codiceUtente);
228
+ userGrants = grantsByUser === null || grantsByUser === void 0 ? void 0 : grantsByUser.get(user.codiceUtente);
201
229
  }
202
230
  let extensionFields;
203
231
  if (options === null || options === void 0 ? void 0 : options.includeExtensionFields) {
204
232
  extensionFields = {};
205
- }
206
- if (extensionFields && this.accessiOptions.extensionFieldsOptions) {
207
- for (const ext of this.accessiOptions.extensionFieldsOptions) {
208
- const values = (yield Orm_1.Orm.query(ext.databaseOptions, `SELECT ${ext.tableFields.join(',')} FROM ${ext.tableName} WHERE ${ext.tableJoinFieldName} = ?`, [user.codiceUtente])).map(Utilities_1.RestUtilities.convertKeysToCamelCase);
209
- extensionFields[ext.objectKey] = values;
233
+ for (const ext of (_c = this.accessiOptions.extensionFieldsOptions) !== null && _c !== void 0 ? _c : []) {
234
+ extensionFields[ext.objectKey] = (_e = (_d = extensionFieldsByUser === null || extensionFieldsByUser === void 0 ? void 0 : extensionFieldsByUser.get(ext.objectKey)) === null || _d === void 0 ? void 0 : _d.get(user.codiceUtente)) !== null && _e !== void 0 ? _e : [];
210
235
  }
211
236
  }
212
237
  usersResponse.push({
@@ -222,6 +247,67 @@ let UserService = class UserService {
222
247
  }
223
248
  });
224
249
  }
250
+ /** Conteggio utenti con gli stessi filtri di `getUsers`, per la paginazione (header X-Total-Count). */
251
+ countUsers(filters) {
252
+ return __awaiter(this, void 0, void 0, function* () {
253
+ var _a, _b;
254
+ const conditions = [];
255
+ const params = [];
256
+ if (filters === null || filters === void 0 ? void 0 : filters.email) {
257
+ conditions.push('LOWER(U.USRNAME) = ?');
258
+ params.push(filters.email.trim().toLowerCase());
259
+ }
260
+ if (filters === null || filters === void 0 ? void 0 : filters.codiceUtente) {
261
+ conditions.push('U.CODUTE = ?');
262
+ params.push(filters.codiceUtente);
263
+ }
264
+ const query = `SELECT COUNT(*) AS TOTAL FROM UTENTI U INNER JOIN UTENTI_CONFIG G ON U.CODUTE = G.CODUTE WHERE 1=1 ${conditions.map((condition) => `AND ${condition}`).join(' ')}`;
265
+ const rows = (yield Orm_1.Orm.query(this.accessiOptions.databaseOptions, query, params, false));
266
+ const row = rows[0];
267
+ return Number((_b = (_a = row === null || row === void 0 ? void 0 : row.TOTAL) !== null && _a !== void 0 ? _a : row === null || row === void 0 ? void 0 : row.total) !== null && _b !== void 0 ? _b : 0) || 0;
268
+ });
269
+ }
270
+ loadExtensionFieldsForUsers(codiceUtenti) {
271
+ return __awaiter(this, void 0, void 0, function* () {
272
+ var _a;
273
+ const byObjectKey = new Map();
274
+ const extensions = (_a = this.accessiOptions.extensionFieldsOptions) !== null && _a !== void 0 ? _a : [];
275
+ const uniqueCodes = Array.from(new Set(codiceUtenti));
276
+ for (const ext of extensions) {
277
+ const grouped = new Map();
278
+ for (const chunk of this.chunkArray(uniqueCodes, 500)) {
279
+ if (chunk.length === 0) {
280
+ continue;
281
+ }
282
+ const placeholders = chunk.map(() => '?').join(', ');
283
+ const rows = (yield Orm_1.Orm.query(ext.databaseOptions, `SELECT ${ext.tableJoinFieldName} AS __joinkey, ${ext.tableFields.join(', ')} FROM ${ext.tableName} WHERE ${ext.tableJoinFieldName} IN (${placeholders})`, chunk)).map(Utilities_1.RestUtilities.convertKeysToCamelCase);
284
+ for (const row of rows) {
285
+ const { __joinkey } = row, rest = __rest(row, ["__joinkey"]);
286
+ const key = Number(__joinkey);
287
+ if (!Number.isFinite(key)) {
288
+ continue;
289
+ }
290
+ const bucket = grouped.get(key);
291
+ if (bucket) {
292
+ bucket.push(rest);
293
+ }
294
+ else {
295
+ grouped.set(key, [rest]);
296
+ }
297
+ }
298
+ }
299
+ byObjectKey.set(ext.objectKey, grouped);
300
+ }
301
+ return byObjectKey;
302
+ });
303
+ }
304
+ chunkArray(items, size) {
305
+ const chunks = [];
306
+ for (let index = 0; index < items.length; index += size) {
307
+ chunks.push(items.slice(index, index + size));
308
+ }
309
+ return chunks;
310
+ }
225
311
  /** Cerca il codice interno per email normalizzata. Il chiamante deve gestire il caso senza righe. */
226
312
  getCodiceUtenteByEmail(email) {
227
313
  return __awaiter(this, void 0, void 0, function* () {
@@ -266,12 +352,11 @@ let UserService = class UserService {
266
352
  WHERE LOWER(U.USRNAME) = ?
267
353
  `;
268
354
  const utenti = (yield Orm_1.Orm.query(this.accessiOptions.databaseOptions, query, [email]).then((results) => results.map(Utilities_1.RestUtilities.convertKeysToCamelCase)));
269
- if (utenti.length <= 0)
355
+ const user = utenti[0];
356
+ if (!user)
270
357
  return null;
271
- const filtriUtente = yield this.filtriService.getFiltriUser(utenti[0].codiceUtente);
272
- if (utenti.length > 0 && filtriUtente.length > 0) {
273
- const user = utenti[0];
274
- const filtro = filtriUtente[0];
358
+ const filtro = (yield this.filtriService.getFiltriUser(user.codiceUtente))[0];
359
+ if (filtro) {
275
360
  const filterValues = {};
276
361
  Object.entries(FiltriUtente_1.FILTRI_UTENTE_DB_MAPPING).forEach(([key]) => {
277
362
  if (key in filtro) {
@@ -280,7 +365,7 @@ let UserService = class UserService {
280
365
  });
281
366
  Object.assign(user, filterValues);
282
367
  }
283
- return utenti.length > 0 ? this.normalizeUserFlags(utenti[0]) : null;
368
+ return this.normalizeUserFlags(user);
284
369
  });
285
370
  }
286
371
  insertUserFilters(codiceUtente, filterData) {
@@ -288,18 +373,13 @@ let UserService = class UserService {
288
373
  yield this.filtriService.upsertFiltriUtente(codiceUtente, filterData);
289
374
  });
290
375
  }
291
- executeInTransaction(operation) {
292
- return __awaiter(this, void 0, void 0, function* () {
293
- yield operation();
294
- });
295
- }
296
376
  /**
297
377
  * Crea utente, configurazione e filtri. La registrazione pubblica resta in stato `INVIO` e non puo
298
378
  * impostare privilegi, ruoli o grant; soltanto flussi backend fidati possono usare `allowPrivilegedFields`.
299
379
  */
300
380
  register(registrationData, options) {
301
381
  return __awaiter(this, void 0, void 0, function* () {
302
- var _a, _b, _c, _d;
382
+ var _a;
303
383
  try {
304
384
  const allowPrivilegedFields = (options === null || options === void 0 ? void 0 : options.allowPrivilegedFields) === true;
305
385
  const normalizedEmail = this.normalizeEmail(registrationData.email);
@@ -319,71 +399,77 @@ let UserService = class UserService {
319
399
  }
320
400
  yield this.validateApplicationFields(registrationData);
321
401
  yield this.ensureEmailIsAvailable(normalizedEmail);
322
- const queryUtenti = `INSERT INTO UTENTI (USRNAME, STAREG) VALUES (?,?)`;
402
+ const queryUtenti = `INSERT INTO UTENTI (USRNAME, STAREG) VALUES (?,?) RETURNING CODUTE`;
323
403
  // Public/local registrations keep the historic INVIO state; trusted master or SSO flows may opt into CONF.
324
404
  const paramsUtenti = [normalizedEmail, (_a = options === null || options === void 0 ? void 0 : options.initialState) !== null && _a !== void 0 ? _a : StatoRegistrazione_1.StatoRegistrazione.INVIO];
325
- yield Orm_1.Orm.execute(this.accessiOptions.databaseOptions, queryUtenti, paramsUtenti);
326
- const codiceUtenteResult = yield Orm_1.Orm.query(this.accessiOptions.databaseOptions, 'SELECT FIRST 1 CODUTE FROM UTENTI WHERE USRNAME = ? ORDER BY CODUTE DESC', [normalizedEmail]);
327
- const codiceUtente = Number((_c = (_b = codiceUtenteResult === null || codiceUtenteResult === void 0 ? void 0 : codiceUtenteResult[0]) === null || _b === void 0 ? void 0 : _b.CODUTE) !== null && _c !== void 0 ? _c : (_d = codiceUtenteResult === null || codiceUtenteResult === void 0 ? void 0 : codiceUtenteResult[0]) === null || _d === void 0 ? void 0 : _d.codute);
328
- if (!codiceUtente) {
329
- throw new Error('Creazione utente non riuscita: impossibile recuperare CODUTE.');
330
- }
331
- const utentiConfigFields = ['CODUTE', 'COGNOME', 'NOME'];
332
- const utentiConfigPlaceholders = ['?', '?', '?'];
333
- const utentiConfigParams = [codiceUtente, registrationData.cognome, registrationData.nome];
334
- const optionalFields = [
335
- {
336
- key: 'cellulare',
337
- dbField: 'CELLULARE',
338
- transform: (v) => String(v),
339
- },
340
- {
341
- key: 'avatar',
342
- dbField: 'AVATAR',
343
- transform: (v) => String(v),
344
- },
345
- {
346
- key: 'flagDueFattori',
347
- dbField: 'FLG2FATT',
348
- transform: (v) => (v ? 1 : 0),
349
- },
350
- {
351
- key: 'paginaDefault',
352
- dbField: 'PAGDEF',
353
- transform: (v) => String(v),
354
- },
355
- {
356
- key: 'nummac',
357
- dbField: 'NUMMAC',
358
- transform: (v) => Number(v),
359
- },
360
- {
361
- key: 'ragSocCli',
362
- dbField: 'RAGSOCCLI',
363
- transform: (v) => String(v),
364
- },
365
- ];
366
- if (allowPrivilegedFields) {
367
- optionalFields.push({
368
- key: 'flagSuper',
369
- dbField: 'FLGSUPER',
370
- transform: (v) => (v ? 1 : 0),
371
- }, {
372
- key: 'flagAdminConfigurator',
373
- dbField: 'FLGADMINCONFIG',
374
- transform: (v) => (v ? 1 : 0),
375
- });
376
- }
377
- for (const field of optionalFields) {
378
- const value = registrationData[field.key];
379
- if (value !== undefined && value !== null) {
380
- utentiConfigFields.push(field.dbField);
381
- utentiConfigPlaceholders.push('?');
382
- utentiConfigParams.push(field.transform ? field.transform(value) : value);
405
+ // Utente e configurazione sono creati in un'unica transazione; filtri e assegnazioni restano
406
+ // fuori perché delegati a servizi dedicati.
407
+ const codiceUtente = yield Orm_1.Orm.withTransaction(this.accessiOptions.databaseOptions, (transaction) => __awaiter(this, void 0, void 0, function* () {
408
+ var _a;
409
+ const insertedUsers = yield Orm_1.Orm.transactionQuery(transaction, queryUtenti, paramsUtenti);
410
+ const insertedUser = (Array.isArray(insertedUsers) ? insertedUsers[0] : insertedUsers);
411
+ const newCodiceUtente = Number((_a = insertedUser === null || insertedUser === void 0 ? void 0 : insertedUser.CODUTE) !== null && _a !== void 0 ? _a : insertedUser === null || insertedUser === void 0 ? void 0 : insertedUser.codute);
412
+ if (!newCodiceUtente) {
413
+ throw new Error('Creazione utente non riuscita: impossibile recuperare CODUTE.');
383
414
  }
384
- }
385
- const queryUtentiConfig = `INSERT INTO UTENTI_CONFIG (${utentiConfigFields.join(', ')}) VALUES (${utentiConfigPlaceholders.join(', ')})`;
386
- yield Orm_1.Orm.execute(this.accessiOptions.databaseOptions, queryUtentiConfig, utentiConfigParams);
415
+ const utentiConfigFields = ['CODUTE', 'COGNOME', 'NOME'];
416
+ const utentiConfigPlaceholders = ['?', '?', '?'];
417
+ const utentiConfigParams = [newCodiceUtente, registrationData.cognome, registrationData.nome];
418
+ const optionalFields = [
419
+ {
420
+ key: 'cellulare',
421
+ dbField: 'CELLULARE',
422
+ transform: (v) => String(v),
423
+ },
424
+ {
425
+ key: 'avatar',
426
+ dbField: 'AVATAR',
427
+ transform: (v) => String(v),
428
+ },
429
+ {
430
+ key: 'flagDueFattori',
431
+ dbField: 'FLG2FATT',
432
+ transform: (v) => (v ? 1 : 0),
433
+ },
434
+ {
435
+ key: 'paginaDefault',
436
+ dbField: 'PAGDEF',
437
+ transform: (v) => String(v),
438
+ },
439
+ {
440
+ key: 'nummac',
441
+ dbField: 'NUMMAC',
442
+ transform: (v) => Number(v),
443
+ },
444
+ {
445
+ key: 'ragSocCli',
446
+ dbField: 'RAGSOCCLI',
447
+ transform: (v) => String(v),
448
+ },
449
+ ];
450
+ if (allowPrivilegedFields) {
451
+ optionalFields.push({
452
+ key: 'flagSuper',
453
+ dbField: 'FLGSUPER',
454
+ transform: (v) => (v ? 1 : 0),
455
+ }, {
456
+ key: 'flagAdminConfigurator',
457
+ dbField: 'FLGADMINCONFIG',
458
+ transform: (v) => (v ? 1 : 0),
459
+ });
460
+ }
461
+ for (const field of optionalFields) {
462
+ const value = registrationData[field.key];
463
+ if (value !== undefined && value !== null) {
464
+ utentiConfigFields.push(field.dbField);
465
+ utentiConfigPlaceholders.push('?');
466
+ utentiConfigParams.push(field.transform ? field.transform(value) : value);
467
+ }
468
+ }
469
+ const queryUtentiConfig = `INSERT INTO UTENTI_CONFIG (${utentiConfigFields.join(', ')}) VALUES (${utentiConfigPlaceholders.join(', ')})`;
470
+ yield Orm_1.Orm.transactionQuery(transaction, queryUtentiConfig, utentiConfigParams);
471
+ return newCodiceUtente;
472
+ }));
387
473
  yield this.filtriService.upsertFiltriUtente(codiceUtente, registrationData);
388
474
  if (allowPrivilegedFields && !!registrationData.roles && registrationData.roles.length > 0) {
389
475
  yield this.permissionService.assignRolesToUser(codiceUtente, registrationData.roles);