@hed-hog/core 0.0.390 → 0.0.393

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 (528) hide show
  1. package/README.md +39 -0
  2. package/dist/access-log/access-log.controller.d.ts.map +1 -1
  3. package/dist/access-log/access-log.controller.js +1 -1
  4. package/dist/access-log/access-log.controller.js.map +1 -1
  5. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  6. package/dist/access-log/access-log.interceptor.js +8 -5
  7. package/dist/access-log/access-log.interceptor.js.map +1 -1
  8. package/dist/access-log/access-log.service.d.ts +4 -0
  9. package/dist/access-log/access-log.service.d.ts.map +1 -1
  10. package/dist/access-log/access-log.service.js +31 -4
  11. package/dist/access-log/access-log.service.js.map +1 -1
  12. package/dist/access-log/dto/query-access-log.dto.d.ts +2 -0
  13. package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -1
  14. package/dist/access-log/dto/query-access-log.dto.js +5 -0
  15. package/dist/access-log/dto/query-access-log.dto.js.map +1 -1
  16. package/dist/ai/ai.controller.d.ts +12 -8
  17. package/dist/ai/ai.controller.d.ts.map +1 -1
  18. package/dist/ai/ai.controller.js +17 -2
  19. package/dist/ai/ai.controller.js.map +1 -1
  20. package/dist/ai/ai.module.d.ts.map +1 -1
  21. package/dist/ai/ai.module.js +3 -2
  22. package/dist/ai/ai.module.js.map +1 -1
  23. package/dist/ai/ai.service.d.ts +30 -1
  24. package/dist/ai/ai.service.d.ts.map +1 -1
  25. package/dist/ai/ai.service.js +97 -3
  26. package/dist/ai/ai.service.js.map +1 -1
  27. package/dist/ai/dto/richtext-edit-html.dto.d.ts +5 -0
  28. package/dist/ai/dto/richtext-edit-html.dto.d.ts.map +1 -0
  29. package/dist/ai/dto/richtext-edit-html.dto.js +27 -0
  30. package/dist/ai/dto/richtext-edit-html.dto.js.map +1 -0
  31. package/dist/ai/richtext-ai.prompts.d.ts +5 -0
  32. package/dist/ai/richtext-ai.prompts.d.ts.map +1 -0
  33. package/dist/ai/richtext-ai.prompts.js +57 -0
  34. package/dist/ai/richtext-ai.prompts.js.map +1 -0
  35. package/dist/ai/richtext-ai.service.d.ts +34 -0
  36. package/dist/ai/richtext-ai.service.d.ts.map +1 -0
  37. package/dist/ai/richtext-ai.service.js +95 -0
  38. package/dist/ai/richtext-ai.service.js.map +1 -0
  39. package/dist/ai-instruction/ai-instruction.controller.d.ts +5 -5
  40. package/dist/auth/auth.controller.d.ts +66 -7
  41. package/dist/auth/auth.controller.d.ts.map +1 -1
  42. package/dist/auth/auth.controller.js +67 -23
  43. package/dist/auth/auth.controller.js.map +1 -1
  44. package/dist/auth/auth.module.d.ts.map +1 -1
  45. package/dist/auth/auth.module.js +2 -0
  46. package/dist/auth/auth.module.js.map +1 -1
  47. package/dist/auth/auth.service.d.ts +136 -4
  48. package/dist/auth/auth.service.d.ts.map +1 -1
  49. package/dist/auth/auth.service.js +246 -48
  50. package/dist/auth/auth.service.js.map +1 -1
  51. package/dist/auth/dto/confirm-email.dto.d.ts +4 -0
  52. package/dist/auth/dto/confirm-email.dto.d.ts.map +1 -0
  53. package/dist/auth/dto/confirm-email.dto.js +22 -0
  54. package/dist/auth/dto/confirm-email.dto.js.map +1 -0
  55. package/dist/auth/dto/resend-confirmation.dto.d.ts +8 -0
  56. package/dist/auth/dto/resend-confirmation.dto.d.ts.map +1 -0
  57. package/dist/auth/dto/resend-confirmation.dto.js +36 -0
  58. package/dist/auth/dto/resend-confirmation.dto.js.map +1 -0
  59. package/dist/authorize/authorize.controller.d.ts +1 -1
  60. package/dist/authorize/authorize.controller.d.ts.map +1 -1
  61. package/dist/authorize/authorize.controller.js +4 -2
  62. package/dist/authorize/authorize.controller.js.map +1 -1
  63. package/dist/authorize/authorize.service.d.ts +7 -0
  64. package/dist/authorize/authorize.service.d.ts.map +1 -1
  65. package/dist/authorize/authorize.service.js +1 -1
  66. package/dist/authorize/authorize.service.js.map +1 -1
  67. package/dist/challenge/challenge.service.d.ts +18 -5
  68. package/dist/challenge/challenge.service.d.ts.map +1 -1
  69. package/dist/challenge/challenge.service.js +50 -3
  70. package/dist/challenge/challenge.service.js.map +1 -1
  71. package/dist/core.module.d.ts.map +1 -1
  72. package/dist/core.module.js +17 -0
  73. package/dist/core.module.js.map +1 -1
  74. package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -6
  75. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -6
  76. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +4 -4
  77. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +4 -4
  78. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -1
  79. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -1
  80. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +2 -2
  81. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
  82. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -2
  83. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -2
  84. package/dist/file/file.controller.d.ts +8 -1
  85. package/dist/file/file.controller.d.ts.map +1 -1
  86. package/dist/file/file.controller.js +45 -7
  87. package/dist/file/file.controller.js.map +1 -1
  88. package/dist/file/file.service.d.ts +128 -1
  89. package/dist/file/file.service.d.ts.map +1 -1
  90. package/dist/file/file.service.js +319 -8
  91. package/dist/file/file.service.js.map +1 -1
  92. package/dist/file/image-optimization.service.d.ts +43 -0
  93. package/dist/file/image-optimization.service.d.ts.map +1 -1
  94. package/dist/file/image-optimization.service.js +154 -0
  95. package/dist/file/image-optimization.service.js.map +1 -1
  96. package/dist/file/provider/abstract.provider.d.ts +16 -0
  97. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  98. package/dist/file/provider/abstract.provider.js +4 -0
  99. package/dist/file/provider/abstract.provider.js.map +1 -1
  100. package/dist/file/provider/gcs.provider.d.ts +2 -0
  101. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  102. package/dist/file/provider/gcs.provider.js +10 -0
  103. package/dist/file/provider/gcs.provider.js.map +1 -1
  104. package/dist/file/provider/local.provider.d.ts +1 -0
  105. package/dist/file/provider/local.provider.d.ts.map +1 -1
  106. package/dist/file/provider/local.provider.js +13 -0
  107. package/dist/file/provider/local.provider.js.map +1 -1
  108. package/dist/file/provider/s3.provider.d.ts +5 -0
  109. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  110. package/dist/file/provider/s3.provider.js +15 -0
  111. package/dist/file/provider/s3.provider.js.map +1 -1
  112. package/dist/file/provider/spaces.provider.d.ts +5 -0
  113. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  114. package/dist/file/provider/spaces.provider.js +16 -0
  115. package/dist/file/provider/spaces.provider.js.map +1 -1
  116. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts +4 -0
  117. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts.map +1 -0
  118. package/dist/impersonation/dto/redeem-impersonation.dto.js +23 -0
  119. package/dist/impersonation/dto/redeem-impersonation.dto.js.map +1 -0
  120. package/dist/impersonation/dto/start-impersonation.dto.d.ts +7 -0
  121. package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -0
  122. package/dist/impersonation/dto/start-impersonation.dto.js +27 -0
  123. package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -0
  124. package/dist/impersonation/impersonation-restrictions.d.ts +26 -0
  125. package/dist/impersonation/impersonation-restrictions.d.ts.map +1 -0
  126. package/dist/impersonation/impersonation-restrictions.js +76 -0
  127. package/dist/impersonation/impersonation-restrictions.js.map +1 -0
  128. package/dist/impersonation/impersonation.controller.d.ts +90 -0
  129. package/dist/impersonation/impersonation.controller.d.ts.map +1 -0
  130. package/dist/impersonation/impersonation.controller.js +135 -0
  131. package/dist/impersonation/impersonation.controller.js.map +1 -0
  132. package/dist/impersonation/impersonation.module.d.ts +8 -0
  133. package/dist/impersonation/impersonation.module.d.ts.map +1 -0
  134. package/dist/impersonation/impersonation.module.js +44 -0
  135. package/dist/impersonation/impersonation.module.js.map +1 -0
  136. package/dist/impersonation/impersonation.service.d.ts +164 -0
  137. package/dist/impersonation/impersonation.service.d.ts.map +1 -0
  138. package/dist/impersonation/impersonation.service.js +408 -0
  139. package/dist/impersonation/impersonation.service.js.map +1 -0
  140. package/dist/index.d.ts +17 -0
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +19 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
  145. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
  146. package/dist/integration/services/outbox-polling.coordinator.js +50 -30
  147. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
  148. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  149. package/dist/integration/services/outbox.service.js +5 -1
  150. package/dist/integration/services/outbox.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  152. package/dist/integration-profile/integration-profile.secrets.js +3 -0
  153. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  154. package/dist/integration-profile/integration-profile.utils.d.ts +14 -0
  155. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  156. package/dist/integration-profile/integration-profile.utils.js +26 -0
  157. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  158. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -1
  159. package/dist/integration-profile/testers/email.testers.js +28 -0
  160. package/dist/integration-profile/testers/email.testers.js.map +1 -1
  161. package/dist/language/en.json +11 -0
  162. package/dist/language/pt.json +11 -0
  163. package/dist/mail/dto/send.dto.d.ts +38 -1
  164. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  165. package/dist/mail/dto/send.dto.js +10 -0
  166. package/dist/mail/dto/send.dto.js.map +1 -1
  167. package/dist/mail/mail.controller.d.ts +1 -0
  168. package/dist/mail/mail.controller.d.ts.map +1 -1
  169. package/dist/mail/mail.module.d.ts.map +1 -1
  170. package/dist/mail/mail.module.js +2 -0
  171. package/dist/mail/mail.module.js.map +1 -1
  172. package/dist/mail/mail.service.d.ts +48 -4
  173. package/dist/mail/mail.service.d.ts.map +1 -1
  174. package/dist/mail/mail.service.js +187 -35
  175. package/dist/mail/mail.service.js.map +1 -1
  176. package/dist/mail-delivery/mail-delivery-provision.service.d.ts +21 -0
  177. package/dist/mail-delivery/mail-delivery-provision.service.d.ts.map +1 -0
  178. package/dist/mail-delivery/mail-delivery-provision.service.js +64 -0
  179. package/dist/mail-delivery/mail-delivery-provision.service.js.map +1 -0
  180. package/dist/mail-delivery/mail-delivery.constants.d.ts +17 -0
  181. package/dist/mail-delivery/mail-delivery.constants.d.ts.map +1 -0
  182. package/dist/mail-delivery/mail-delivery.constants.js +31 -0
  183. package/dist/mail-delivery/mail-delivery.constants.js.map +1 -0
  184. package/dist/mail-delivery/mail-delivery.controller.d.ts +19 -0
  185. package/dist/mail-delivery/mail-delivery.controller.d.ts.map +1 -0
  186. package/dist/mail-delivery/mail-delivery.controller.js +53 -0
  187. package/dist/mail-delivery/mail-delivery.controller.js.map +1 -0
  188. package/dist/mail-delivery/mail-delivery.module.d.ts +7 -0
  189. package/dist/mail-delivery/mail-delivery.module.d.ts.map +1 -0
  190. package/dist/mail-delivery/mail-delivery.module.js +36 -0
  191. package/dist/mail-delivery/mail-delivery.module.js.map +1 -0
  192. package/dist/mail-delivery/mail-delivery.service.d.ts +38 -0
  193. package/dist/mail-delivery/mail-delivery.service.d.ts.map +1 -0
  194. package/dist/mail-delivery/mail-delivery.service.js +140 -0
  195. package/dist/mail-delivery/mail-delivery.service.js.map +1 -0
  196. package/dist/mail-delivery/ses-event.parser.d.ts +27 -0
  197. package/dist/mail-delivery/ses-event.parser.d.ts.map +1 -0
  198. package/dist/mail-delivery/ses-event.parser.js +131 -0
  199. package/dist/mail-delivery/ses-event.parser.js.map +1 -0
  200. package/dist/mail-delivery/ses-webhook.service.d.ts +58 -0
  201. package/dist/mail-delivery/ses-webhook.service.d.ts.map +1 -0
  202. package/dist/mail-delivery/ses-webhook.service.js +214 -0
  203. package/dist/mail-delivery/ses-webhook.service.js.map +1 -0
  204. package/dist/mail-delivery/sns-message.util.d.ts +38 -0
  205. package/dist/mail-delivery/sns-message.util.d.ts.map +1 -0
  206. package/dist/mail-delivery/sns-message.util.js +109 -0
  207. package/dist/mail-delivery/sns-message.util.js.map +1 -0
  208. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +5 -0
  209. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -1
  210. package/dist/mail-sent/dto/query-mail-sent-list.dto.js +6 -0
  211. package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -1
  212. package/dist/mail-sent/mail-sent.controller.d.ts +39 -0
  213. package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
  214. package/dist/mail-sent/mail-sent.service.d.ts +39 -0
  215. package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
  216. package/dist/mail-sent/mail-sent.service.js +45 -4
  217. package/dist/mail-sent/mail-sent.service.js.map +1 -1
  218. package/dist/mailbox/mailbox-fetcher.registry.d.ts +100 -0
  219. package/dist/mailbox/mailbox-fetcher.registry.d.ts.map +1 -0
  220. package/dist/mailbox/mailbox-fetcher.registry.js +59 -0
  221. package/dist/mailbox/mailbox-fetcher.registry.js.map +1 -0
  222. package/dist/mailbox/mailbox.module.d.ts +8 -0
  223. package/dist/mailbox/mailbox.module.d.ts.map +1 -0
  224. package/dist/mailbox/mailbox.module.js +27 -0
  225. package/dist/mailbox/mailbox.module.js.map +1 -0
  226. package/dist/meeting/meeting-provider.registry.d.ts +155 -0
  227. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -0
  228. package/dist/meeting/meeting-provider.registry.js +53 -0
  229. package/dist/meeting/meeting-provider.registry.js.map +1 -0
  230. package/dist/meeting/meeting.constants.d.ts +47 -0
  231. package/dist/meeting/meeting.constants.d.ts.map +1 -0
  232. package/dist/meeting/meeting.constants.js +21 -0
  233. package/dist/meeting/meeting.constants.js.map +1 -0
  234. package/dist/meeting/meeting.module.d.ts +7 -0
  235. package/dist/meeting/meeting.module.d.ts.map +1 -0
  236. package/dist/meeting/meeting.module.js +26 -0
  237. package/dist/meeting/meeting.module.js.map +1 -0
  238. package/dist/middlewares/locale-injection.middleware.d.ts +12 -0
  239. package/dist/middlewares/locale-injection.middleware.d.ts.map +1 -1
  240. package/dist/middlewares/locale-injection.middleware.js +20 -4
  241. package/dist/middlewares/locale-injection.middleware.js.map +1 -1
  242. package/dist/oauth/providers/google.provider.d.ts +7 -0
  243. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  244. package/dist/oauth/providers/google.provider.js +43 -13
  245. package/dist/oauth/providers/google.provider.js.map +1 -1
  246. package/dist/observability/monitored-cron.d.ts +34 -0
  247. package/dist/observability/monitored-cron.d.ts.map +1 -0
  248. package/dist/observability/monitored-cron.js +118 -0
  249. package/dist/observability/monitored-cron.js.map +1 -0
  250. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts +12 -0
  251. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts.map +1 -0
  252. package/dist/pairing/dto/approve-reverse-pairing.dto.js +30 -0
  253. package/dist/pairing/dto/approve-reverse-pairing.dto.js.map +1 -0
  254. package/dist/pairing/dto/claim-pairing.dto.d.ts +11 -0
  255. package/dist/pairing/dto/claim-pairing.dto.d.ts.map +1 -0
  256. package/dist/pairing/dto/claim-pairing.dto.js +35 -0
  257. package/dist/pairing/dto/claim-pairing.dto.js.map +1 -0
  258. package/dist/pairing/dto/confirm-pairing.dto.d.ts +10 -0
  259. package/dist/pairing/dto/confirm-pairing.dto.d.ts.map +1 -0
  260. package/dist/pairing/dto/confirm-pairing.dto.js +24 -0
  261. package/dist/pairing/dto/confirm-pairing.dto.js.map +1 -0
  262. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts +5 -0
  263. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts.map +1 -0
  264. package/dist/pairing/dto/poll-reverse-pairing.dto.js +24 -0
  265. package/dist/pairing/dto/poll-reverse-pairing.dto.js.map +1 -0
  266. package/dist/pairing/dto/start-pairing.dto.d.ts +11 -0
  267. package/dist/pairing/dto/start-pairing.dto.d.ts.map +1 -0
  268. package/dist/pairing/dto/start-pairing.dto.js +30 -0
  269. package/dist/pairing/dto/start-pairing.dto.js.map +1 -0
  270. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts +24 -0
  271. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts.map +1 -0
  272. package/dist/pairing/dto/start-reverse-pairing.dto.js +47 -0
  273. package/dist/pairing/dto/start-reverse-pairing.dto.js.map +1 -0
  274. package/dist/pairing/pairing.controller.d.ts +118 -0
  275. package/dist/pairing/pairing.controller.d.ts.map +1 -0
  276. package/dist/pairing/pairing.controller.js +296 -0
  277. package/dist/pairing/pairing.controller.js.map +1 -0
  278. package/dist/pairing/pairing.module.d.ts +10 -0
  279. package/dist/pairing/pairing.module.d.ts.map +1 -0
  280. package/dist/pairing/pairing.module.js +40 -0
  281. package/dist/pairing/pairing.module.js.map +1 -0
  282. package/dist/pairing/pairing.service.d.ts +238 -0
  283. package/dist/pairing/pairing.service.d.ts.map +1 -0
  284. package/dist/pairing/pairing.service.js +495 -0
  285. package/dist/pairing/pairing.service.js.map +1 -0
  286. package/dist/pdf/pdf.service.d.ts +3 -6
  287. package/dist/pdf/pdf.service.d.ts.map +1 -1
  288. package/dist/pdf/pdf.service.js +8 -20
  289. package/dist/pdf/pdf.service.js.map +1 -1
  290. package/dist/profile/profile.controller.d.ts +11 -0
  291. package/dist/profile/profile.controller.d.ts.map +1 -1
  292. package/dist/profile/profile.service.d.ts +11 -0
  293. package/dist/profile/profile.service.d.ts.map +1 -1
  294. package/dist/profile/profile.service.js +20 -11
  295. package/dist/profile/profile.service.js.map +1 -1
  296. package/dist/redis/redis-pubsub.d.ts.map +1 -1
  297. package/dist/redis/redis-pubsub.js +44 -0
  298. package/dist/redis/redis-pubsub.js.map +1 -1
  299. package/dist/role/guards/role.guard.d.ts +5 -0
  300. package/dist/role/guards/role.guard.d.ts.map +1 -1
  301. package/dist/role/guards/role.guard.js +41 -39
  302. package/dist/role/guards/role.guard.js.map +1 -1
  303. package/dist/session/session.controller.d.ts +2 -0
  304. package/dist/session/session.controller.d.ts.map +1 -1
  305. package/dist/session/session.service.d.ts +39 -0
  306. package/dist/session/session.service.d.ts.map +1 -1
  307. package/dist/session/session.service.js +91 -8
  308. package/dist/session/session.service.js.map +1 -1
  309. package/dist/setting/brand-asset.service.d.ts +48 -0
  310. package/dist/setting/brand-asset.service.d.ts.map +1 -0
  311. package/dist/setting/brand-asset.service.js +130 -0
  312. package/dist/setting/brand-asset.service.js.map +1 -0
  313. package/dist/setting/setting.module.d.ts.map +1 -1
  314. package/dist/setting/setting.module.js +3 -2
  315. package/dist/setting/setting.module.js.map +1 -1
  316. package/dist/setting/setting.service.d.ts +33 -0
  317. package/dist/setting/setting.service.d.ts.map +1 -1
  318. package/dist/setting/setting.service.js +88 -0
  319. package/dist/setting/setting.service.js.map +1 -1
  320. package/dist/token/token.service.d.ts +23 -2
  321. package/dist/token/token.service.d.ts.map +1 -1
  322. package/dist/token/token.service.js +39 -4
  323. package/dist/token/token.service.js.map +1 -1
  324. package/dist/user/user.controller.d.ts +8 -22
  325. package/dist/user/user.controller.d.ts.map +1 -1
  326. package/dist/user/user.service.d.ts +44 -24
  327. package/dist/user/user.service.d.ts.map +1 -1
  328. package/dist/user/user.service.js +89 -6
  329. package/dist/user/user.service.js.map +1 -1
  330. package/dist/user-device/user-device.controller.d.ts +54 -0
  331. package/dist/user-device/user-device.controller.d.ts.map +1 -0
  332. package/dist/user-device/user-device.controller.js +103 -0
  333. package/dist/user-device/user-device.controller.js.map +1 -0
  334. package/dist/user-device/user-device.module.d.ts +3 -0
  335. package/dist/user-device/user-device.module.d.ts.map +1 -0
  336. package/dist/user-device/user-device.module.js +26 -0
  337. package/dist/user-device/user-device.module.js.map +1 -0
  338. package/dist/user-device/user-device.service.d.ts +71 -0
  339. package/dist/user-device/user-device.service.d.ts.map +1 -0
  340. package/dist/user-device/user-device.service.js +158 -0
  341. package/dist/user-device/user-device.service.js.map +1 -0
  342. package/dist/utils/device-key.d.ts +23 -0
  343. package/dist/utils/device-key.d.ts.map +1 -0
  344. package/dist/utils/device-key.js +31 -0
  345. package/dist/utils/device-key.js.map +1 -0
  346. package/dist/utils/geo-ip.d.ts +38 -0
  347. package/dist/utils/geo-ip.d.ts.map +1 -0
  348. package/dist/utils/geo-ip.js +122 -0
  349. package/dist/utils/geo-ip.js.map +1 -0
  350. package/dist/utils/locale-context.d.ts +10 -7
  351. package/dist/utils/locale-context.d.ts.map +1 -1
  352. package/dist/utils/locale-context.js +14 -8
  353. package/dist/utils/locale-context.js.map +1 -1
  354. package/dist/utils/security-event-vars.d.ts +56 -0
  355. package/dist/utils/security-event-vars.d.ts.map +1 -0
  356. package/dist/utils/security-event-vars.js +109 -0
  357. package/dist/utils/security-event-vars.js.map +1 -0
  358. package/dist/utils/user-agent.d.ts +19 -0
  359. package/dist/utils/user-agent.d.ts.map +1 -0
  360. package/dist/utils/user-agent.js +36 -0
  361. package/dist/utils/user-agent.js.map +1 -0
  362. package/dist/webhook-integration/webhook-integration.service.d.ts +5 -0
  363. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  364. package/dist/webhook-integration/webhook-integration.service.js +50 -4
  365. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  366. package/dist/webhook-integration/webhook-protocol.registry.d.ts +6 -0
  367. package/dist/webhook-integration/webhook-protocol.registry.d.ts.map +1 -1
  368. package/dist/webhook-integration/webhook-protocol.registry.js.map +1 -1
  369. package/hedhog/data/integration_provider.yaml +9 -0
  370. package/hedhog/data/mail.yaml +198 -62
  371. package/hedhog/data/mail_layout.yaml +43 -33
  372. package/hedhog/data/menu.yaml +2 -0
  373. package/hedhog/data/role.yaml +16 -0
  374. package/hedhog/data/route.yaml +172 -3
  375. package/hedhog/data/setting_group.yaml +178 -2
  376. package/hedhog/data/setting_subgroup.yaml +8 -0
  377. package/hedhog/frontend/app/_lib/hooks/use-core-access.test.tsx.ejs +125 -0
  378. package/hedhog/frontend/app/_lib/hooks/use-core-access.ts.ejs +29 -0
  379. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.test.ts.ejs +71 -0
  380. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.ts.ejs +32 -0
  381. package/hedhog/frontend/app/access-log/page.tsx.ejs +96 -22
  382. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +17 -1
  383. package/hedhog/frontend/app/account/components/known-devices.tsx.ejs +89 -0
  384. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +2 -1
  385. package/hedhog/frontend/app/account/components/user-devices-list.tsx.ejs +186 -0
  386. package/hedhog/frontend/app/account/devices/page.tsx.ejs +5 -0
  387. package/hedhog/frontend/app/account/layout.tsx.ejs +2 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +298 -0
  389. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +376 -1236
  390. package/hedhog/frontend/app/mail/log/page.tsx.ejs +210 -3
  391. package/hedhog/frontend/app/mail/template/page.tsx.ejs +190 -132
  392. package/hedhog/frontend/app/users/active-session.tsx.ejs +58 -11
  393. package/hedhog/frontend/app/users/page.tsx.ejs +69 -10
  394. package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +202 -0
  395. package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +170 -0
  396. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +19 -0
  397. package/hedhog/frontend/app/users/user-devices.tsx.ejs +89 -0
  398. package/hedhog/frontend/app/users/user-logs.tsx.ejs +13 -1
  399. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +46 -24
  400. package/hedhog/frontend/messages/en.json +146 -48
  401. package/hedhog/frontend/messages/pt.json +146 -48
  402. package/hedhog/table/access_log.yaml +7 -0
  403. package/hedhog/table/file.yaml +8 -0
  404. package/hedhog/table/mail_delivery_event.yaml +76 -0
  405. package/hedhog/table/mail_sent.yaml +23 -0
  406. package/hedhog/table/user_activity.yaml +9 -0
  407. package/hedhog/table/user_device.yaml +52 -0
  408. package/hedhog/table/user_session.yaml +20 -1
  409. package/package.json +6 -5
  410. package/src/access-log/access-log.controller.ts +1 -0
  411. package/src/access-log/access-log.interceptor.ts +5 -1
  412. package/src/access-log/access-log.service.spec.ts +33 -2
  413. package/src/access-log/access-log.service.ts +38 -0
  414. package/src/access-log/dto/query-access-log.dto.ts +5 -0
  415. package/src/ai/ai.controller.ts +9 -0
  416. package/src/ai/ai.module.ts +3 -2
  417. package/src/ai/ai.service.ts +150 -13
  418. package/src/ai/dto/richtext-edit-html.dto.ts +11 -0
  419. package/src/ai/richtext-ai.prompts.ts +57 -0
  420. package/src/ai/richtext-ai.service.ts +109 -0
  421. package/src/auth/auth.controller.ts +47 -8
  422. package/src/auth/auth.module.ts +2 -0
  423. package/src/auth/auth.service.spec.ts +383 -6
  424. package/src/auth/auth.service.ts +345 -56
  425. package/src/auth/dto/confirm-email.dto.ts +7 -0
  426. package/src/auth/dto/resend-confirmation.dto.ts +20 -0
  427. package/src/authorize/authorize.controller.ts +2 -0
  428. package/src/authorize/authorize.service.spec.ts +23 -0
  429. package/src/authorize/authorize.service.ts +8 -0
  430. package/src/challenge/challenge.service.spec.ts +73 -0
  431. package/src/challenge/challenge.service.ts +62 -3
  432. package/src/core.module.ts +19 -0
  433. package/src/file/file.controller.ts +54 -4
  434. package/src/file/file.service.spec.ts +275 -105
  435. package/src/file/file.service.ts +409 -14
  436. package/src/file/image-optimization.service.spec.ts +92 -0
  437. package/src/file/image-optimization.service.ts +202 -0
  438. package/src/file/provider/abstract.provider.ts +23 -0
  439. package/src/file/provider/gcs.provider.ts +17 -0
  440. package/src/file/provider/local.provider.ts +21 -0
  441. package/src/file/provider/s3.provider.ts +25 -0
  442. package/src/file/provider/spaces.provider.ts +26 -0
  443. package/src/impersonation/dto/redeem-impersonation.dto.ts +8 -0
  444. package/src/impersonation/dto/start-impersonation.dto.ts +13 -0
  445. package/src/impersonation/impersonation-restrictions.ts +84 -0
  446. package/src/impersonation/impersonation.controller.ts +109 -0
  447. package/src/impersonation/impersonation.module.ts +31 -0
  448. package/src/impersonation/impersonation.service.spec.ts +427 -0
  449. package/src/impersonation/impersonation.service.ts +577 -0
  450. package/src/index.ts +21 -0
  451. package/src/integration/services/outbox-polling.coordinator.spec.ts +90 -0
  452. package/src/integration/services/outbox-polling.coordinator.ts +62 -40
  453. package/src/integration/services/outbox.service.spec.ts +8 -0
  454. package/src/integration/services/outbox.service.ts +19 -13
  455. package/src/integration-profile/integration-profile.secrets.ts +3 -0
  456. package/src/integration-profile/integration-profile.utils.ts +38 -0
  457. package/src/integration-profile/testers/email.testers.ts +34 -1
  458. package/src/language/en.json +11 -0
  459. package/src/language/pt.json +11 -0
  460. package/src/mail/dto/send.dto.ts +46 -2
  461. package/src/mail/mail-template.spec.ts +79 -2
  462. package/src/mail/mail.module.ts +2 -0
  463. package/src/mail/mail.service.spec.ts +307 -6
  464. package/src/mail/mail.service.ts +227 -30
  465. package/src/mail-delivery/mail-delivery-provision.service.ts +66 -0
  466. package/src/mail-delivery/mail-delivery.constants.ts +31 -0
  467. package/src/mail-delivery/mail-delivery.controller.ts +38 -0
  468. package/src/mail-delivery/mail-delivery.module.ts +23 -0
  469. package/src/mail-delivery/mail-delivery.service.spec.ts +136 -0
  470. package/src/mail-delivery/mail-delivery.service.ts +152 -0
  471. package/src/mail-delivery/ses-event.parser.spec.ts +140 -0
  472. package/src/mail-delivery/ses-event.parser.ts +191 -0
  473. package/src/mail-delivery/ses-webhook.service.spec.ts +170 -0
  474. package/src/mail-delivery/ses-webhook.service.ts +240 -0
  475. package/src/mail-delivery/sns-message.util.spec.ts +163 -0
  476. package/src/mail-delivery/sns-message.util.ts +125 -0
  477. package/src/mail-sent/dto/query-mail-sent-list.dto.ts +15 -0
  478. package/src/mail-sent/mail-sent.service.ts +46 -2
  479. package/src/mailbox/mailbox-fetcher.registry.ts +132 -0
  480. package/src/mailbox/mailbox.module.ts +14 -0
  481. package/src/meeting/meeting-provider.registry.ts +193 -0
  482. package/src/meeting/meeting.constants.ts +53 -0
  483. package/src/meeting/meeting.module.ts +13 -0
  484. package/src/middlewares/locale-injection.middleware.ts +25 -6
  485. package/src/oauth/providers/google.provider.ts +49 -15
  486. package/src/observability/monitored-cron.spec.ts +102 -0
  487. package/src/observability/monitored-cron.ts +110 -0
  488. package/src/pairing/dto/approve-reverse-pairing.dto.ts +20 -0
  489. package/src/pairing/dto/claim-pairing.dto.ts +19 -0
  490. package/src/pairing/dto/confirm-pairing.dto.ts +14 -0
  491. package/src/pairing/dto/poll-reverse-pairing.dto.ts +9 -0
  492. package/src/pairing/dto/start-pairing.dto.ts +19 -0
  493. package/src/pairing/dto/start-reverse-pairing.dto.ts +45 -0
  494. package/src/pairing/pairing.controller.ts +246 -0
  495. package/src/pairing/pairing.module.ts +27 -0
  496. package/src/pairing/pairing.service.spec.ts +462 -0
  497. package/src/pairing/pairing.service.ts +614 -0
  498. package/src/pdf/pdf.service.ts +6 -24
  499. package/src/profile/profile.service.ts +26 -8
  500. package/src/redis/redis-pubsub.ts +11 -0
  501. package/src/role/guards/role.guard.spec.ts +75 -0
  502. package/src/role/guards/role.guard.ts +64 -46
  503. package/src/session/session.service.spec.ts +81 -0
  504. package/src/session/session.service.ts +93 -6
  505. package/src/setting/brand-asset.service.ts +137 -0
  506. package/src/setting/setting.module.ts +3 -2
  507. package/src/setting/setting.service.spec.ts +79 -1
  508. package/src/setting/setting.service.ts +109 -1
  509. package/src/token/token.service.spec.ts +62 -0
  510. package/src/token/token.service.ts +57 -3
  511. package/src/user/user.service.spec.ts +33 -2
  512. package/src/user/user.service.ts +108 -6
  513. package/src/user-device/user-device.controller.ts +77 -0
  514. package/src/user-device/user-device.module.ts +13 -0
  515. package/src/user-device/user-device.service.spec.ts +234 -0
  516. package/src/user-device/user-device.service.ts +168 -0
  517. package/src/utils/device-key.spec.ts +57 -0
  518. package/src/utils/device-key.ts +32 -0
  519. package/src/utils/geo-ip.spec.ts +134 -0
  520. package/src/utils/geo-ip.ts +93 -0
  521. package/src/utils/locale-context.spec.ts +27 -1
  522. package/src/utils/locale-context.ts +15 -12
  523. package/src/utils/security-event-vars.spec.ts +161 -0
  524. package/src/utils/security-event-vars.ts +153 -0
  525. package/src/utils/user-agent.spec.ts +37 -0
  526. package/src/utils/user-agent.ts +46 -0
  527. package/src/webhook-integration/webhook-integration.service.ts +57 -3
  528. package/src/webhook-integration/webhook-protocol.registry.ts +6 -0
