@hed-hog/core 0.0.390 → 0.0.394

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
@@ -24,6 +24,7 @@ import { SecurityService } from '../security/security.service';
24
24
  import { SettingService } from '../setting/setting.service';
25
25
  import { TokenService } from '../token/token.service';
26
26
  import { UserService } from '../user/user.service';
27
+ import { buildSecurityEventVars } from '../utils/security-event-vars';
27
28
  import { resolveWebAuthnOrigin } from '../utils/webauthn-origin';
28
29
  import { ChangeEmailDto } from './dto/change-email.dto';
29
30
  import { ChangePasswordDto } from './dto/change-password.dto';
@@ -322,9 +323,18 @@ export class ProfileService {
322
323
  }
323
324
 
324
325
  async getProfile(userId: number) {
325
- return this.prisma.user.findUnique({
326
- where: { id: userId },
327
- });
326
+ const [user, passwordCredentials] = await Promise.all([
327
+ this.prisma.user.findUnique({ where: { id: userId } }),
328
+ this.prisma.user_credential.count({ where: { user_id: userId, type: 'password' } }),
329
+ ]);
330
+
331
+ if (!user) return user;
332
+
333
+ // Quem entrou só por login social não tem senha para digitar. Sem este
334
+ // campo o cliente não tem como saber disso e acaba exigindo a senha em
335
+ // telas onde `deleteAccount`/`changePassword` já a dispensam — o que deixa
336
+ // essas contas sem caminho para excluir a própria conta.
337
+ return { ...user, hasPassword: passwordCredentials > 0 };
328
338
  }
329
339
 
330
340
  async getMFAMethods(userId: number) {
@@ -477,14 +487,22 @@ export class ProfileService {
477
487
  });
478
488
 
