@hed-hog/core 0.0.387 → 0.0.390

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 (535) hide show
  1. package/README.md +12 -11
  2. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  3. package/dist/access-log/access-log.interceptor.js +4 -2
  4. package/dist/access-log/access-log.interceptor.js.map +1 -1
  5. package/dist/ai/ai-attachment.fragments.d.ts +23 -0
  6. package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
  7. package/dist/ai/ai-attachment.fragments.js +34 -0
  8. package/dist/ai/ai-attachment.fragments.js.map +1 -0
  9. package/dist/ai/ai.controller.d.ts +7 -16
  10. package/dist/ai/ai.controller.d.ts.map +1 -1
  11. package/dist/ai/ai.prompts.d.ts +16 -0
  12. package/dist/ai/ai.prompts.d.ts.map +1 -0
  13. package/dist/ai/ai.prompts.js +37 -0
  14. package/dist/ai/ai.prompts.js.map +1 -0
  15. package/dist/ai/ai.service.d.ts +63 -12
  16. package/dist/ai/ai.service.d.ts.map +1 -1
  17. package/dist/ai/ai.service.js +215 -88
  18. package/dist/ai/ai.service.js.map +1 -1
  19. package/dist/ai/dto/chat.dto.d.ts +4 -1
  20. package/dist/ai/dto/chat.dto.d.ts.map +1 -1
  21. package/dist/ai/dto/chat.dto.js +23 -1
  22. package/dist/ai/dto/chat.dto.js.map +1 -1
  23. package/dist/ai/dto/create-agent.dto.d.ts +1 -1
  24. package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
  25. package/dist/ai/dto/create-agent.dto.js +1 -1
  26. package/dist/ai/dto/create-agent.dto.js.map +1 -1
  27. package/dist/ai/dto/update-agent.dto.d.ts +1 -1
  28. package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
  29. package/dist/ai/dto/update-agent.dto.js +1 -1
  30. package/dist/ai/dto/update-agent.dto.js.map +1 -1
  31. package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
  32. package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
  33. package/dist/ai-instruction/ai-instruction.controller.js +63 -20
  34. package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
  35. package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
  36. package/dist/ai-instruction/ai-instruction.module.js +13 -2
  37. package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
  38. package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
  39. package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
  40. package/dist/ai-instruction/ai-instruction.service.js +533 -60
  41. package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
  42. package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
  43. package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
  44. package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
  45. package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
  46. package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
  47. package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
  48. package/dist/ai-instruction/ai-prompt-registry.js +49 -0
  49. package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
  50. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
  51. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
  52. package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
  53. package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
  54. package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
  55. package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
  56. package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
  57. package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
  58. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
  59. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
  60. package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
  61. package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
  62. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
  63. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
  64. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
  65. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
  66. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
  67. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
  68. package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
  69. package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
  70. package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
  71. package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
  72. package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
  73. package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
  74. package/dist/auth/auth.controller.d.ts +24 -9
  75. package/dist/auth/auth.controller.d.ts.map +1 -1
  76. package/dist/auth/auth.controller.js +49 -21
  77. package/dist/auth/auth.controller.js.map +1 -1
  78. package/dist/auth/auth.service.d.ts +33 -8
  79. package/dist/auth/auth.service.d.ts.map +1 -1
  80. package/dist/auth/auth.service.js +78 -29
  81. package/dist/auth/auth.service.js.map +1 -1
  82. package/dist/captcha/captcha.module.d.ts +3 -0
  83. package/dist/captcha/captcha.module.d.ts.map +1 -0
  84. package/dist/captcha/captcha.module.js +23 -0
  85. package/dist/captcha/captcha.module.js.map +1 -0
  86. package/dist/captcha/captcha.service.d.ts +38 -0
  87. package/dist/captcha/captcha.service.d.ts.map +1 -0
  88. package/dist/captcha/captcha.service.js +144 -0
  89. package/dist/captcha/captcha.service.js.map +1 -0
  90. package/dist/challenge/challenge.service.d.ts +16 -7
  91. package/dist/challenge/challenge.service.d.ts.map +1 -1
  92. package/dist/challenge/challenge.service.js +64 -16
  93. package/dist/challenge/challenge.service.js.map +1 -1
  94. package/dist/core.module.d.ts +3 -1
  95. package/dist/core.module.d.ts.map +1 -1
  96. package/dist/core.module.js +29 -2
  97. package/dist/core.module.js.map +1 -1
  98. package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
  99. package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
  100. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
  101. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
  102. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
  103. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
  104. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
  105. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
  106. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
  107. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
  108. package/dist/file/file.service.d.ts +6 -0
  109. package/dist/file/file.service.d.ts.map +1 -1
  110. package/dist/file/file.service.js +44 -4
  111. package/dist/file/file.service.js.map +1 -1
  112. package/dist/incident/incident-notifier.service.d.ts +36 -0
  113. package/dist/incident/incident-notifier.service.d.ts.map +1 -0
  114. package/dist/incident/incident-notifier.service.js +208 -0
  115. package/dist/incident/incident-notifier.service.js.map +1 -0
  116. package/dist/incident/incident.controller.d.ts +23 -0
  117. package/dist/incident/incident.controller.d.ts.map +1 -0
  118. package/dist/incident/incident.controller.js +75 -0
  119. package/dist/incident/incident.controller.js.map +1 -0
  120. package/dist/incident/incident.module.d.ts +7 -0
  121. package/dist/incident/incident.module.d.ts.map +1 -0
  122. package/dist/incident/incident.module.js +32 -0
  123. package/dist/incident/incident.module.js.map +1 -0
  124. package/dist/incident/incident.service.d.ts +72 -0
  125. package/dist/incident/incident.service.d.ts.map +1 -0
  126. package/dist/incident/incident.service.js +184 -0
  127. package/dist/incident/incident.service.js.map +1 -0
  128. package/dist/index.d.ts +26 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +30 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/integration-profile/dto/test.dto.d.ts +13 -0
  133. package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
  134. package/dist/integration-profile/dto/test.dto.js +26 -0
  135. package/dist/integration-profile/dto/test.dto.js.map +1 -0
  136. package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
  137. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  138. package/dist/integration-profile/integration-profile.controller.js +9 -1
  139. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  140. package/dist/integration-profile/integration-profile.module.d.ts +6 -1
  141. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  142. package/dist/integration-profile/integration-profile.module.js +26 -3
  143. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  144. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  145. package/dist/integration-profile/integration-profile.secrets.js +9 -1
  146. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  147. package/dist/integration-profile/integration-profile.service.d.ts +26 -7
  148. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  149. package/dist/integration-profile/integration-profile.service.js +94 -91
  150. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
  152. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  153. package/dist/integration-profile/integration-profile.utils.js +20 -5
  154. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  155. package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
  156. package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
  157. package/dist/integration-profile/testers/ai.testers.js +125 -0
  158. package/dist/integration-profile/testers/ai.testers.js.map +1 -0
  159. package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
  160. package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
  161. package/dist/integration-profile/testers/captcha.testers.js +121 -0
  162. package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
  163. package/dist/integration-profile/testers/email.testers.d.ts +3 -0
  164. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
  165. package/dist/integration-profile/testers/email.testers.js +128 -0
  166. package/dist/integration-profile/testers/email.testers.js.map +1 -0
  167. package/dist/integration-profile/testers/http-probe.d.ts +57 -0
  168. package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
  169. package/dist/integration-profile/testers/http-probe.js +140 -0
  170. package/dist/integration-profile/testers/http-probe.js.map +1 -0
  171. package/dist/integration-profile/testers/index.d.ts +12 -0
  172. package/dist/integration-profile/testers/index.d.ts.map +1 -0
  173. package/dist/integration-profile/testers/index.js +45 -0
  174. package/dist/integration-profile/testers/index.js.map +1 -0
  175. package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
  176. package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
  177. package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
  178. package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
  179. package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
  180. package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
  181. package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
  182. package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
  183. package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
  184. package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
  185. package/dist/integration-profile/testers/integration-tester.types.js +16 -0
  186. package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
  187. package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
  188. package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
  189. package/dist/integration-profile/testers/messaging.testers.js +70 -0
  190. package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
  191. package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
  192. package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
  193. package/dist/integration-profile/testers/oauth.testers.js +285 -0
  194. package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
  195. package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
  196. package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
  197. package/dist/integration-profile/testers/payment.testers.js +169 -0
  198. package/dist/integration-profile/testers/payment.testers.js.map +1 -0
  199. package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
  200. package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
  201. package/dist/integration-profile/testers/storage.testers.js +208 -0
  202. package/dist/integration-profile/testers/storage.testers.js.map +1 -0
  203. package/dist/language/en.json +4 -2
  204. package/dist/language/pt.json +4 -2
  205. package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
  206. package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
  207. package/dist/leaked-password/leaked-password.controller.js +78 -0
  208. package/dist/leaked-password/leaked-password.controller.js.map +1 -0
  209. package/dist/leaked-password/leaked-password.module.d.ts +8 -0
  210. package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
  211. package/dist/leaked-password/leaked-password.module.js +31 -0
  212. package/dist/leaked-password/leaked-password.module.js.map +1 -0
  213. package/dist/leaked-password/leaked-password.service.d.ts +64 -0
  214. package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
  215. package/dist/leaked-password/leaked-password.service.js +218 -0
  216. package/dist/leaked-password/leaked-password.service.js.map +1 -0
  217. package/dist/locale/locale.controller.d.ts +1 -1
  218. package/dist/locale/locale.service.d.ts +1 -1
  219. package/dist/mail/dto/preview.dto.d.ts +18 -0
  220. package/dist/mail/dto/preview.dto.d.ts.map +1 -0
  221. package/dist/mail/dto/preview.dto.js +66 -0
  222. package/dist/mail/dto/preview.dto.js.map +1 -0
  223. package/dist/mail/dto/send.dto.d.ts +7 -0
  224. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  225. package/dist/mail/dto/send.dto.js +10 -0
  226. package/dist/mail/dto/send.dto.js.map +1 -1
  227. package/dist/mail/mail.controller.d.ts +4 -0
  228. package/dist/mail/mail.controller.d.ts.map +1 -1
  229. package/dist/mail/mail.controller.js +16 -2
  230. package/dist/mail/mail.controller.js.map +1 -1
  231. package/dist/mail/mail.service.d.ts +23 -2
  232. package/dist/mail/mail.service.d.ts.map +1 -1
  233. package/dist/mail/mail.service.js +102 -13
  234. package/dist/mail/mail.service.js.map +1 -1
  235. package/dist/mail-layout/dto/update.dto.d.ts +17 -0
  236. package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
  237. package/dist/mail-layout/dto/update.dto.js +27 -0
  238. package/dist/mail-layout/dto/update.dto.js.map +1 -0
  239. package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
  240. package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
  241. package/dist/mail-layout/mail-layout.controller.js +71 -0
  242. package/dist/mail-layout/mail-layout.controller.js.map +1 -0
  243. package/dist/mail-layout/mail-layout.module.d.ts +3 -0
  244. package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
  245. package/dist/mail-layout/mail-layout.module.js +35 -0
  246. package/dist/mail-layout/mail-layout.module.js.map +1 -0
  247. package/dist/mail-layout/mail-layout.service.d.ts +27 -0
  248. package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
  249. package/dist/mail-layout/mail-layout.service.js +80 -0
  250. package/dist/mail-layout/mail-layout.service.js.map +1 -0
  251. package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
  252. package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
  253. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  254. package/dist/mcp-chat/mcp-chat.service.js +80 -24
  255. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  256. package/dist/menu/menu.controller.d.ts +3 -3
  257. package/dist/menu/menu.service.d.ts +3 -3
  258. package/dist/notification/notification.controller.d.ts +12 -12
  259. package/dist/notification/notification.service.d.ts +12 -12
  260. package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
  261. package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
  262. package/dist/oauth/oauth-token-broker.service.js +258 -0
  263. package/dist/oauth/oauth-token-broker.service.js.map +1 -0
  264. package/dist/oauth/oauth.module.d.ts.map +1 -1
  265. package/dist/oauth/oauth.module.js +3 -1
  266. package/dist/oauth/oauth.module.js.map +1 -1
  267. package/dist/oauth/oauth.service.d.ts +2 -0
  268. package/dist/oauth/oauth.service.d.ts.map +1 -1
  269. package/dist/oauth/oauth.service.js +6 -3
  270. package/dist/oauth/oauth.service.js.map +1 -1
  271. package/dist/pdf/pdf.service.d.ts +8 -0
  272. package/dist/pdf/pdf.service.d.ts.map +1 -1
  273. package/dist/pdf/pdf.service.js +52 -0
  274. package/dist/pdf/pdf.service.js.map +1 -1
  275. package/dist/presence/presence.controller.d.ts +16 -0
  276. package/dist/presence/presence.controller.d.ts.map +1 -0
  277. package/dist/presence/presence.controller.js +60 -0
  278. package/dist/presence/presence.controller.js.map +1 -0
  279. package/dist/presence/presence.listener.d.ts +29 -0
  280. package/dist/presence/presence.listener.d.ts.map +1 -0
  281. package/dist/presence/presence.listener.js +82 -0
  282. package/dist/presence/presence.listener.js.map +1 -0
  283. package/dist/presence/presence.module.d.ts +3 -0
  284. package/dist/presence/presence.module.d.ts.map +1 -0
  285. package/dist/presence/presence.module.js +32 -0
  286. package/dist/presence/presence.module.js.map +1 -0
  287. package/dist/presence/presence.service.d.ts +42 -0
  288. package/dist/presence/presence.service.d.ts.map +1 -0
  289. package/dist/presence/presence.service.js +147 -0
  290. package/dist/presence/presence.service.js.map +1 -0
  291. package/dist/profile/dto/delete-account.dto.d.ts +8 -0
  292. package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
  293. package/dist/profile/dto/delete-account.dto.js +22 -0
  294. package/dist/profile/dto/delete-account.dto.js.map +1 -0
  295. package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
  296. package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
  297. package/dist/profile/events/profile-account-deleted.event.js +17 -0
  298. package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
  299. package/dist/profile/profile.controller.d.ts +21 -15
  300. package/dist/profile/profile.controller.d.ts.map +1 -1
  301. package/dist/profile/profile.controller.js +75 -46
  302. package/dist/profile/profile.controller.js.map +1 -1
  303. package/dist/profile/profile.service.d.ts +30 -18
  304. package/dist/profile/profile.service.d.ts.map +1 -1
  305. package/dist/profile/profile.service.js +113 -37
  306. package/dist/profile/profile.service.js.map +1 -1
  307. package/dist/realtime/realtime.gateway.d.ts +48 -0
  308. package/dist/realtime/realtime.gateway.d.ts.map +1 -0
  309. package/dist/realtime/realtime.gateway.js +156 -0
  310. package/dist/realtime/realtime.gateway.js.map +1 -0
  311. package/dist/realtime/realtime.module.d.ts +8 -0
  312. package/dist/realtime/realtime.module.d.ts.map +1 -0
  313. package/dist/realtime/realtime.module.js +29 -0
  314. package/dist/realtime/realtime.module.js.map +1 -0
  315. package/dist/realtime/realtime.service.d.ts +51 -0
  316. package/dist/realtime/realtime.service.d.ts.map +1 -0
  317. package/dist/realtime/realtime.service.js +177 -0
  318. package/dist/realtime/realtime.service.js.map +1 -0
  319. package/dist/redis/redis-pubsub.d.ts +27 -0
  320. package/dist/redis/redis-pubsub.d.ts.map +1 -0
  321. package/dist/redis/redis-pubsub.js +140 -0
  322. package/dist/redis/redis-pubsub.js.map +1 -0
  323. package/dist/role/role.controller.d.ts +3 -3
  324. package/dist/role/role.service.d.ts +3 -3
  325. package/dist/setting/dto/create.dto.d.ts.map +1 -1
  326. package/dist/setting/dto/create.dto.js +9 -2
  327. package/dist/setting/dto/create.dto.js.map +1 -1
  328. package/dist/setting/setting.controller.d.ts +5 -5
  329. package/dist/setting/setting.service.d.ts +8 -5
  330. package/dist/setting/setting.service.d.ts.map +1 -1
  331. package/dist/setting/setting.service.js +25 -1
  332. package/dist/setting/setting.service.js.map +1 -1
  333. package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
  334. package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
  335. package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
  336. package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
  337. package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
  338. package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
  339. package/dist/user/dto/query-user-mail-list.dto.js +25 -0
  340. package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
  341. package/dist/user/user-credential.util.d.ts +19 -0
  342. package/dist/user/user-credential.util.d.ts.map +1 -0
  343. package/dist/user/user-credential.util.js +32 -0
  344. package/dist/user/user-credential.util.js.map +1 -0
  345. package/dist/user/user.controller.d.ts +17 -5
  346. package/dist/user/user.controller.d.ts.map +1 -1
  347. package/dist/user/user.controller.js +18 -3
  348. package/dist/user/user.controller.js.map +1 -1
  349. package/dist/user/user.service.d.ts +28 -6
  350. package/dist/user/user.service.d.ts.map +1 -1
  351. package/dist/user/user.service.js +204 -13
  352. package/dist/user/user.service.js.map +1 -1
  353. package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
  354. package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
  355. package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
  356. package/dist/validators/service-locator.d.ts +9 -0
  357. package/dist/validators/service-locator.d.ts.map +1 -1
  358. package/dist/validators/service-locator.js +12 -0
  359. package/dist/validators/service-locator.js.map +1 -1
  360. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
  361. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
  362. package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
  363. package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
  364. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
  365. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
  366. package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
  367. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
  368. package/hedhog/data/integration_provider.yaml +16 -0
  369. package/hedhog/data/mail.yaml +88 -61
  370. package/hedhog/data/mail_layout.yaml +188 -0
  371. package/hedhog/data/menu.yaml +67 -3
  372. package/hedhog/data/route.yaml +135 -0
  373. package/hedhog/data/setting_group.yaml +139 -1
  374. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
  375. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
  376. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
  377. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
  378. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
  379. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
  380. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
  381. package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
  382. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
  383. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
  384. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
  385. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
  386. package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
  387. package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
  389. package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
  390. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
  391. package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
  392. package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
  393. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
  394. package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
  395. package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
  396. package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
  397. package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
  398. package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
  399. package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
  400. package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
  401. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
  402. package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
  403. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
  404. package/hedhog/frontend/messages/en.json +499 -3
  405. package/hedhog/frontend/messages/pt.json +499 -3
  406. package/hedhog/table/ai_agent.yaml +1 -0
  407. package/hedhog/table/ai_instruction.yaml +15 -0
  408. package/hedhog/table/leaked_password.yaml +18 -0
  409. package/hedhog/table/mail_layout.yaml +35 -0
  410. package/hedhog/table/mail_var.yaml +4 -0
  411. package/hedhog/table/setting.yaml +1 -0
  412. package/hedhog/table/system_incident.yaml +59 -0
  413. package/hedhog/table/user_presence.yaml +36 -0
  414. package/package.json +8 -4
  415. package/src/access-log/access-log.interceptor.ts +4 -2
  416. package/src/ai/ai-attachment.fragments.spec.ts +319 -0
  417. package/src/ai/ai-attachment.fragments.ts +35 -0
  418. package/src/ai/ai.prompts.ts +38 -0
  419. package/src/ai/ai.service.spec.ts +280 -0
  420. package/src/ai/ai.service.ts +338 -82
  421. package/src/ai/dto/chat.dto.ts +33 -3
  422. package/src/ai/dto/create-agent.dto.ts +2 -2
  423. package/src/ai/dto/update-agent.dto.ts +2 -2
  424. package/src/ai-instruction/ai-instruction.controller.ts +40 -9
  425. package/src/ai-instruction/ai-instruction.module.ts +13 -2
  426. package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
  427. package/src/ai-instruction/ai-instruction.service.ts +661 -62
  428. package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
  429. package/src/ai-instruction/ai-prompt-registry.ts +87 -0
  430. package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
  431. package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
  432. package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
  433. package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
  434. package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
  435. package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
  436. package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
  437. package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
  438. package/src/auth/auth.controller.ts +31 -5
  439. package/src/auth/auth.service.spec.ts +3 -1
  440. package/src/auth/auth.service.ts +101 -36
  441. package/src/captcha/captcha.module.ts +10 -0
  442. package/src/captcha/captcha.service.spec.ts +210 -0
  443. package/src/captcha/captcha.service.ts +191 -0
  444. package/src/challenge/challenge.service.spec.ts +39 -4
  445. package/src/challenge/challenge.service.ts +72 -17
  446. package/src/core.module.ts +27 -1
  447. package/src/file/file.service.spec.ts +105 -0
  448. package/src/file/file.service.ts +60 -3
  449. package/src/incident/incident-notifier.service.spec.ts +205 -0
  450. package/src/incident/incident-notifier.service.ts +241 -0
  451. package/src/incident/incident.controller.ts +38 -0
  452. package/src/incident/incident.module.ts +19 -0
  453. package/src/incident/incident.service.spec.ts +150 -0
  454. package/src/incident/incident.service.ts +209 -0
  455. package/src/index.ts +37 -0
  456. package/src/integration-profile/dto/test.dto.ts +19 -0
  457. package/src/integration-profile/integration-profile.controller.ts +10 -1
  458. package/src/integration-profile/integration-profile.module.spec.ts +51 -0
  459. package/src/integration-profile/integration-profile.module.ts +23 -4
  460. package/src/integration-profile/integration-profile.secrets.ts +9 -1
  461. package/src/integration-profile/integration-profile.service.spec.ts +279 -0
  462. package/src/integration-profile/integration-profile.service.ts +120 -124
  463. package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
  464. package/src/integration-profile/integration-profile.utils.ts +40 -3
  465. package/src/integration-profile/testers/ai.testers.ts +165 -0
  466. package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
  467. package/src/integration-profile/testers/captcha.testers.ts +139 -0
  468. package/src/integration-profile/testers/email.testers.ts +161 -0
  469. package/src/integration-profile/testers/http-probe.spec.ts +153 -0
  470. package/src/integration-profile/testers/http-probe.ts +171 -0
  471. package/src/integration-profile/testers/index.ts +30 -0
  472. package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
  473. package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
  474. package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
  475. package/src/integration-profile/testers/integration-tester.types.ts +61 -0
  476. package/src/integration-profile/testers/messaging.testers.ts +102 -0
  477. package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
  478. package/src/integration-profile/testers/oauth.testers.ts +305 -0
  479. package/src/integration-profile/testers/payment.testers.ts +185 -0
  480. package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
  481. package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
  482. package/src/integration-profile/testers/storage.testers.ts +226 -0
  483. package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
  484. package/src/language/en.json +4 -2
  485. package/src/language/pt.json +4 -2
  486. package/src/leaked-password/README.md +83 -0
  487. package/src/leaked-password/leaked-password.controller.ts +52 -0
  488. package/src/leaked-password/leaked-password.module.ts +18 -0
  489. package/src/leaked-password/leaked-password.service.ts +242 -0
  490. package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
  491. package/src/leaked-password/seed/common-passwords.txt +335 -0
  492. package/src/mail/dto/preview.dto.ts +50 -0
  493. package/src/mail/dto/send.dto.ts +14 -1
  494. package/src/mail/mail-template.spec.ts +197 -0
  495. package/src/mail/mail.controller.ts +9 -2
  496. package/src/mail/mail.service.spec.ts +116 -1
  497. package/src/mail/mail.service.ts +118 -12
  498. package/src/mail-layout/dto/update.dto.ts +19 -0
  499. package/src/mail-layout/mail-layout.controller.ts +47 -0
  500. package/src/mail-layout/mail-layout.module.ts +22 -0
  501. package/src/mail-layout/mail-layout.service.ts +94 -0
  502. package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
  503. package/src/mcp-chat/mcp-chat.service.ts +122 -26
  504. package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
  505. package/src/oauth/oauth-token-broker.service.ts +363 -0
  506. package/src/oauth/oauth.module.ts +3 -1
  507. package/src/oauth/oauth.service.ts +8 -2
  508. package/src/pdf/pdf.service.ts +75 -0
  509. package/src/presence/presence.controller.ts +29 -0
  510. package/src/presence/presence.listener.ts +53 -0
  511. package/src/presence/presence.module.ts +19 -0
  512. package/src/presence/presence.service.spec.ts +97 -0
  513. package/src/presence/presence.service.ts +160 -0
  514. package/src/profile/dto/delete-account.dto.ts +11 -0
  515. package/src/profile/events/profile-account-deleted.event.ts +13 -0
  516. package/src/profile/profile.controller.ts +46 -34
  517. package/src/profile/profile.service.ts +138 -38
  518. package/src/realtime/realtime.gateway.ts +138 -0
  519. package/src/realtime/realtime.module.ts +16 -0
  520. package/src/realtime/realtime.service.spec.ts +50 -0
  521. package/src/realtime/realtime.service.ts +187 -0
  522. package/src/redis/redis-pubsub.ts +179 -0
  523. package/src/setting/dto/create.dto.ts +13 -2
  524. package/src/setting/setting.service.spec.ts +56 -0
  525. package/src/setting/setting.service.ts +29 -1
  526. package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
  527. package/src/user/dto/query-user-mail-list.dto.ts +10 -0
  528. package/src/user/user-credential.util.ts +35 -0
  529. package/src/user/user.controller.ts +17 -2
  530. package/src/user/user.service.spec.ts +153 -0
  531. package/src/user/user.service.ts +255 -12
  532. package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
  533. package/src/validators/service-locator.ts +15 -0
  534. package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
  535. package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