@@ -4,6 +4,7 @@ import { PrismaModule } from '@hed-hog/api-prisma';
4
4
  import { Global, Module, forwardRef } from '@nestjs/common';
5
5
  import { AuthModule } from '../auth/auth.module';
6
6
  import { MailModule } from '../mail/mail.module';
7
+ import { BrandAssetService } from './brand-asset.service';
7
8
  import { SettingsController } from './setting.controller';
8
9
  import { SettingService } from './setting.service';
9
10
 
@@ -17,7 +18,7 @@ import { SettingService } from './setting.service';
17
18
  forwardRef(() => MailModule),
18
19
  ],
19
20
  controllers: [SettingsController],
20
- providers: [SettingService],
21
- exports: [SettingService],
21
+ providers: [SettingService, BrandAssetService],
22
+ exports: [SettingService, BrandAssetService],
22
23
  })
23
24
  export class SettingModule { }
@@ -7,6 +7,7 @@ import {
7
7
  jest,
8
8
  } from '@jest/globals';
9
9
  import { BadRequestException } from '@nestjs/common';
10
+ import { localeStorage } from '@hed-hog/api-locale';
10
11
  import { SettingService } from './setting.service';
11
12
 
12
13
  const makeDeps = () => {
@@ -16,7 +17,7 @@ const makeDeps = () => {
16
17
  findFirst: jest.fn(),
17
18
  findUnique: jest.fn(),
18
19
  },
19
- setting_user: { findMany: jest.fn() },
20
+ setting_user: { findMany: jest.fn(), findFirst: jest.fn() },
20
21
  user: { findUnique: jest.fn() },
21
22
  $executeRawUnsafe: jest.fn().mockResolvedValue(undefined),
22
23
  $queryRawUnsafe: jest.fn().mockResolvedValue([]),
@@ -369,4 +370,81 @@ describe('SettingService', () => {
369
370
  ).resolves.toBeUndefined();
370
371
  });