479
489
  if (user?.user_identifier?.[0]?.value) {
480
- this.mail.sendTemplatedMail(locale, {
481
- email: user.user_identifier[0].value,
490
+ const recipientEmail = user.user_identifier[0].value;
491
+ // Data e pais sao formatados aqui, entao o idioma tem que ser o mesmo que
492
+ // o corpo do e-mail vai usar - resolvido antes, nao dentro do envio.
493
+ const recipientLocale = await this.mail.resolveRecipientLocale(
494
+ recipientEmail,
495
+ locale,
496
+ userId,
497
+ );
498
+
499
+ this.mail.sendTemplatedMail(recipientLocale, {
500
+ email: recipientEmail,
482
501
  slug: 'auth-change-password-notification',
502
+ userId,
483
503
  variables: {
484
504
  name: user.name,
485
- userAgent: userAgent || 'Unknown',
486
- ipAddress: ipAddress || 'Unknown',
487
- location: 'Unknown',
505
+ ...buildSecurityEventVars({ ipAddress, userAgent, locale: recipientLocale }),
488
506
  },
489
507
  app: await this.challenge.resolveAppKeyFromOrigin(origin),
490
508
  }).catch(error => {
@@ -1,3 +1,4 @@
1
+ import * as Sentry from '@sentry/nestjs';
1
2
  import { Logger } from '@nestjs/common';
2
3
  import { createClient, type RedisClientType } from 'redis';
3
4
 
@@ -132,6 +133,16 @@ export async function createRedisPubSub(
132
133
  try {
133
134
  await onMessage(message);
134
135
  } catch (error) {
136
+ // A instrumentação automática do Sentry para node-redis cobre publish()
137
+ // mas NÃO subscribe()/entrega de mensagem (limitação da instrumentação
138
+ // vendorizada, não deste projeto) — sem esta captura explícita, uma
139
+ // falha aqui era só um logger.warn, invisível fora do log do pod.
140
+ Sentry.withScope((scope) => {
141
+ scope.setTags({ 'redis.channel': channel, 'redis.label': label });
142
+ Sentry.captureException(error, {
143
+ mechanism: { handled: true, type: 'hedhog.redis_pubsub' },
144
+ });
145
+ });
135
146
  logger.warn(
136
147
  `Failed to handle Redis ${label} message: ${extractRedisErrorMessage(error)}`,
137
148
  );
@@ -154,4 +154,79 @@ describe('RoleGuard', () => {
154
154
  true,
155
155
  );
156
156
  });
157
+
158
+ describe('simulação de acesso — denylist de rotas', () => {
159
+ const impersonatedRequest = {
160
+ headers: { authorization: 'Bearer tok' },
161
+ auth: { sub: 42, sessionId: 555, act: 7, imp: true },
162
+ };
163
+
164
+ it('bloqueia GET /auth/desktop-token, que é @NoRole()', async () => {
165
+ // Sem esta guarda o operador cunharia para o ALVO uma sessão completa,
166
+ // renovável e de 30 dias — furando o teto da simulação.
167
+ const { guard, deps } = makeService();
168
+ setMeta(deps.reflector, { withNoRole: true });
169
+ setPath(deps.reflector, 'auth', 'desktop-token', RequestMethod.GET);
170
+
171
+ await expect(
172
+ guard.canActivate(makeContext(impersonatedRequest) as any),
173
+ ).rejects.toThrow(ForbiddenException);
174
+ });
175
+
176
+ it('bloqueia POST /auth/logout, que é @Public()', async () => {
177
+ // O cookie `rt` da mesma origem é o do OPERADOR: sair por aqui revogaria
178
+ // a sessão real dele.
179
+ const { guard, deps } = makeService();
180
+ setMeta(deps.reflector, { public: true });
181
+ setPath(deps.reflector, 'auth', 'logout', RequestMethod.POST);
182
+
183
+ await expect(
184
+ guard.canActivate(makeContext(impersonatedRequest) as any),
185
+ ).rejects.toThrow(ForbiddenException);
186
+ });
187
+
188
+ it('bloqueia a família /auth/pairing por prefixo', async () => {
189
+ const { guard, deps } = makeService();
190
+ setMeta(deps.reflector, { public: true });
191
+ setPath(deps.reflector, 'auth', 'pairing/redeem', RequestMethod.POST);
192
+
193
+ await expect(
194
+ guard.canActivate(makeContext(impersonatedRequest) as any),
195
+ ).rejects.toThrow(ForbiddenException);
196
+ });
197
+
198
+ it('bloqueia simulação aninhada em POST /impersonation/start', async () => {
199
+ const { guard, deps } = makeService();
200
+ setMeta(deps.reflector, { withRole: true });
201
+ setPath(deps.reflector, 'impersonation', 'start', RequestMethod.POST);
202
+
203
+ await expect(
204
+ guard.canActivate(makeContext(impersonatedRequest) as any),
205
+ ).rejects.toThrow(ForbiddenException);
206
+ });
207
+
208
+ it('libera POST /impersonation/stop — sem ela não há como sair', async () => {
209
+ const { guard, deps } = makeService();
210
+ setMeta(deps.reflector, { withNoRole: true });
211
+ setPath(deps.reflector, 'impersonation', 'stop', RequestMethod.POST);
212
+
213
+ await expect(
214
+ guard.canActivate(makeContext(impersonatedRequest) as any),
215
+ ).resolves.toBe(true);
216
+ });
217
+
218
+ it('não interfere fora de uma simulação', async () => {
219
+ const { guard, deps } = makeService();
220
+ setMeta(deps.reflector, { withNoRole: true });
221
+ setPath(deps.reflector, 'auth', 'desktop-token', RequestMethod.GET);
222
+ const request = {
223
+ headers: { authorization: 'Bearer tok' },
224
+ auth: { sub: 3, sessionId: 1 },
225
+ };
226
+
227
+ await expect(guard.canActivate(makeContext(request) as any)).resolves.toBe(
228
+ true,
229
+ );
230
+ });
231
+ });
157
232
  });
@@ -14,6 +14,7 @@ import {
14
14
  import { METHOD_METADATA } from '@nestjs/common/constants';
15
15
  import { Reflector } from '@nestjs/core';
16
16
  import { Request } from 'express';
17
+ import { isRestrictedDuringImpersonation } from '../../impersonation/impersonation-restrictions';
17
18
 
18
19
  @Injectable()
19
20
  export class RoleGuard implements CanActivate {
@@ -29,6 +30,27 @@ export class RoleGuard implements CanActivate {
29
30
  ) {}
30
31
 
31
32
  async canActivate(context: ExecutionContext): Promise<boolean> {
33
+ const request = context.switchToHttp().getRequest<Request>();
34
+ const { fullPath, httpMethod } = this.resolveRoute(context);
35
+
36
+ // ANTES dos atalhos de @Public()/@NoRole(): sao esses dois decorators que
37
+ // protegem as rotas que uma simulacao nao pode alcancar (ver
38
+ // impersonation-restrictions.ts). Checar depois deles deixaria
39
+ // /auth/desktop-token e /auth/logout passarem.
40
+ if (
41
+ (request as any)?.auth?.imp === true &&
42
+ isRestrictedDuringImpersonation(httpMethod, fullPath)
43
+ ) {
44
+ const locale = request['locale'] || 'en';
45
+ throw new ForbiddenException(
46
+ getLocaleText(
47
+ 'impersonation.restrictedRoute',
48
+ locale,
49
+ 'This action is not available during an access simulation.',
50
+ ),
51
+ );
52
+ }
53
+
32
54
  const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
33
55
  context.getHandler(),
34
56
  context.getClass(),
@@ -57,23 +79,6 @@ export class RoleGuard implements CanActivate {
57
79
  return true;
58
80
  }
59
81
 
60
- const request = context.switchToHttp().getRequest<Request>();
61
- const controller = context.getClass();
62
- const handler = context.getHandler();
63
- const controllerPath = this.reflector.get<string>('path', controller) || '';
64
- const methodPath = this.reflector.get<string>('path', handler) || '';
65
-
66
- const requestMethod = this.reflector.get<RequestMethod>(
67
- METHOD_METADATA,
68
- handler,
69
- );
70
-
71
- let fullPath = `/${controllerPath}/${methodPath}`.replace(/\/+/g, '/');
72
-
73
- if (fullPath.endsWith('/')) {
74
- fullPath = fullPath.slice(0, -1);
75
- }
76
-
77
82
  const token = this.extractTokenFromHeader(request);
78
83
 
79
84
  if (!token) {
@@ -85,37 +90,11 @@ export class RoleGuard implements CanActivate {
85
90
 
86
91
  const userId = (request as any)?.auth?.sub;
87
92
 
88
- let httpMethod: any;
89
- switch (requestMethod) {
90
- case RequestMethod.GET:
91
- httpMethod = 'GET';
92
- break;
93
- case RequestMethod.POST:
94
- httpMethod = 'POST';
95
- break;
96
- case RequestMethod.PUT:
97
- httpMethod = 'PUT';
98
- break;
99
- case RequestMethod.DELETE:
100
- httpMethod = 'DELETE';
101
- break;
102
- case RequestMethod.PATCH:
103
- httpMethod = 'PATCH';
104
- break;
105
- case RequestMethod.OPTIONS:
106
- httpMethod = 'OPTIONS';
107
- break;
108
- case RequestMethod.HEAD:
109
- httpMethod = 'HEAD';
110
- break;
111
- case RequestMethod.ALL:
112
- httpMethod = 'ALL';
113
- break;
114
- }
115
-
116
93
  const route = await this.prisma.route.count({
117
94
  where: {
118
- method: httpMethod,
95
+ // `route.method` e um enum gerado com nome variavel por instalacao; o
96
+ // valor vem dos metadados do Nest e sempre casa com um dos membros.
97
+ method: httpMethod as any,
119
98
  url: fullPath,
120
99
  role_route: {
121
100
  some: {
@@ -171,6 +150,45 @@ export class RoleGuard implements CanActivate {
171
150
  return true;
172
151
  }
173
152
 
153
+ /**
154
+ * Reconstroi `METHOD /path` a partir dos metadados do Nest — a mesma chave
155
+ * usada na tabela `route`, com os `:params` declarados no controller.
156
+ */
157
+ private resolveRoute(context: ExecutionContext): {
158
+ fullPath: string;
159
+ httpMethod: string;
160
+ } {
161
+ const controllerPath =
162
+ this.reflector.get<string>('path', context.getClass()) || '';
163
+ const methodPath =
164
+ this.reflector.get<string>('path', context.getHandler()) || '';
165
+
166
+ let fullPath = `/${controllerPath}/${methodPath}`.replace(/\/+/g, '/');
167
+
168
+ if (fullPath.endsWith('/')) {
169
+ fullPath = fullPath.slice(0, -1);
170
+ }
171
+
172
+ const requestMethod = this.reflector.get<RequestMethod>(
173
+ METHOD_METADATA,
174
+ context.getHandler(),
175
+ );
176
+
177
+ const httpMethod =
178
+ {
179
+ [RequestMethod.GET]: 'GET',
180
+ [RequestMethod.POST]: 'POST',
181
+ [RequestMethod.PUT]: 'PUT',
182
+ [RequestMethod.DELETE]: 'DELETE',
183
+ [RequestMethod.PATCH]: 'PATCH',
184
+ [RequestMethod.OPTIONS]: 'OPTIONS',
185
+ [RequestMethod.HEAD]: 'HEAD',
186
+ [RequestMethod.ALL]: 'ALL',
187
+ }[requestMethod] ?? '';
188
+
189
+ return { fullPath, httpMethod };
190
+ }
191
+
174
192
  private extractTokenFromHeader(request: Request): string | undefined {
175
193
  const [type, token] = request.headers.authorization?.split(' ') ?? [];
176
194
  return type === 'Bearer' ? token : undefined;
@@ -292,6 +292,9 @@ describe('SessionService', () => {
292
292
  revoked_at: null,
293
293
  expires_at: new Date(Date.now() + 100000),
294
294
  browser_id: 'br',
295
+ // O refresh e fail-closed: `impersonator_user_id` precisa vir do select
296
+ // e ser explicitamente null para a rotacao seguir.
297
+ impersonator_user_id: null,
295
298
  });
296
299
  const tx = {
297
300
  user_session: {
@@ -312,6 +315,84 @@ describe('SessionService', () => {
312
315
  });
313
316
  });
314
317
 
318
+ describe('createImpersonation', () => {
319
+ it('grava o operador, expiração própria e browser_id nulo', async () => {
320
+ jest.useFakeTimers().setSystemTime(new Date('2026-08-22T12:00:00Z'));
321
+ const { service, deps } = makeService();
322
+ deps.prisma.user_session.create.mockResolvedValue({ id: 9 });
323
+
324
+ await service.createImpersonation('pt', 42, 7, '1.2.3.4', 'ua', 30, 'chamado 123');
325
+
326
+ const data = (deps.prisma.user_session.create as any).mock.calls[0][0].data;
327
+ expect(data.user_id).toBe(42);
328
+ expect(data.impersonator_user_id).toBe(7);
329
+ expect(data.impersonation_reason).toBe('chamado 123');
330
+ expect(data.browser_id).toBeNull();
331
+ expect(data.expires_at).toEqual(new Date('2026-08-22T12:30:00Z'));
332
+ });
333
+
334
+ it('descarta o token opaco — não existe refresh token para a sessão simulada', async () => {
335
+ const { service, deps } = makeService();
336
+ deps.prisma.user_session.create.mockResolvedValue({ id: 9 });
337
+
338
+ const result: any = await service.createImpersonation(
339
+ 'pt', 42, 7, '1.2.3.4', 'ua', 30,
340
+ );
341
+
342
+ expect(result).toEqual({ id: 9 });
343
+ expect(result.token).toBeUndefined();
344
+ });
345
+
346
+ it('NÃO aplica o limite de sessões: simular alguém não pode derrubar a pessoa', async () => {
347
+ const { service, deps } = makeService();
348
+ deps.setting.getSettingValues.mockResolvedValue({ 'max-concurrent-sessions': 1 });
349
+ deps.prisma.user_session.create.mockResolvedValue({ id: 9 });
350
+
351
+ await service.createImpersonation('pt', 42, 7, '1.2.3.4', 'ua', 30);
352
+
353
+ expect(deps.prisma.user_session.findMany).not.toHaveBeenCalled();
354
+ expect(deps.prisma.user_session.updateMany).not.toHaveBeenCalled();
355
+ });
356
+ });
357
+
358
+ describe('refresh - sessão simulada', () => {
359
+ it('rejeita rotação de sessão de simulação', async () => {
360
+ const { service, deps } = makeService();
361
+ deps.prisma.user_session.findFirst.mockResolvedValue({
362
+ id: 1,
363
+ user_id: 42,
364
+ ip_address: '1.2.3.4',
365
+ user_agent: 'ua',
366
+ revoked_at: null,
367
+ expires_at: new Date(Date.now() + 100000),
368
+ browser_id: null,
369
+ impersonator_user_id: 7,
370
+ });
371
+
372
+ await expect(service.refresh('pt', 'tok', '1.2.3.4', 'ua')).rejects.toThrow(
373
+ UnauthorizedException,
374
+ );
375
+ expect(deps.prisma.$transaction).not.toHaveBeenCalled();
376
+ });
377
+ });
378
+
379
+ describe('enforceSessionLimit - sessões simuladas', () => {
380
+ it('não conta simulações no limite de sessões do usuário', async () => {
381
+ const { service, deps } = makeService();
382
+ deps.setting.getSettingValues.mockResolvedValue({
383
+ 'max-concurrent-sessions': 2,
384
+ 'refresh-token-expiration-minutes': 100,
385
+ });
386
+ deps.prisma.user_session.findMany.mockResolvedValue([]);
387
+ deps.prisma.user_session.create.mockResolvedValue({ id: 1 });
388
+
389
+ await service.create('pt', 42, '1.2.3.4', 'ua', 'br');
390
+
391
+ const where = (deps.prisma.user_session.findMany as any).mock.calls[0][0].where;
392
+ expect(where.impersonator_user_id).toBeNull();
393
+ });
394
+ });
395
+
315
396
  describe('getUserSessions - paginação', () => {
316
397
  it('lança BadRequest quando usuário não existe', async () => {
317
398
  const { service, deps } = makeService();
@@ -79,6 +79,77 @@ export class SessionService {
79
79
  return { token, session };
80
80
  }
81
81
 
82
+ /**
83
+ * Sessao de simulacao de acesso: um operador vendo o sistema como outro
84
+ * usuario. Caminho separado do `create` por tres motivos, todos deliberados:
85
+ *
86
+ * 1. NAO chama `enforceSessionLimit`. Com `max-concurrent-sessions > 0`,
87
+ * abrir uma simulacao despejaria sessoes REAIS do alvo — simular alguem
88
+ * derrubaria a pessoa.
89
+ * 2. O token opaco e gerado e DESCARTADO. Ninguem recebe refresh token para
90
+ * esta sessao, entao ela e nao renovavel por construcao, nao so por regra.
91
+ * 3. `browser_id` fica nulo, para a simulacao nao se misturar aos slots de
92
+ * navegador do alvo.
93
+ */
94
+ async createImpersonation(
95
+ locale: string,
96
+ targetUserId: number,
97
+ impersonatorUserId: number,
98
+ ipAddress: string,
99
+ userAgent: string,
100
+ expiresInMinutes: number,
101
+ reason?: string | null,
102
+ ) {
103
+ if (!targetUserId || !impersonatorUserId) {
104
+ throw new BadRequestException(getLocaleText('session.userIdRequired', locale, 'User ID is required to create a session.'));
105
+ }
106
+
107
+ const throwawayToken = await this.token.createOpaqueToken();
108
+
109
+ return this.prisma.user_session.create({
110
+ data: {
111
+ user_id: targetUserId,
112
+ ip_address: ipAddress || 'unknown',
113
+ user_agent: userAgent || 'unknown',
114
+ expires_at: new Date(Date.now() + expiresInMinutes * 60 * 1000),
115
+ hash: this.security.hashWithPepper(throwawayToken),
116
+ browser_id: null,
117
+ impersonator_user_id: impersonatorUserId,
118
+ impersonation_reason: reason ?? null,
119
+ },
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Anexa `impersonator: { id, name }` as sessoes simuladas. Nao ha relacao
125
+ * Prisma para isso: `impersonator_user_id` e INT puro, sem FK, para nao
126
+ * renomear `user.user_session` no db pull. Mesmo padrao de
127
+ * `UserService.listUserChangeLogs` com os atores.
128
+ */
129
+ private async attachImpersonators<T extends { impersonator_user_id?: number | null }>(rows: T[]) {
130
+ const ids = Array.from(
131
+ new Set(rows.map((row) => row.impersonator_user_id).filter((id): id is number => typeof id === 'number')),
132
+ );
133
+
134
+ if (!ids.length) {
135
+ return rows.map((row) => ({ ...row, impersonator: null }));
136
+ }
137
+
138
+ const users = await this.prisma.user.findMany({
139
+ where: { id: { in: ids } },
140
+ select: { id: true, name: true },
141
+ });
142
+ const byId = new Map(users.map((user) => [user.id, user]));
143
+
144
+ return rows.map((row) => ({
145
+ ...row,
146
+ impersonator:
147
+ typeof row.impersonator_user_id === 'number'
148
+ ? (byId.get(row.impersonator_user_id) ?? { id: row.impersonator_user_id, name: '' })
149
+ : null,
150
+ }));
151
+ }
152
+
82
153
  async refresh(locale: string, refreshToken: string, ipAddress: string, userAgent: string, browserId?: string | null) {
83
154
  if (!refreshToken) {
84
155
  throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
@@ -88,7 +159,7 @@ export class SessionService {
88
159
 
89
160
  const rawSession = await this.prisma.user_session.findFirst({
90
161
  where: { hash },
91
- select: { id: true, user_id: true, ip_address: true, user_agent: true, revoked_at: true, expires_at: true, browser_id: true },
162
+ select: { id: true, user_id: true, ip_address: true, user_agent: true, revoked_at: true, expires_at: true, browser_id: true, impersonator_user_id: true },
92
163
  });
93
164
 
94
165
  if (!rawSession) {
@@ -106,6 +177,15 @@ export class SessionService {
106
177
  throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
107
178
  }
108
179
 
180
+ // Simulacao de acesso e nao renovavel. `createImpersonation` ja descarta o
181
+ // token opaco, entao chegar aqui significa que alguem obteve o segredo por
182
+ // outro caminho — e renovar transformaria a janela de 30 minutos numa sessao
183
+ // normal de 30 dias, sem a marca de simulado.
184
+ if (rawSession.impersonator_user_id !== null) {
185
+ this.logger.warn(`[SessionRefresh] Refresh rejected: sessionId=${rawSession.id} userId=${rawSession.user_id} is an impersonation session`);
186
+ throw new UnauthorizedException(getLocaleText('accessDenied', locale, 'Access denied.'));
187
+ }
188
+
109
189
  const session = {
110
190
  id: rawSession.id,
111
191
  user_id: rawSession.user_id,
@@ -223,7 +303,9 @@ export class SessionService {
223
303
  });
224
304
 
225
305
  const itemsWithLocation = await Promise.all(
226
- paginate.data.map(async (s) => {
306
+ // O `hash` é o resumo do refresh token: nenhum cliente precisa dele, e
307
+ // a lista de sessões é exibida na tela de segurança do app e do site.
308
+ paginate.data.map(async ({ hash: _hash, ...s }) => {
227
309
  const ip = s.ip_address || s.ip || null;
228
310
  let location: GeoIpResult | null = null;
229
311
  if (ip && ip !== '127.0.0.1' && ip !== '::1') {
@@ -240,7 +322,7 @@ export class SessionService {
240
322
  );
241
323
 
242
324
  return {
243
- data: itemsWithLocation,
325
+ data: await this.attachImpersonators(itemsWithLocation),
244
326
  total: paginate.total || 0,
245
327
  lastPage: Math.ceil((paginate.total || 0) / (paginate.pageSize || 1)),
246
328
  page: paginate.page ?? 1,
@@ -272,7 +354,9 @@ export class SessionService {
272
354
  });
273
355
 
274
356
  const itemsWithLocation = await Promise.all(
275
- paginate.data.map(async (s) => {
357
+ // O `hash` é o resumo do refresh token: nenhum cliente precisa dele, e
358
+ // a lista de sessões é exibida na tela de segurança do app e do site.
359
+ paginate.data.map(async ({ hash: _hash, ...s }) => {
276
360
  const ip = s.ip_address || s.ip || null;
277
361
  let location: GeoIpResult | null = null;
278
362
  if (ip && ip !== '127.0.0.1' && ip !== '::1') {
@@ -289,7 +373,7 @@ export class SessionService {
289
373
  );
290
374
 
291
375
  return {
292
- data: itemsWithLocation,
376
+ data: await this.attachImpersonators(itemsWithLocation),
293
377
  total: paginate.total || 0,
294
378
  lastPage: Math.ceil((paginate.total || 0) / (paginate.pageSize || 1)),
295
379
  page: paginate.page ?? 1,
@@ -391,7 +475,10 @@ export class SessionService {
391
475
  where: {
392
476
  user_id: userId,
393
477
  revoked_at: null,
394
- expires_at: { gt: new Date() }
478
+ expires_at: { gt: new Date() },
479
+ // Simulacao de acesso nao consome slot: ela nasce com browser_id nulo e
480
+ // seria contada como slot proprio, despejando uma sessao real do alvo.
481
+ impersonator_user_id: null,
395
482
  },
396
483
  orderBy: { created_at: 'asc' },
397
484
  select: { id: true, browser_id: true }
@@ -0,0 +1,137 @@
1
+ import {
2
+ BrandAssetBases,
3
+ isPublicAssetUrl,
4
+ resolveBrandAssetUrl,
5
+ } from '@hed-hog/api-mail';
6
+ import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
7
+ import { ConfigService } from '@nestjs/config';
8
+ import { SettingService } from './setting.service';
9
+
10
+ // Reexportados para que as libraries que ja dependem do core (lms, crm,
11
+ // operations) usem a regra sem precisar declarar `@hed-hog/api-mail`.
12
+ export { isPublicAssetUrl, resolveBrandAssetUrl };
13
+ export type { BrandAssetBases };
14
+
15
+ /** Timeout do fetch em {@link BrandAssetService.fetchAsDataUri}. */
16
+ const FETCH_TIMEOUT_MS = 5_000;
17
+
18
+ /**
19
+ * Resolucao das settings de marca (`image-url`, `icon-url`, `pdf-logo`) para
20
+ * URL absoluta, para quem renderiza fora do browser: e-mail, PDF, relatorio.
21
+ *
22
+ * Existe porque a mesma regra estava duplicada em quatro lugares, todos
23
+ * derivando a base da env `API_URL` - que nao esta definida em nenhum
24
+ * manifesto de producao. As settings `url` e `api-url` sao a fonte de verdade
25
+ * (gravadas na instalacao e editaveis no admin); a env fica como fallback de
26
+ * dev/CI.
27
+ */
28
+ @Injectable()
29
+ export class BrandAssetService {
30
+ private readonly logger = new Logger(BrandAssetService.name);
31
+
32
+ constructor(
33
+ @Inject(forwardRef(() => SettingService))
34
+ private readonly setting: SettingService,
35
+ private readonly configService: ConfigService,
36
+ ) {}
37
+
38
+ /** Bases publicas do front (setting `url`) e da API (setting `api-url`). */
39
+ async getBases(): Promise<BrandAssetBases> {
40
+ const port = this.configService.get<string | number>('PORT') ?? 3100;
41
+ let settings: Record<string, any> = {};
42
+
43
+ try {
44
+ settings = await this.setting.getSettingValues(['url', 'api-url']);
45
+ } catch (error) {
46
+ this.logger.warn(
47
+ `Could not read the base URL settings, falling back to the environment: ${
48
+ error instanceof Error ? error.message : String(error)
49
+ }`,
50
+ );
51
+ }
52
+
53
+ const read = (slug: string) => String(settings[slug] ?? '').trim();
54
+
55
+ return {
56
+ appBaseUrl: read('url'),
57
+ apiBaseUrl:
58
+ read('api-url') ||
59
+ String(this.configService.get<string>('API_URL') ?? '').trim() ||
60
+ `http://localhost:${port}`,
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Resolve um valor de setting de marca para URL absoluta. Devolve `''`
66
+ * quando nao ha valor ou nao ha base utilizavel - quem chama decide o
67
+ * fallback.
68
+ */
69
+ async resolveAssetUrl(value?: string | null): Promise<string> {
70
+ const trimmed = String(value ?? '').trim();
71
+
72
+ if (!trimmed) {
73
+ return '';
74
+ }
75
+
76
+ return resolveBrandAssetUrl(trimmed, await this.getBases());
77
+ }
78
+
79
+ /**
80
+ * Logo do sistema ja absoluto, na ordem de prioridade informada.
81
+ *
82
+ * `slugs` permite ao chamador antepor um slug proprio (ex.: `pdf-logo`)
83
+ * antes de cair no `image-url` global.
84
+ */
85
+ async resolveLogoUrl(
86
+ slugs: string[] = ['image-url', 'icon-url'],
87
+ ): Promise<string> {
88
+ const settings = await this.setting.getSettingValues(slugs);
89
+
90
+ for (const slug of slugs) {
91
+ const value = String(settings[slug] ?? '').trim();
92
+
93
+ if (value) {
94
+ return this.resolveAssetUrl(value);
95
+ }
96
+ }
97
+
98
+ return '';
99
+ }
100
+
101
+ /**
102
+ * Baixa a imagem e devolve como data URI.
103
+ *
104
+ * O Playwright renderiza o PDF em uma pagina sem sessao e, dependendo do
105
+ * ambiente, sem rota para o host publico; embutir o binario evita o slot de
106
+ * imagem vazio. Devolve `null` em qualquer falha, para o chamador cair na
107
+ * URL absoluta.
108
+ */
109
+ async fetchAsDataUri(url: string): Promise<string | null> {
110
+ if (!url || url.startsWith('data:')) {
111
+ return url || null;
112
+ }
113
+
114
+ try {
115
+ const controller = new AbortController();
116
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
117
+ const response = await globalThis.fetch(url, { signal: controller.signal });
118
+ clearTimeout(timeout);
119
+
120
+ if (!response.ok) {
121
+ return null;
122
+ }
123
+
124
+ const contentType = response.headers.get('content-type') ?? 'image/svg+xml';
125
+ const buffer = await response.arrayBuffer();
126
+
127
+ return `data:${contentType};base64,${Buffer.from(buffer).toString('base64')}`;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /** Reexportado para quem renderiza e-mail. Ver `isPublicAssetUrl`. */
134
+ isPublicUrl(url?: string | null): boolean {
135
+ return isPublicAssetUrl(url);
136
+ }
137
+ }