@@ -0,0 +1,210 @@
1
+ import { BadRequestException } from '@nestjs/common';
2
+ import { createHash, createHmac } from 'crypto';
3
+ import { CaptchaService } from './captcha.service';
4
+
5
+ describe('CaptchaService', () => {
6
+ let prisma: any;
7
+ let service: CaptchaService;
8
+ const originalFetch = global.fetch;
9
+
10
+ beforeEach(() => {
11
+ prisma = { integration_profile: { findFirst: jest.fn() } };
12
+ service = new CaptchaService(prisma);
13
+ global.fetch = jest.fn() as never;
14
+ });
15
+
16
+ afterEach(() => {
17
+ global.fetch = originalFetch;
18
+ jest.restoreAllMocks();
19
+ });
20
+
21
+ function mockFetch(success: boolean) {
22
+ (global.fetch as jest.Mock).mockResolvedValue({
23
+ json: async () => ({ success }),
24
+ });
25
+ }
26
+
27
+ describe('verify', () => {
28
+ it('rejects an empty token before calling the provider', async () => {
29
+ await expect(service.verify('', 'recaptcha', {})).rejects.toBeInstanceOf(
30
+ BadRequestException,
31
+ );
32
+ expect(global.fetch).not.toHaveBeenCalled();
33
+ });
34
+
35
+ it('rejects an unsupported provider', async () => {
36
+ await expect(
37
+ service.verify('token', 'hcaptcha', {}),
38
+ ).rejects.toThrow('Unsupported CAPTCHA provider: hcaptcha.');
39
+ });
40
+
41
+ describe('recaptcha', () => {
42
+ it('accepts a token the provider confirms', async () => {
43
+ mockFetch(true);
44
+ await expect(
45
+ service.verify('token', 'recaptcha', { secret_key: 'secret' }),
46
+ ).resolves.toBeUndefined();
47
+
48
+ const [url, init] = (global.fetch as jest.Mock).mock.calls[0];
49
+ expect(url).toBe('https://www.google.com/recaptcha/api/siteverify');
50
+ expect(init.method).toBe('POST');
51
+ expect(String(init.body)).toContain('secret=secret');
52
+ expect(String(init.body)).toContain('response=token');
53
+ });
54
+
55
+ it('rejects a token the provider refuses', async () => {
56
+ mockFetch(false);
57
+ await expect(
58
+ service.verify('token', 'recaptcha', { secret_key: 'secret' }),
59
+ ).rejects.toThrow('CAPTCHA validation failed.');
60
+ });
61
+ });
62
+
63
+ describe('cloudflare-turnstile', () => {
64
+ it('calls the turnstile endpoint', async () => {
65
+ mockFetch(true);
66
+ await service.verify('token', 'cloudflare-turnstile', {
67
+ secret_key: 'secret',
68
+ });
69
+
70
+ expect((global.fetch as jest.Mock).mock.calls[0][0]).toBe(
71
+ 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
72
+ );
73
+ });
74
+
75
+ it('rejects a refused token', async () => {
76
+ mockFetch(false);
77
+ await expect(
78
+ service.verify('token', 'cloudflare-turnstile', { secret_key: 's' }),
79
+ ).rejects.toThrow('CAPTCHA validation failed.');
80
+ });
81
+ });
82
+
83
+ describe('altcha', () => {
84
+ const hmacKey = 'hmac-key';
85
+
86
+ function buildToken(overrides: Partial<Record<string, unknown>> = {}) {
87
+ const salt = 'salt';
88
+ const number = 12345;
89
+ const challenge = createHash('sha256')
90
+ .update(salt + String(number))
91
+ .digest('hex');
92
+ const signature = createHmac('sha256', hmacKey)
93
+ .update(challenge)
94
+ .digest('hex');
95
+
96
+ const payload = {
97
+ algorithm: 'SHA-256',
98
+ challenge,
99
+ number,
100
+ salt,
101
+ signature,
102
+ ...overrides,
103
+ };
104
+ return Buffer.from(JSON.stringify(payload)).toString('base64');
105
+ }
106
+
107
+ it('accepts a correctly solved challenge', async () => {
108
+ await expect(
109
+ service.verify(buildToken(), 'altcha', { hmac_key: hmacKey }),
110
+ ).resolves.toBeUndefined();
111
+ // Altcha e verificado localmente: nenhuma chamada de rede.
112
+ expect(global.fetch).not.toHaveBeenCalled();
113
+ });
114
+
115
+ it('rejects a malformed token', async () => {
116
+ await expect(
117
+ service.verify('not-base64-json', 'altcha', { hmac_key: hmacKey }),
118
+ ).rejects.toThrow('Invalid CAPTCHA token format.');
119
+ });
120
+
121
+ it('rejects when the proof of work does not match the challenge', async () => {
122
+ await expect(
123
+ service.verify(buildToken({ number: 999 }), 'altcha', {
124
+ hmac_key: hmacKey,
125
+ }),
126
+ ).rejects.toThrow('CAPTCHA validation failed.');
127
+ });
128
+
129
+ it('rejects a challenge we did not sign', async () => {
130
+ await expect(
131
+ service.verify(buildToken(), 'altcha', { hmac_key: 'other-key' }),
132
+ ).rejects.toThrow('CAPTCHA validation failed.');
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('verifyForProfile', () => {
138
+ it('resolves the provider from the integration profile', async () => {
139
+ prisma.integration_profile.findFirst.mockResolvedValue({
140
+ config: { secret_key: 'secret' },
141
+ integration_provider: { slug: 'recaptcha' },
142
+ });
143
+ mockFetch(true);
144
+
145
+ await expect(service.verifyForProfile(7, 'token')).resolves.toBeUndefined();
146
+ expect(prisma.integration_profile.findFirst).toHaveBeenCalledWith(
147
+ expect.objectContaining({ where: { id: 7 } }),
148
+ );
149
+ });
150
+
151
+ it('refuses when no profile id is configured', async () => {
152
+ await expect(service.verifyForProfile(null, 'token')).rejects.toThrow(
153
+ 'CAPTCHA is enabled but no integration profile is configured.',
154
+ );
155
+ });
156
+
157
+ it('refuses when the profile does not exist', async () => {
158
+ prisma.integration_profile.findFirst.mockResolvedValue(null);
159
+ await expect(service.verifyForProfile(7, 'token')).rejects.toThrow(
160
+ 'CAPTCHA integration profile not found.',
161
+ );
162
+ });
163
+ });
164
+
165
+ describe('getPublicConfig', () => {
166
+ it('returns the provider and site key', async () => {
167
+ prisma.integration_profile.findFirst.mockResolvedValue({
168
+ config: { site_key: 'public-key', secret_key: 'secret' },
169
+ integration_provider: { slug: 'cloudflare-turnstile' },
170
+ });
171
+
172
+ const config = await service.getPublicConfig(7);
173
+
174
+ expect(config).toEqual({
175
+ enabled: true,
176
+ provider_slug: 'cloudflare-turnstile',
177
+ site_key: 'public-key',
178
+ });
179
+ // O segredo nunca sai do servidor.
180
+ expect(JSON.stringify(config)).not.toContain('secret');
181
+ });
182
+
183
+ it('reports disabled when there is no profile', async () => {
184
+ await expect(service.getPublicConfig(null)).resolves.toEqual({
185
+ enabled: false,
186
+ provider_slug: null,
187
+ site_key: null,
188
+ });
189
+ });
190
+
191
+ it('reports disabled when the caller says the feature is off', async () => {
192
+ await expect(service.getPublicConfig(7, false)).resolves.toEqual({
193
+ enabled: false,
194
+ provider_slug: null,
195
+ site_key: null,
196
+ });
197
+ expect(prisma.integration_profile.findFirst).not.toHaveBeenCalled();
198
+ });
199
+ });
200
+
201
+ describe('extractSiteKey', () => {
202
+ it('accepts the naming variants providers use', () => {
203
+ expect(CaptchaService.extractSiteKey({ site_key: 'a' })).toBe('a');
204
+ expect(CaptchaService.extractSiteKey({ sitekey: 'b' })).toBe('b');
205
+ expect(CaptchaService.extractSiteKey({ public_key: 'c' })).toBe('c');
206
+ expect(CaptchaService.extractSiteKey({ secret_key: 'x' })).toBeNull();
207
+ expect(CaptchaService.extractSiteKey(null)).toBeNull();
208
+ });
209
+ });
210
+ });
@@ -0,0 +1,191 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ BadRequestException,
4
+ Inject,
5
+ Injectable,
6
+ forwardRef,
7
+ } from '@nestjs/common';
8
+ import { createHash, createHmac } from 'crypto';
9
+
10
+ export const CAPTCHA_PROVIDERS = [
11
+ 'recaptcha',
12
+ 'cloudflare-turnstile',
13
+ 'altcha',
14
+ ] as const;
15
+
16
+ export type CaptchaProviderSlug = (typeof CAPTCHA_PROVIDERS)[number];
17
+
18
+ export interface CaptchaPublicConfig {
19
+ enabled: boolean;
20
+ provider_slug: string | null;
21
+ site_key: string | null;
22
+ }
23
+
24
+ interface AltchaPayload {
25
+ algorithm: string;
26
+ challenge: string;
27
+ number: number;
28
+ salt: string;
29
+ signature: string;
30
+ }
31
+
32
+ /**
33
+ * Server-side CAPTCHA verification shared by every public form in the platform.
34
+ *
35
+ * Credentials live in an `integration_profile`, so a form only stores the profile
36
+ * id and never the secret itself.
37
+ */
38
+ @Injectable()
39
+ export class CaptchaService {
40
+ constructor(
41
+ @Inject(forwardRef(() => PrismaService))
42
+ private readonly prisma: PrismaService,
43
+ ) {}
44
+
45
+ /**
46
+ * Verifies a token against the provider configured in an integration profile.
47
+ * Throws `BadRequestException` when the challenge does not check out.
48
+ */
49
+ async verifyForProfile(
50
+ integrationProfileId: number | null | undefined,
51
+ token: string | null | undefined,
52
+ ): Promise<void> {
53
+ if (!integrationProfileId) {
54
+ throw new BadRequestException(
55
+ 'CAPTCHA is enabled but no integration profile is configured.',
56
+ );
57
+ }
58
+
59
+ const profile = await (this.prisma as any).integration_profile.findFirst({
60
+ where: { id: integrationProfileId },
61
+ select: {
62
+ config: true,
63
+ integration_provider: { select: { slug: true } },
64
+ },
65
+ });
66
+
67
+ if (!profile) {
68
+ throw new BadRequestException('CAPTCHA integration profile not found.');
69
+ }
70
+
71
+ await this.verify(
72
+ token ?? '',
73
+ profile.integration_provider?.slug as string,
74
+ (profile.config ?? {}) as Record<string, string>,
75
+ );
76
+ }
77
+
78
+ /** Verifies a token against an explicit provider and its credentials. */
79
+ async verify(
80
+ token: string,
81
+ providerSlug: string,
82
+ config: Record<string, string>,
83
+ ): Promise<void> {
84
+ if (!token) {
85
+ throw new BadRequestException('CAPTCHA token is required.');
86
+ }
87
+
88
+ switch (providerSlug) {
89
+ case 'recaptcha':
90
+ await this.verifyRemote(
91
+ 'https://www.google.com/recaptcha/api/siteverify',
92
+ config.secret_key,
93
+ token,
94
+ );
95
+ break;
96
+
97
+ case 'cloudflare-turnstile':
98
+ await this.verifyRemote(
99
+ 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
100
+ config.secret_key,
101
+ token,
102
+ );
103
+ break;
104
+
105
+ case 'altcha':
106
+ this.verifyAltcha(token, config);
107
+ break;
108
+
109
+ default:
110
+ throw new BadRequestException(
111
+ `Unsupported CAPTCHA provider: ${providerSlug}.`,
112
+ );
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Public settings a front end needs to mount the widget. Only the site key is
118
+ * returned - the secret never leaves the server.
119
+ */
120
+ async getPublicConfig(
121
+ integrationProfileId: number | null | undefined,
122
+ enabled = true,
123
+ ): Promise<CaptchaPublicConfig> {
124
+ if (!enabled || !integrationProfileId) {
125
+ return { enabled: false, provider_slug: null, site_key: null };
126
+ }
127
+
128
+ const profile = await (this.prisma as any).integration_profile.findFirst({
129
+ where: { id: integrationProfileId },
130
+ select: {
131
+ config: true,
132
+ integration_provider: { select: { slug: true } },
133
+ },
134
+ });
135
+
136
+ return {
137
+ enabled: true,
138
+ provider_slug: profile?.integration_provider?.slug ?? null,
139
+ site_key: CaptchaService.extractSiteKey(profile?.config ?? null),
140
+ };
141
+ }
142
+
143
+ static extractSiteKey(
144
+ config: Record<string, unknown> | null,
145
+ ): string | null {
146
+ if (!config) return null;
147
+ const value = config.site_key ?? config.sitekey ?? config.public_key;
148
+ return value ? String(value) : null;
149
+ }
150
+
151
+ private async verifyRemote(
152
+ url: string,
153
+ secret: string,
154
+ token: string,
155
+ ): Promise<void> {
156
+ const body = new URLSearchParams({ secret, response: token });
157
+ const response = await fetch(url, { method: 'POST', body });
158
+ const json = (await response.json()) as { success: boolean };
159
+
160
+ if (!json.success) {
161
+ throw new BadRequestException('CAPTCHA validation failed.');
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Altcha is verified locally: the client solves a proof-of-work, and the HMAC
167
+ * proves the challenge was issued by us.
168
+ */
169
+ private verifyAltcha(token: string, config: Record<string, string>): void {
170
+ let payload: AltchaPayload;
171
+ try {
172
+ payload = JSON.parse(Buffer.from(token, 'base64').toString('utf8'));
173
+ } catch {
174
+ throw new BadRequestException('Invalid CAPTCHA token format.');
175
+ }
176
+
177
+ const hash = createHash('sha256')
178
+ .update(payload.salt + String(payload.number))
179
+ .digest('hex');
180
+ if (hash !== payload.challenge) {
181
+ throw new BadRequestException('CAPTCHA validation failed.');
182
+ }
183
+
184
+ const hmac = createHmac('sha256', config.hmac_key)
185
+ .update(payload.challenge)
186
+ .digest('hex');
187
+ if (hmac !== payload.signature) {
188
+ throw new BadRequestException('CAPTCHA validation failed.');
189
+ }
190
+ }
191
+ }
@@ -129,12 +129,15 @@ describe('ChallengeService', () => {
129
129
  ).rejects.toThrow(BadRequestException);
130
130
  });
131
131
 
132
- it('cria challenge e envia email com link base64', async () => {
132
+ it('cria challenge e envia email com link contendo o token original (sem recodificação)', async () => {
133
133
  const { service, deps } = makeService();
134
134
  deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
135
135
  deps.prisma.user_identifier.findFirst.mockResolvedValue({
136
136
  id: 10,
137
- user: { name: 'Bob' },
137
+ user: {
138
+ name: 'Bob',
139
+ user_credential: [{ type: 'password', revoked_at: null }],
140
+ },
138
141
  });
139
142
  deps.setting.getSettingValues.mockResolvedValue({
140
143
  'mfa-challenge-expiration-minutes': 15,
@@ -146,19 +149,51 @@ describe('ChallengeService', () => {
146
149
  const result = await service.forgotEmail('en', 'a@b.com');
147
150
 
148
151
  expect(result).toEqual({ id: 1 });
149
- const base64 = Buffer.from('tok').toString('base64');
150
152
  expect(deps.mail.sendTemplatedMail).toHaveBeenCalledWith(
151
153
  'en',
152
154
  expect.objectContaining({
153
155
  email: 'a@b.com',
154
156
  slug: 'auth-forget-password',
155
157
  variables: expect.objectContaining({
156
- link: `https://app.com/reset-password?token=${base64}`,
158
+ link: `https://app.com/reset-password?token=${encodeURIComponent('tok')}`,
157
159
  name: 'Bob',
158
160
  }),
159
161
  }),
160
162
  );
161
163
  });
164
+
165
+ it('lança BadRequest quando usuário não possui senha cadastrada (ex: login via SSO)', async () => {
166
+ const { service, deps } = makeService();
167
+ deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
168
+ deps.prisma.user_identifier.findFirst.mockResolvedValue({
169
+ id: 10,
170
+ user: { name: 'Bob', user_credential: [] },
171
+ });
172
+
173
+ await expect(
174
+ service.forgotEmail('en', 'a@b.com'),
175
+ ).rejects.toThrow(BadRequestException);
176
+ expect(deps.prisma.user_identifier_challenge.create).not.toHaveBeenCalled();
177
+ expect(deps.mail.sendTemplatedMail).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it('lança BadRequest quando a única credencial de senha está revogada', async () => {
181
+ const { service, deps } = makeService();
182
+ deps.prisma.user_identifier_challenge.count.mockResolvedValue(0);
183
+ deps.prisma.user_identifier.findFirst.mockResolvedValue({
184
+ id: 10,
185
+ user: {
186
+ name: 'Bob',
187
+ user_credential: [
188
+ { type: 'password', revoked_at: new Date('2026-01-01T00:00:00Z') },
189
+ ],
190
+ },
191
+ });
192
+
193
+ await expect(
194
+ service.forgotEmail('en', 'a@b.com'),
195
+ ).rejects.toThrow(BadRequestException);
196
+ });
162
197
  });
163
198
 
164
199
  describe('verifyCodePassword', () => {
@@ -5,6 +5,7 @@ import { MailService } from "../mail/mail.service";
5
5
  import { SecurityService } from "../security/security.service";
6
6
  import { SettingService } from "../setting/setting.service";
7
7
  import { TokenService } from "../token/token.service";
8
+ import { hasPasswordCredential } from "../user/user-credential.util";
8
9
 
9
10
  @Injectable()
10
11
  export class ChallengeService {
@@ -21,7 +22,50 @@ export class ChallengeService {
21
22
  private readonly mail: MailService,
22
23
  ) { }
23
24
 
24
- async verifyMfaEmail(locale: string, userId: number, email: string) {
25
+ /**
26
+ * Resolve a chave do app (ex.: "class") a partir do header Origin da
27
+ * requisicao, cruzando contra a setting `app-urls` (o mesmo mapeamento ja
28
+ * usado para montar links de e-mail por app). Usada para decidir a marca
29
+ * (`app-names`) em fluxos onde o frontend nao manda `app` explicitamente no
30
+ * corpo — o Origin ja chega sozinho em toda chamada cross-origin do browser.
31
+ */
32
+ async resolveAppKeyFromOrigin(origin?: string): Promise<string | undefined> {
33
+ if (!origin) {
34
+ return undefined;
35
+ }
36
+
37
+ let originHost: string;
38
+ try {
39
+ originHost = new URL(origin).hostname;
40
+ } catch {
41
+ return undefined;
42
+ }
43
+
44
+ const settings = await this.setting.getSettingValues(['app-urls']);
45
+ const appUrls: string[] = Array.isArray(settings['app-urls']) ? settings['app-urls'] : [];
46
+
47
+ for (const entry of appUrls) {
48
+ const separatorIndex = entry.indexOf('=');
49
+ if (separatorIndex === -1) {
50
+ continue;
51
+ }
52
+
53
+ const key = entry.slice(0, separatorIndex).trim();
54
+ const url = entry.slice(separatorIndex + 1).trim();
55
+
56
+ try {
57
+ if (new URL(url).hostname === originHost) {
58
+ return key;
59
+ }
60
+ } catch {
61
+ continue;
62
+ }
63
+ }
64
+
65
+ return undefined;
66
+ }
67
+
68
+ async verifyMfaEmail(locale: string, userId: number, email: string, origin?: string) {
25
69
  const settings = await this.setting.getSettingValues([
26
70
  'mfa-email-code-length',
27
71
  'mfa-challenge-expiration-minutes'
@@ -75,7 +119,8 @@ export class ChallengeService {
75
119
  variables: {
76
120
  code,
77
121
  name: user.name,
78
- }
122
+ },
123
+ app: await this.resolveAppKeyFromOrigin(origin),
79
124
  }
80
125
  );
81
126
 
@@ -84,7 +129,7 @@ export class ChallengeService {
84
129
  return { challengeId };
85
130
  }
86
131
 
87
- async verifyEmail(locale: string, userId: number, email: string) {
132
+ async verifyEmail(locale: string, userId: number, email: string, app?: string, origin?: string) {
88
133
 
89
134
  const identifier = await this.prisma.user_identifier.findFirst({
90
135
  where: {
@@ -139,7 +184,8 @@ export class ChallengeService {
139
184
  variables: {
140
185
  code,
141
186
  name: user.name,
142
- }
187
+ },
188
+ app: app || await this.resolveAppKeyFromOrigin(origin),
143
189
  }
144
190
  );
145
191
 
@@ -202,11 +248,15 @@ export class ChallengeService {
202
248
 
203
249
  const userIdentifier = await this.prisma.user_identifier.findFirst({
204
250
  where: { type: 'email', value: email, enabled: true },
205
- include: { user: true },
251
+ include: { user: { include: { user_credential: true } } },
206
252
  });
207
253
 
208
254
  if (!userIdentifier) throw new BadRequestException(getLocaleText('forgot.userNotFound', locale, 'User not found'));
209
255
 
256
+ if (!hasPasswordCredential(userIdentifier.user)) {
257
+ throw new BadRequestException(getLocaleText('forgot.noPassword', locale, 'This account does not have a password set. Sign in using your organization\'s login method instead.'));
258
+ }
259
+
210
260
  const settings = await this.setting.getSettingValues([
211
261
  'mfa-challenge-expiration-minutes',
212
262
  'url',
@@ -223,16 +273,16 @@ export class ChallengeService {
223
273
  },
224
274
  });
225
275
 
226
- const tokenBase64 = Buffer.from(token).toString('base64');
227
276
  this.mail.sendTemplatedMail(
228
277
  locale,
229
278
  {
230
279
  email,
231
280
  slug: 'auth-forget-password',
232
281
  variables: {
233
- link: `${this.resolveAppUrl(settings, app)}/reset-password?token=${tokenBase64}`,
282
+ link: `${this.resolveAppUrl(settings, app)}/reset-password?token=${encodeURIComponent(token)}`,
234
283
  name: userIdentifier.user.name,
235
- }
284
+ },
285
+ app,
236
286
  }
237
287
  );
238
288
 
@@ -241,9 +291,10 @@ export class ChallengeService {
241
291
 
242
292
  /**
243
293
  * Cria um challenge de convite de criação de conta vinculado a um
244
- * `user_identifier` de email já existente e retorna o token em base64
245
- * (mesmo formato consumido pelo fluxo de reset de senha). A validade usa a
246
- * setting `account-invite-expiration-minutes` (padrão 7 dias).
294
+ * `user_identifier` de email já existente e retorna o token opaco (mesmo
295
+ * formato consumido pelo fluxo de reset de senha) — quem monta o link a
296
+ * partir dele deve aplicar `encodeURIComponent`. A validade usa a setting
297
+ * `account-invite-expiration-minutes` (padrão 7 dias).
247
298
  */
248
299
  async createAccountInviteChallenge(userIdentifierId: number): Promise<string> {
249
300
  const settings = await this.setting.getSettingValues([
@@ -264,7 +315,7 @@ export class ChallengeService {
264
315
  },
265
316
  });
266
317
 
267
- return Buffer.from(token).toString('base64');
318
+ return token;
268
319
  }
269
320
 
270
321
  private resolveAppUrl(settings: Record<string, any>, app?: string): string {
@@ -338,7 +389,7 @@ export class ChallengeService {
338
389
  return { success: true }
339
390
  }
340
391
 
341
- async sendMfaCodeByEmail(locale: string, userId: number, email: string) {
392
+ async sendMfaCodeByEmail(locale: string, userId: number, email: string, origin?: string) {
342
393
  const user = await this.prisma.user.findUnique({
343
394
  where: { id: userId },
344
395
  select: { name: true }
@@ -382,7 +433,8 @@ export class ChallengeService {
382
433
  variables: {
383
434
  code,
384
435
  name: user.name,
385
- }
436
+ },
437
+ app: await this.resolveAppKeyFromOrigin(origin),
386
438
  }
387
439
  );
388
440
 
@@ -413,7 +465,7 @@ export class ChallengeService {
413
465
  return true;
414
466
  }
415
467
 
416
- async sendMfaCodeToMultipleEmails(locale: string, userId: number, emails: string[]) {
468
+ async sendMfaCodeToMultipleEmails(locale: string, userId: number, emails: string[], origin?: string) {
417
469
 
418
470
  if (!emails || emails.length === 0) {
419
471
  return { success: true };
@@ -455,8 +507,10 @@ export class ChallengeService {
455
507
  }
456
508
  });
457
509
 
510
+ const app = await this.resolveAppKeyFromOrigin(origin);
511
+
458
512
  for (const email of emails) {
459
-
513
+
460
514
  this.mail.sendTemplatedMail(
461
515
  locale,
462
516
  {
@@ -465,7 +519,8 @@ export class ChallengeService {
465
519
  variables: {
466
520
  code,
467
521
  name: user.name,
468
- }
522
+ },
523
+ app,
469
524
  }
470
525
  );
471
526
  }