371
372
  });
373
+
374
+ // Cadeia única de idioma do sistema: e-mail transacional e aviso de idioma de
375
+ // curso precisam responder a mesma pergunta do mesmo jeito.
376
+ describe('resolveUserLocale', () => {
377
+ const arrange = (deps: any, opts: { override?: string | null; system?: string } = {}) => {
378
+ deps.prisma.setting_user.findFirst.mockResolvedValue(
379
+ opts.override ? { value: opts.override } : null,
380
+ );
381
+ deps.prisma.setting.findMany.mockResolvedValue([
382
+ { id: 1, slug: 'language', value: opts.system ?? 'en', type: 'string', user_override: true },
383
+ ]);
384
+ };
385
+
386
+ it('a preferência do usuário vence tudo', async () => {
387
+ const { service, deps } = makeService();
388
+ arrange(deps, { override: 'pt', system: 'en' });
389
+
390
+ const result = await localeStorage.run({ locale: 'en', detected: true }, () =>
391
+ service.resolveUserLocale(42),
392
+ );
393
+
394
+ expect(result).toBe('pt');
395
+ });
396
+
397
+ it('normaliza a preferência gravada com região', async () => {
398
+ const { service, deps } = makeService();
399
+ arrange(deps, { override: 'pt-BR' });
400
+
401
+ await expect(service.resolveUserLocale(42)).resolves.toBe('pt');
402
+ });
403
+
404
+ it('sem preferência, usa o idioma que o cliente declarou', async () => {
405
+ const { service, deps } = makeService();
406
+ arrange(deps, { system: 'en' });
407
+
408
+ const result = await localeStorage.run({ locale: 'pt', detected: true }, () =>
409
+ service.resolveUserLocale(42),
410
+ );
411
+
412
+ expect(result).toBe('pt');
413
+ });
414
+
415
+ // O `en` default do middleware não pode passar por escolha do cliente e
416
+ // atropelar a setting do sistema.
417
+ it('ignora o locale do contexto quando o cliente não declarou idioma', async () => {
418
+ const { service, deps } = makeService();
419
+ arrange(deps, { system: 'pt' });
420
+
421
+ const result = await localeStorage.run({ locale: 'en', detected: false }, () =>
422
+ service.resolveUserLocale(42),
423
+ );
424
+
425
+ expect(result).toBe('pt');
426
+ });
427
+
428
+ it('cai na setting do sistema fora de qualquer requisição', async () => {
429
+ const { service, deps } = makeService();
430
+ arrange(deps, { system: 'pt' });
431
+
432
+ await expect(service.resolveUserLocale(42)).resolves.toBe('pt');
433
+ });
434
+
435
+ it('funciona sem usuário (destinatário que não tem conta)', async () => {
436
+ const { service, deps } = makeService();
437
+ arrange(deps, { system: 'pt' });
438
+
439
+ await expect(service.resolveUserLocale(null)).resolves.toBe('pt');
440
+ expect(deps.prisma.setting_user.findFirst).not.toHaveBeenCalled();
441
+ });
442
+
443
+ it('cai em "en" quando a consulta falha', async () => {
444
+ const { service, deps } = makeService();
445
+ deps.prisma.setting_user.findFirst.mockRejectedValue(new Error('db down'));
446
+
447
+ await expect(service.resolveUserLocale(42)).resolves.toBe('en');
448
+ });
449
+ });
372
450
  });
@@ -1,5 +1,11 @@
1
1
  import { itemTranslations } from '@hed-hog/api';
2
- import { getLocaleText, LocaleService } from '@hed-hog/api-locale';
2
+ import {
3
+ getLocaleFromContext,
4
+ getLocaleText,
5
+ isLocaleDetectedFromContext,
6
+ LocaleService,
7
+ normalizeLocaleCode,
8
+ } from '@hed-hog/api-locale';
3
9
  import { PaginationDTO, PaginationService } from '@hed-hog/api-pagination';
4
10
  import { PrismaService, setting_type_0f055cd5ea_enum } from '@hed-hog/api-prisma';
5
11
  import {
@@ -499,6 +505,8 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
499
505
  'password-block-leaked',
500
506
  'mfa-email-code-length',
501
507
  'mcp-enabled',
508
+ 'richtext-ai-enabled',
509
+ 'richtext-ai-profile-id',
502
510
  'date-format',
503
511
  'time-format',
504
512
  'timezone',
@@ -1303,6 +1311,106 @@ export class SettingService implements OnModuleInit, OnModuleDestroy {
1303
1311
  });
1304
1312
  }
1305
1313
 
1314
+ /**
1315
+ * Le SO o override do usuario, sem cair para o valor global.
1316
+ *
1317
+ * `getSettingValues(slug, userId)` nao serve para isso: quando o usuario nao
1318
+ * tem override ele devolve o valor global, indistinguivel de uma preferencia
1319
+ * real. Quem precisa saber se o usuario *escolheu* algo - o idioma do e-mail
1320
+ * e a gravacao unica no login - depende dessa diferenca.
1321
+ */
1322
+ async getUserSettingValue(
1323
+ userId: number,
1324
+ slug: string,
1325
+ ): Promise<string | null> {
1326
+ if (!userId) {
1327
+ return null;
1328
+ }
1329
+
1330
+ const settingUser = await this.prismaService.setting_user.findFirst({
1331
+ where: {
1332
+ user_id: userId,
1333
+ setting: {
1334
+ slug,
1335
+ user_override: true,
1336
+ },
1337
+ },
1338
+ select: {
1339
+ value: true,
1340
+ },
1341
+ });
1342
+
1343
+ const value = String(settingUser?.value ?? '').trim();
1344
+
1345
+ return value || null;
1346
+ }
1347
+
1348
+ /**
1349
+ * Idioma efetivo de um usuario.
1350
+ *
1351
+ * Cadeia unica do sistema, para que e-mail, aviso de idioma de curso e
1352
+ * qualquer outra decisao de idioma respondam a mesma pergunta do mesmo jeito:
1353
+ *
1354
+ * 1. preferencia gravada do usuario - escolha explicita sempre vence;
1355
+ * 2. idioma que o cliente declarou nesta requisicao;
1356
+ * 3. setting global `language`;
1357
+ * 4. `en`.
1358
+ *
1359
+ * O passo 2 exige que o cliente tenha REALMENTE declarado: o `en` que o
1360
+ * middleware usa como default nao pode vencer a setting do sistema.
1361
+ */
1362
+ async resolveUserLocale(userId?: number | null): Promise<string> {
1363
+ try {
1364
+ if (userId) {
1365
+ const preferred = normalizeLocaleCode(
1366
+ await this.getUserSettingValue(userId, 'language'),
1367
+ );
1368
+
1369
+ if (preferred) {
1370
+ return preferred;
1371
+ }
1372
+ }
1373
+
1374
+ const fromRequest = normalizeLocaleCode(getLocaleFromContext());
1375
+
1376
+ if (fromRequest && isLocaleDetectedFromContext()) {
1377
+ return fromRequest;
1378
+ }
1379
+
1380
+ const settings = await this.getSettingValues(['language']);
1381
+
1382
+ return normalizeLocaleCode(settings?.['language']) ?? fromRequest ?? 'en';
1383
+ } catch (error) {
1384
+ this.logger.warn(`Could not resolve locale for user ${userId}: ${error}`);
1385
+
1386
+ return normalizeLocaleCode(getLocaleFromContext()) ?? 'en';
1387
+ }
1388
+ }
1389
+
1390
+ /**
1391
+ * Valores aceitos por uma setting de combobox (`setting_list`).
1392
+ *
1393
+ * `setSettingUserValue` valida que a setting existe e permite override, mas
1394
+ * nao valida o *valor*. Sem esta checagem, um navegador em `de-DE` gravaria
1395
+ * `de` como idioma e todo e-mail seguinte procuraria um template que nao
1396
+ * existe.
1397
+ */
1398
+ async getSettingListValues(slug: string): Promise<string[]> {
1399
+ const setting = await this.prismaService.setting.findFirst({
1400
+ where: { slug },
1401
+ select: {
1402
+ setting_list: {
1403
+ select: { value: true },
1404
+ orderBy: { order: 'asc' },
1405
+ },
1406
+ },
1407
+ });
1408
+
1409
+ return (setting?.setting_list ?? [])
1410
+ .map((item) => String(item?.value ?? '').trim())
1411
+ .filter(Boolean);
1412
+ }
1413
+
1306
1414
  async getSettingValues(
1307
1415
  slug: string | string[],
1308
1416
  userId?: number,
@@ -7,6 +7,7 @@ import {
7
7
  jest,
8
8
  } from '@jest/globals';
9
9
  import {
10
+ BadRequestException,
10
11
  ForbiddenException,
11
12
  UnauthorizedException,
12
13
  } from '@nestjs/common';
@@ -279,6 +280,21 @@ describe('TokenService', () => {
279
280
  );
280
281
  });
281
282
 
283
+ it('createAccessToken repassa expiresIn por chamada (sessão simulada)', async () => {
284
+ const { service, deps } = makeService();
285
+ deps.jwt.signAsync.mockResolvedValue('signed');
286
+
287
+ await service.createAccessToken(
288
+ { sub: 42, sessionId: 555, act: 7, imp: true },
289
+ { expiresIn: 1800 },
290
+ );
291
+
292
+ expect(deps.jwt.signAsync).toHaveBeenCalledWith(
293
+ { sub: 42, sessionId: 555, act: 7, imp: true },
294
+ { secret: 'secret', expiresIn: 1800 },
295
+ );
296
+ });
297
+
282
298
  it('createOpaqueToken delega ao security', async () => {
283
299
  const { service, deps } = makeService();
284
300
  await expect(service.createOpaqueToken(32)).resolves.toBe('opaque-token');
@@ -328,6 +344,52 @@ describe('TokenService', () => {
328
344
  );
329
345
  });
330
346
 
347
+ it('createEmailConfirmationToken assina com propósito e validade de 7d', async () => {
348
+ const { service, deps } = makeService();
349
+ deps.jwt.signAsync.mockResolvedValue('confirm-tok');
350
+
351
+ await expect(
352
+ service.createEmailConfirmationToken({ userIdentifierId: 42 }),
353
+ ).resolves.toBe('confirm-tok');
354
+ expect(deps.jwt.signAsync).toHaveBeenCalledWith(
355
+ { userIdentifierId: 42, purpose: 'email-confirmation' },
356
+ expect.objectContaining({ secret: 'secret', expiresIn: '7d' }),
357
+ );
358
+ });
359
+
360
+ it('verifyEmailConfirmationToken retorna o identificador do payload', async () => {
361
+ const { service, deps } = makeService();
362
+ deps.jwt.verifyAsync.mockResolvedValue({
363
+ userIdentifierId: 42,
364
+ purpose: 'email-confirmation',
365
+ });
366
+
367
+ await expect(
368
+ service.verifyEmailConfirmationToken('pt-BR', 'tok'),
369
+ ).resolves.toEqual({ userIdentifierId: 42 });
370
+ });
371
+
372
+ // Sem esta checagem, um token de MFA ou de sessão viraria confirmação de
373
+ // e-mail — e, no sentido inverso, o token do link entraria em fluxos que
374
+ // autenticam.
375
+ it('verifyEmailConfirmationToken recusa token de outro propósito', async () => {
376
+ const { service, deps } = makeService();
377
+ deps.jwt.verifyAsync.mockResolvedValue({ userIdentifierId: 42 });
378
+
379
+ await expect(
380
+ service.verifyEmailConfirmationToken('pt-BR', 'tok'),
381
+ ).rejects.toThrow(BadRequestException);
382
+ });
383
+
384
+ it('verifyEmailConfirmationToken lança BadRequest em token inválido', async () => {
385
+ const { service, deps } = makeService();
386
+ deps.jwt.verifyAsync.mockRejectedValue(new Error('bad'));
387
+
388
+ await expect(
389
+ service.verifyEmailConfirmationToken('pt-BR', 'tok'),
390
+ ).rejects.toThrow(BadRequestException);
391
+ });
392
+
331
393
  it('decodeExpiredToken ignora expiração', async () => {
332
394
  const { service, deps } = makeService();
333
395
  deps.jwt.verifyAsync.mockResolvedValue({ sub: 7 });
@@ -1,10 +1,13 @@
1
1
  import { getLocaleText } from "@hed-hog/api-locale";
2
2
  import { PrismaService } from "@hed-hog/api-prisma";
3
- import { ForbiddenException, forwardRef, Inject, Injectable, Logger, UnauthorizedException } from "@nestjs/common";
4
- import { JwtService } from "@nestjs/jwt";
3
+ import { BadRequestException, ForbiddenException, forwardRef, Inject, Injectable, Logger, UnauthorizedException } from "@nestjs/common";
4
+ import { JwtService, JwtSignOptions } from "@nestjs/jwt";
5
5
  import { SecurityService } from "../security/security.service";
6
6
  import { SettingService } from "../setting/setting.service";
7
7
 
8
+ /** Claim que separa o token do link de confirmação de e-mail dos demais JWTs. */
9
+ const EMAIL_CONFIRMATION_PURPOSE = 'email-confirmation';
10
+
8
11
  @Injectable()
9
12
  export class TokenService {
10
13
  private readonly logger = new Logger(TokenService.name);
@@ -79,9 +82,16 @@ export class TokenService {
79
82
  }
80
83
  }
81
84
 
82
- async createAccessToken(payload: Record<string, any>) {
85
+ /**
86
+ * `options` permite sobrescrever o `expiresIn` global do JwtModule numa
87
+ * chamada especifica — usado pela simulacao de acesso, cujo token precisa
88
+ * morrer junto com a sessao (que nao e renovavel) e nao aos 15 minutos
89
+ * padrao.
90
+ */
91
+ async createAccessToken(payload: Record<string, any>, options?: JwtSignOptions) {
83
92
  return this.jwt.signAsync(payload, {
84
93
  secret: this.security.getJwtSecret(),
94
+ ...(options ?? {}),
85
95
  });
86
96
  }
87
97
 
@@ -178,6 +188,50 @@ export class TokenService {
178
188
  }
179
189
  }
180
190
 
191
+ /**
192
+ * Token do link de confirmação de e-mail enviado no cadastro. É um JWT, e não
193
+ * um token opaco em `user_identifier_challenge` como o do reset de senha, por
194
+ * dois motivos: `completeAccount`/`authorizeAccountInvite` aceitam qualquer
195
+ * linha válida daquela tabela, então um token gravado ali transformaria o
196
+ * e-mail de boas-vindas num link de login de 7 dias — o claim `purpose` fecha
197
+ * essa porta; e como nada é consumido, clicar duas vezes (pré-fetch do
198
+ * cliente de e-mail, encaminhamento) continua funcionando.
199
+ */
200
+ async createEmailConfirmationToken(payload: { userIdentifierId: number }) {
201
+ return this.jwt.signAsync(
202
+ { ...payload, purpose: EMAIL_CONFIRMATION_PURPOSE },
203
+ {
204
+ secret: this.security.getJwtSecret(),
205
+ expiresIn: '7d',
206
+ },
207
+ );
208
+ }
209
+
210
+ async verifyEmailConfirmationToken(
211
+ locale: string,
212
+ token: string,
213
+ ): Promise<{ userIdentifierId: number }> {
214
+ try {
215
+ const payload = await this.jwt.verifyAsync(token, {
216
+ secret: this.security.getJwtSecret(),
217
+ });
218
+
219
+ if (payload.purpose !== EMAIL_CONFIRMATION_PURPOSE) {
220
+ throw new Error('Token purpose mismatch');
221
+ }
222
+
223
+ return { userIdentifierId: payload.userIdentifierId };
224
+ } catch (error) {
225
+ throw new BadRequestException(
226
+ getLocaleText(
227
+ 'confirmEmail.invalidOrExpired',
228
+ locale,
229
+ 'This confirmation link is invalid or has expired.',
230
+ ),
231
+ );
232
+ }
233
+ }
234
+
181
235
  async decodeExpiredToken(token: string): Promise<any> {
182
236
  try {
183
237
  return await this.jwt.verifyAsync(token, {
@@ -23,7 +23,7 @@ const makeDeps = () => {
23
23
  };
24
24
  const paginationService = { paginatePrismaModel: jest.fn() };
25
25
  const security = { hashArgon2: jest.fn().mockResolvedValue('hashed') };
26
- const challenge = { verifyEmail: jest.fn() };
26
+ const challenge = { sendEmailConfirmationLink: jest.fn() };
27
27
  const file = { upload: jest.fn(), delete: jest.fn(), getBuffer: jest.fn() };
28
28
  const integrationApi = { publishEvent: jest.fn().mockResolvedValue(undefined) };
29
29
  const settingService = { getSettingValues: jest.fn().mockResolvedValue({}) };
@@ -69,7 +69,16 @@ describe('UserService', () => {
69
69
  deps.prismaService.user_activity.create.mockResolvedValue({ id: 1 });
70
70
  await service.registerUserActivity(5, 'login');
71
71
  expect(deps.prismaService.user_activity.create).toHaveBeenCalledWith({
72
- data: { user_id: 5, action: 'login' },
72
+ data: { user_id: 5, action: 'login', actor_user_id: null },
73
+ });
74
+ });
75
+
76
+ it('grava o ator quem provocou a atividade (simulação de acesso)', async () => {
77
+ const { service, deps } = makeService();
78
+ deps.prismaService.user_activity.create.mockResolvedValue({ id: 1 });
79
+ await service.registerUserActivity(5, 'impersonationStarted', 9);
80
+ expect(deps.prismaService.user_activity.create).toHaveBeenCalledWith({
81
+ data: { user_id: 5, action: 'impersonationStarted', actor_user_id: 9 },
73
82
  });
74
83
  });
75
84
  });
@@ -403,7 +412,29 @@ describe('UserService', () => {
403
412
  id: '7',
404
413
  action: 'login',
405
414
  createdAt: '2026-01-01T00:00:00.000Z',
415
+ actor: null,
416
+ });
417
+ });
418
+
419
+ it('resolve o nome do ator numa atividade de simulação de acesso', async () => {
420
+ const { service, deps } = makeService();
421
+ deps.prismaService.user.findUnique.mockResolvedValue({ id: 1 });
422
+ deps.paginationService.paginatePrismaModel.mockResolvedValue({
423
+ data: [
424
+ {
425
+ id: 8,
426
+ action: 'impersonationStarted',
427
+ actor_user_id: 42,
428
+ created_at: new Date('2026-01-01T00:00:00Z'),
429
+ },
430
+ ],
431
+ total: 1,
406
432
  });
433
+ deps.prismaService.user.findMany.mockResolvedValue([{ id: 42, name: 'Operadora' }]);
434
+
435
+ const result = await service.listUserActivities('en', 1, {});
436
+
437
+ expect(result.data[0].actor).toEqual({ id: 42, name: 'Operadora' });
407
438
  });
408
439
  });
409
440
  });
@@ -186,7 +186,47 @@ export class UserService {
186
186
  }
187
187
 
188
188
  async get(locale: string, userId: number) {
189
- return this.findUserById(locale, userId);
189
+ const user = await this.findUserById(locale, userId);
190
+
191
+ // Enriquecimento so aqui, e nao em `findUserById`: aquele metodo tambem
192
+ // serve `GET /auth/verify`, que roda a cada carga de pagina. Quem precisa
193
+ // dos nomes de operador/ator e a ficha do usuario no admin.
194
+ const [user_activity, user_session] = await Promise.all([
195
+ this.attachActivityActors((user as any).user_activity ?? []),
196
+ this.attachSessionImpersonators((user as any).user_session ?? []),
197
+ ]);
198
+
199
+ return { ...user, user_activity, user_session };
200
+ }
201
+
202
+ /** Ver `attachActivityActors`: mesma razao, aplicada a `user_session`. */
203
+ private async attachSessionImpersonators(sessions: any[]) {
204
+ const ids = [
205
+ ...new Set(
206
+ sessions
207
+ .map((session) => session.impersonator_user_id)
208
+ .filter((id): id is number => typeof id === 'number'),
209
+ ),
210
+ ];
211
+
212
+ const users = ids.length
213
+ ? await this.prismaService.user.findMany({
214
+ where: { id: { in: ids } },
215
+ select: { id: true, name: true },
216
+ })
217
+ : [];
218
+ const byId = new Map(users.map((user) => [user.id, user]));
219
+
220
+ return sessions.map((session: any) => ({
221
+ ...session,
222
+ impersonator:
223
+ typeof session.impersonator_user_id === 'number'
224
+ ? (byId.get(session.impersonator_user_id) ?? {
225
+ id: session.impersonator_user_id,
226
+ name: '',
227
+ })
228
+ : null,
229
+ }));
190
230
  }
191
231
 
192
232
  async createWithEmailAndPassword(
@@ -236,7 +276,7 @@ export class UserService {
236
276
  });
237
277
 
238
278
  try {
239
- await this.challenge.verifyEmail(locale, user.id, email, app);
279
+ await this.challenge.sendEmailConfirmationLink(locale, user.id, email, app);
240
280
  } catch (error: any) {
241
281
  this.logger.warn(
242
282
  `Signup confirmation email failed for user ${user.id}: ${error?.message ?? error}`
@@ -253,7 +293,10 @@ export class UserService {
253
293
  payload: { id: user.id, name: user.name, email },
254
294
  }).catch(() => null);
255
295
 
256
- return { user, hasChallenge: true };
296
+ // `hasChallenge` não é decidido aqui: quem chama (AuthService.signupWithEmailAndPassword)
297
+ // é que sabe se `require-email-verification` está ligada e se o cadastro
298
+ // deve ou não já sair com uma sessão emitida.
299
+ return { user };
257
300
  }
258
301
 
259
302
  async update(
@@ -459,6 +502,9 @@ export class UserService {
459
502
  take: 20,
460
503
  },
461
504
  user_session: true,
505
+ // Os dispositivos NAO entram aqui: uma conta antiga acumula dezenas
506
+ // deles, e embutir a lista inteira pesaria em todo GET /user/:id. O
507
+ // sheet busca em GET /user-device/user/:userId, que e paginado.
462
508
  user_account: true,
463
509
  role_user: {
464
510
  include: {
@@ -673,7 +719,15 @@ export class UserService {
673
719
  // Activity Tracking
674
720
  // ==========================================
675
721
 
676
- async registerUserActivity(userId: number | null | undefined, action: string) {
722
+ /**
723
+ * `actorUserId` marca quem provocou a atividade quando nao foi o proprio
724
+ * usuario — hoje, o operador de uma simulacao de acesso.
725
+ */
726
+ async registerUserActivity(
727
+ userId: number | null | undefined,
728
+ action: string,
729
+ actorUserId?: number | null,
730
+ ) {
677
731
  if (!userId || !Number.isInteger(userId) || userId <= 0) {
678
732
  return null;
679
733
  }
@@ -682,6 +736,7 @@ export class UserService {
682
736
  data: {
683
737
  user_id: userId,
684
738
  action,
739
+ actor_user_id: actorUserId ?? null,
685
740
  },
686
741
  });
687
742
  }
@@ -690,13 +745,18 @@ export class UserService {
690
745
  // Change Log (administrative audit trail)
691
746
  // ==========================================
692
747
 
693
- private async recordUserChange(data: {
748
+ /**
749
+ * Publico porque a simulacao de acesso (ImpersonationService) tambem precisa
750
+ * gravar na mesma trilha administrativa, com o operador como ator.
751
+ */
752
+ async recordUserChange(data: {
694
753
  targetUserId: number;
695
754
  actorUserId?: number | null;
696
755
  action: string;
697
756
  summary?: string;
698
757
  beforeData?: unknown;
699
758
  afterData?: unknown;
759
+ ipAddress?: string | null;
700
760
  }) {
701
761
  return this.prismaService.user_change_log
702
762
  .create({
@@ -705,6 +765,7 @@ export class UserService {
705
765
  actor_user_id: data.actorUserId ?? null,
706
766
  action: data.action,
707
767
  summary: data.summary ?? null,
768
+ ip_address: data.ipAddress ?? null,
708
769
  before_data:
709
770
  data.beforeData === undefined
710
771
  ? null
@@ -930,16 +991,57 @@ export class UserService {
930
991
  },
931
992
  );
932
993
 
994
+ const withActors = await this.attachActivityActors(paginated.data || []);
995
+
933
996
  return {
934
997
  ...paginated,
935
- data: (paginated.data || []).map((activity: any) => ({
998
+ data: withActors.map((activity: any) => ({
936
999
  id: String(activity.id),
937
1000
  action: activity.action,
938
1001
  createdAt: activity.created_at?.toISOString?.() || null,
1002
+ actor: activity.actor,
939
1003
  })),
940
1004
  };
941
1005
  }
942
1006
 
1007
+ /**
1008
+ * Anexa `actor` as atividades provocadas por outra pessoa (simulacao de
1009
+ * acesso). `actor_user_id` e INT puro, sem FK — uma segunda FK para `user`
1010
+ * renomearia a relacao `user.user_activity` no db pull —, entao o nome vem de
1011
+ * uma consulta separada, como em `listUserChangeLogs`.
1012
+ *
1013
+ * Preserva os campos originais: a ficha do usuario embute `user_activity` cru
1014
+ * e a linha do tempo do admin le `created_at`, nao `createdAt`.
1015
+ */
1016
+ async attachActivityActors(activities: any[]) {
1017
+ const actorIds = [
1018
+ ...new Set(
1019
+ activities
1020
+ .map((activity) => activity.actor_user_id)
1021
+ .filter((id): id is number => typeof id === 'number'),
1022
+ ),
1023
+ ];
1024
+
1025
+ const actors = actorIds.length
1026
+ ? await this.prismaService.user.findMany({
1027
+ where: { id: { in: actorIds } },
1028
+ select: { id: true, name: true },
1029
+ })
1030
+ : [];
1031
+ const actorById = new Map(actors.map((actor) => [actor.id, actor]));
1032
+
1033
+ return activities.map((activity: any) => ({
1034
+ ...activity,
1035
+ actor:
1036
+ typeof activity.actor_user_id === 'number'
1037
+ ? (actorById.get(activity.actor_user_id) ?? {
1038
+ id: activity.actor_user_id,
1039
+ name: '',
1040
+ })
1041
+ : null,
1042
+ }));
1043
+ }
1044
+
943
1045
  // ==========================================
944
1046
  // Private Helper Methods
945
1047
  // ==========================================