@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
@@ -0,0 +1,71 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3
+ import { renderHook, waitFor } from '@testing-library/react';
4
+ import { createElement, type ReactNode } from 'react';
5
+
6
+ const { appState } = vi.hoisted(() => ({
7
+ appState: {
8
+ request: vi.fn(),
9
+ accessToken: 'token-123',
10
+ impersonation: null as unknown,
11
+ },
12
+ }));
13
+
14
+ vi.mock('@hed-hog/next-app-provider', async () => {
15
+ const actual =
16
+ await vi.importActual<typeof import('@tanstack/react-query')>(
17
+ '@tanstack/react-query'
18
+ );
19
+ return {
20
+ useApp: () => appState,
21
+ useQuery: actual.useQuery,
22
+ };
23
+ });
24
+
25
+ import { useImpersonationAccess } from './use-impersonation-access';
26
+
27
+ const renderWithRoles = async (slugs: string[]) => {
28
+ appState.request = vi.fn().mockResolvedValue({
29
+ data: { roles: slugs.map((slug) => ({ slug })) },
30
+ });
31
+
32
+ // Um client por render: cache compartilhado vazaria papéis de um teste para
33
+ // o outro.
34
+ const queryClient = new QueryClient({
35
+ defaultOptions: { queries: { retry: false } },
36
+ });
37
+ const wrapper = ({ children }: { children: ReactNode }) =>
38
+ createElement(QueryClientProvider, { client: queryClient }, children);
39
+
40
+ const { result } = renderHook(() => useImpersonationAccess(), { wrapper });
41
+ await waitFor(() => expect(result.current.isLoading).toBe(false));
42
+
43
+ return result;
44
+ };
45
+
46
+ describe('useImpersonationAccess', () => {
47
+ beforeEach(() => {
48
+ appState.impersonation = null;
49
+ });
50
+
51
+ it('libera para admin', async () => {
52
+ const result = await renderWithRoles(['admin']);
53
+ expect(result.current.canSimulateAccess).toBe(true);
54
+ });
55
+
56
+ it('libera para admin-impersonation', async () => {
57
+ const result = await renderWithRoles(['admin-impersonation']);
58
+ expect(result.current.canSimulateAccess).toBe(true);
59
+ });
60
+
61
+ it('nega para admin-user, que administra o cadastro mas não simula acesso', async () => {
62
+ const result = await renderWithRoles(['admin-user']);
63
+ expect(result.current.canSimulateAccess).toBe(false);
64
+ });
65
+
66
+ it('nega dentro de uma simulação — não existe simulação aninhada', async () => {
67
+ appState.impersonation = { target: { id: 1, name: 'Aluna' } };
68
+ const result = await renderWithRoles(['admin']);
69
+ expect(result.current.canSimulateAccess).toBe(false);
70
+ });
71
+ });
@@ -0,0 +1,32 @@
1
+ import { useApp, useQuery } from '@hed-hog/next-app-provider';
2
+
3
+ type Role = { slug?: string };
4
+
5
+ /**
6
+ * Quem pode abrir uma simulação de acesso. Só decide a VISIBILIDADE do botão —
7
+ * a autorização de verdade é o RoleGuard sobre `POST /impersonation/start`.
8
+ */
9
+ export function useImpersonationAccess() {
10
+ const { request, accessToken, impersonation } = useApp();
11
+
12
+ const { data, isLoading } = useQuery<{ roles: Role[] }>({
13
+ queryKey: ['impersonation-roles', accessToken],
14
+ enabled: !!accessToken,
15
+ queryFn: async () => {
16
+ const response = await request<{ roles: Role[] }>({ url: '/auth/roles' });
17
+ return response.data;
18
+ },
19
+ });
20
+
21
+ const roleSlugs = (data?.roles ?? []).map((r) => r.slug || '').filter(Boolean);
22
+
23
+ return {
24
+ isLoading,
25
+ roleSlugs,
26
+ canSimulateAccess:
27
+ // Dentro de uma simulação não se abre outra: o backend recusa e o botão
28
+ // só criaria uma expectativa falsa.
29
+ !impersonation &&
30
+ (roleSlugs.includes('admin') || roleSlugs.includes('admin-impersonation')),
31
+ };
32
+ }
@@ -47,6 +47,7 @@ import {
47
47
  Play,
48
48
  Terminal,
49
49
  Users,
50
+ VenetianMask,
50
51
  } from 'lucide-react';
51
52
  import { useTranslations } from 'next-intl';
52
53
  import { useMemo, useState } from 'react';
@@ -69,8 +70,16 @@ type AccessLog = {
69
70
  os_version: string | null;
70
71
  country: string | null;
71
72
  city: string | null;
73
+ /**
74
+ * Operador por trás de um acesso simulado. `user` continua sendo o ALVO — sem
75
+ * este campo o registro seria indistinguível de um acesso genuíno dele.
76
+ */
77
+ impersonator_user_id: number | null;
78
+ impersonator: { id: number; name: string; photo_id: number | null } | null;
72
79
  };
73
80
 
81
+ type SimulatedFilter = 'all' | 'only' | 'exclude';
82
+
74
83
  type PaginationResult<T> = {
75
84
  data: T[];
76
85
  total: number;
@@ -118,6 +127,7 @@ export default function AccessLogPage() {
118
127
  const [searchTerm, setSearchTerm] = useState('');
119
128
  const [filterUserId, setFilterUserId] = useState<number | null>(null);
120
129
  const [typeFilter, setTypeFilter] = useState<TypeFilter>('all');
130
+ const [simulatedFilter, setSimulatedFilter] = useState<SimulatedFilter>('all');
121
131
  const [createdAtFrom, setCreatedAtFrom] = useState('');
122
132
  const [createdAtTo, setCreatedAtTo] = useState('');
123
133
  const [viewMode, setViewMode] = useState<ViewMode>('table');
@@ -171,6 +181,7 @@ export default function AccessLogPage() {
171
181
  debouncedSearch,
172
182
  filterUserId,
173
183
  typeFilter,
184
+ simulatedFilter,
174
185
  createdAtFrom,
175
186
  createdAtTo,
176
187
  page,
@@ -183,6 +194,7 @@ export default function AccessLogPage() {
183
194
  search: debouncedSearch || undefined,
184
195
  userId: filterUserId || undefined,
185
196
  type: typeFilter !== 'all' ? typeFilter : undefined,
197
+ simulated: simulatedFilter !== 'all' ? simulatedFilter : undefined,
186
198
  createdAtFrom: createdAtFrom || undefined,
187
199
  createdAtTo: createdAtTo || undefined,
188
200
  page,
@@ -200,6 +212,7 @@ export default function AccessLogPage() {
200
212
  searchTerm.length > 0 ||
201
213
  filterUserId !== null ||
202
214
  typeFilter !== 'all' ||
215
+ simulatedFilter !== 'all' ||
203
216
  createdAtFrom.length > 0 ||
204
217
  createdAtTo.length > 0;
205
218
 
@@ -207,6 +220,7 @@ export default function AccessLogPage() {
207
220
  setSearchTerm('');
208
221
  setFilterUserId(null);
209
222
  setTypeFilter('all');
223
+ setSimulatedFilter('all');
210
224
  setCreatedAtFrom('');
211
225
  setCreatedAtTo('');
212
226
  setPage(1);
@@ -229,6 +243,21 @@ export default function AccessLogPage() {
229
243
  { value: 'mcp', label: t('typeMcp') },
230
244
  ],
231
245
  },
246
+ {
247
+ id: 'simulated',
248
+ type: 'select',
249
+ value: simulatedFilter,
250
+ onChange: (value) => {
251
+ setSimulatedFilter(value as SimulatedFilter);
252
+ setPage(1);
253
+ },
254
+ placeholder: t('filterBySimulated'),
255
+ options: [
256
+ { value: 'all', label: t('simulatedAll') },
257
+ { value: 'only', label: t('simulatedOnly') },
258
+ { value: 'exclude', label: t('simulatedExclude') },
259
+ ],
260
+ },
232
261
  {
233
262
  id: 'created-at-from',
234
263
  type: 'date',
@@ -250,7 +279,7 @@ export default function AccessLogPage() {
250
279
  min: createdAtFrom || undefined,
251
280
  },
252
281
  ],
253
- [createdAtFrom, createdAtTo, typeFilter, t]
282
+ [createdAtFrom, createdAtTo, typeFilter, simulatedFilter, t]
254
283
  );
255
284
 
256
285
  const kpiCards = useMemo<KpiCardItem[]>(() => {
@@ -320,33 +349,60 @@ export default function AccessLogPage() {
320
349
  setIsUserSheetOpen(true);
321
350
  };
322
351
 
352
+ /**
353
+ * Marca de acesso simulado. Fica ao lado do usuário, e não numa coluna
354
+ * própria, porque o ponto é justamente qualificar de quem é aquele acesso.
355
+ */
356
+ const renderSimulatedBadge = (log: AccessLog) => {
357
+ if (!log.impersonator_user_id) return null;
358
+
359
+ const operatorName = log.impersonator?.name || `#${log.impersonator_user_id}`;
360
+
361
+ return (
362
+ <Badge
363
+ variant="outline"
364
+ className="shrink-0 border-amber-500/30 bg-amber-500/10 text-amber-700"
365
+ title={t('simulatedBy', { name: operatorName })}
366
+ >
367
+ <VenetianMask className="mr-1 h-3 w-3" />
368
+ {t('simulated')}
369
+ </Badge>
370
+ );
371
+ };
372
+
323
373
  const renderUserCell = (log: AccessLog) => {
324
374
  if (!log.user) {
325
375
  return (
326
- <span className="text-xs text-muted-foreground">{t('anonymous')}</span>
376
+ <div className="flex items-center gap-2">
377
+ <span className="text-xs text-muted-foreground">{t('anonymous')}</span>
378
+ {renderSimulatedBadge(log)}
379
+ </div>
327
380
  );
328
381
  }
329
382
  return (
330
- <button
331
- type="button"
332
- className="flex items-center gap-2 hover:opacity-80"
333
- onClick={(e) => {
334
- e.stopPropagation();
335
- handleOpenUser(log.user!.id);
336
- }}
337
- title={log.user.name}
338
- >
339
- <Avatar className="h-6 w-6 shrink-0 overflow-hidden rounded-full">
340
- <AvatarImage
341
- src={getPhotoUrl(log.user.photo_id)}
342
- alt={log.user.name}
343
- />
344
- <AvatarFallback className="flex h-full w-full items-center justify-center bg-muted text-[10px] font-medium">
345
- {getUserInitials(log.user.name)}
346
- </AvatarFallback>
347
- </Avatar>
348
- <span className="max-w-30 truncate text-sm">{log.user.name}</span>
349
- </button>
383
+ <div className="flex items-center gap-2">
384
+ <button
385
+ type="button"
386
+ className="flex items-center gap-2 hover:opacity-80"
387
+ onClick={(e) => {
388
+ e.stopPropagation();
389
+ handleOpenUser(log.user!.id);
390
+ }}
391
+ title={log.user.name}
392
+ >
393
+ <Avatar className="h-6 w-6 shrink-0 overflow-hidden rounded-full">
394
+ <AvatarImage
395
+ src={getPhotoUrl(log.user.photo_id)}
396
+ alt={log.user.name}
397
+ />
398
+ <AvatarFallback className="flex h-full w-full items-center justify-center bg-muted text-[10px] font-medium">
399
+ {getUserInitials(log.user.name)}
400
+ </AvatarFallback>
401
+ </Avatar>
402
+ <span className="max-w-30 truncate text-sm">{log.user.name}</span>
403
+ </button>
404
+ {renderSimulatedBadge(log)}
405
+ </div>
350
406
  );
351
407
  };
352
408
 
@@ -828,6 +884,24 @@ export default function AccessLogPage() {
828
884
  </div>
829
885
  </div>
830
886
 
887
+ {selectedLog.impersonator_user_id && (
888
+ <div>
889
+ <h4 className="mb-1 text-sm font-semibold">
890
+ {t('simulatedAccess')}
891
+ </h4>
892
+ <div className="flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 p-2 text-sm text-amber-800">
893
+ <VenetianMask className="h-4 w-4 shrink-0" />
894
+ <span>
895
+ {t('simulatedBy', {
896
+ name:
897
+ selectedLog.impersonator?.name ||
898
+ `#${selectedLog.impersonator_user_id}`,
899
+ })}
900
+ </span>
901
+ </div>
902
+ </div>
903
+ )}
904
+
831
905
  <div className="grid grid-cols-2 gap-4">
832
906
  <div>
833
907
  <h4 className="mb-1 text-sm font-semibold">{t('ip')}</h4>
@@ -33,7 +33,7 @@ import { formatDate, formatDateTime } from '@/lib/format-date';
33
33
  import { parseBrowser, parseDeviceType, parseOS } from '@/lib/session';
34
34
  import { UserSession } from '@hed-hog/api-types';
35
35
  import { useApp, useQuery } from '@hed-hog/next-app-provider';
36
- import { Calendar, Clock, LogOut, MapPin } from 'lucide-react';
36
+ import { Calendar, Clock, LogOut, MapPin, VenetianMask } from 'lucide-react';
37
37
  import { useTranslations } from 'next-intl';
38
38
  import { useMemo, useState } from 'react';
39
39
  import { toast } from 'sonner';
@@ -294,6 +294,22 @@ function SessionRow({
294
294
  <p className="text-xs text-muted-foreground">
295
295
  {parseOS(session.user_agent)}
296
296
  </p>
297
+ {/* Esta é a tela de segurança do próprio usuário: é aqui que ele
298
+ precisa enxergar que alguém entrou na conta dele por simulação
299
+ de acesso, e quem foi. */}
300
+ {session.impersonator_user_id && (
301
+ <Badge
302
+ variant="outline"
303
+ className="mt-1 border-amber-500/30 bg-amber-500/10 text-xs text-amber-700"
304
+ >
305
+ <VenetianMask className="mr-1 size-3" />
306
+ {t('simulatedBy', {
307
+ name:
308
+ session.impersonator?.name ||
309
+ `#${session.impersonator_user_id}`,
310
+ })}
311
+ </Badge>
312
+ )}
297
313
  </div>
298
314
  </div>
299
315
  </TableCell>
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+ import { PaginationFooter } from '@/components/entity-list/pagination-footer';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { usePagination } from '@/hooks/use-pagination';
12
+ import { useApp } from '@hed-hog/next-app-provider';
13
+ import { useTranslations } from 'next-intl';
14
+ import { toast } from 'sonner';
15
+ import {
16
+ DEVICE_PAGE_SIZES,
17
+ UserDevice,
18
+ UserDevicesList,
19
+ } from './user-devices-list';
20
+
21
+ /**
22
+ * Dispositivos conhecidos do proprio usuario.
23
+ *
24
+ * Irma de ActiveSessions, mas responde outra pergunta: sessao e conexao viva e
25
+ * expira sozinha; dispositivo e o aparelho que a conta ja reconhece e so sai
26
+ * daqui se alguem mandar. Esquecer um faz o proximo login vindo dele voltar a
27
+ * disparar o alerta de dispositivo desconhecido.
28
+ */
29
+ export function KnownDevices() {
30
+ const { request } = useApp();
31
+ const t = useTranslations('core.KnownDevices');
32
+
33
+ const {
34
+ items: devices,
35
+ refetch,
36
+ page,
37
+ setPage,
38
+ totalItems,
39
+ pageSize,
40
+ setPageSize,
41
+ } = usePagination({
42
+ url: '/user-device',
43
+ paginationOptions: { pageSizeOptions: [...DEVICE_PAGE_SIZES] },
44
+ });
45
+
46
+ const handleForget = async (deviceId: number) => {
47
+ try {
48
+ await request({ url: `/user-device/${deviceId}`, method: 'DELETE' });
49
+ await refetch();
50
+ toast.success(t('forgetSuccess'));
51
+ } catch {
52
+ toast.error(t('forgetFailure'));
53
+ }
54
+ };
55
+
56
+ return (
57
+ <Card>
58
+ <CardHeader className="pb-4">
59
+ <CardTitle>{t('title')}</CardTitle>
60
+ <CardDescription>{t('description')}</CardDescription>
61
+ {/* Ver a nota equivalente em core/users/user-devices.tsx. */}
62
+ <CardDescription>{t('simulatedNotRecorded')}</CardDescription>
63
+ </CardHeader>
64
+ <CardContent className="px-6 pb-6">
65
+ <UserDevicesList
66
+ devices={devices as UserDevice[]}
67
+ onForget={handleForget}
68
+ emptyLabel={t('noDevices')}
69
+ t={t}
70
+ />
71
+ {totalItems > pageSize ? (
72
+ <div className="mt-4">
73
+ <PaginationFooter
74
+ currentPage={page}
75
+ pageSize={pageSize}
76
+ totalItems={totalItems}
77
+ onPageChange={setPage}
78
+ onPageSizeChange={(newSize) => {
79
+ setPageSize(newSize);
80
+ setPage(1);
81
+ }}
82
+ pageSizeOptions={[...DEVICE_PAGE_SIZES]}
83
+ />
84
+ </div>
85
+ ) : null}
86
+ </CardContent>
87
+ </Card>
88
+ );
89
+ }
@@ -18,6 +18,7 @@ import {
18
18
  FormMessage,
19
19
  } from '@/components/ui/form';
20
20
  import { Input } from '@/components/ui/input';
21
+ import { NameInput } from '@/components/ui/name-input';
21
22
  import { useFormDraft } from '@/hooks/use-form-draft';
22
23
  import { formatDateTime } from '@/lib/format-date';
23
24
  import { getPhotoUrl } from '@/lib/get-photo-url';
@@ -263,7 +264,7 @@ export function ProfileForm() {
263
264
  <FormItem>
264
265
  <FormLabel>{t('formNameLabel')}</FormLabel>
265
266
  <FormControl>
266
- <Input placeholder={t('formNamePlaceholder')} {...field} />
267
+ <NameInput placeholder={t('formNamePlaceholder')} {...field} />
267
268
  </FormControl>
268
269
  <FormMessage />
269
270
  </FormItem>
@@ -0,0 +1,186 @@
1
+ 'use client';
2
+
3
+ import { BrowserIcon, OsIcon } from '@/components/browser-os-icon';
4
+ import {
5
+ AlertDialog,
6
+ AlertDialogAction,
7
+ AlertDialogCancel,
8
+ AlertDialogContent,
9
+ AlertDialogDescription,
10
+ AlertDialogFooter,
11
+ AlertDialogHeader,
12
+ AlertDialogTitle,
13
+ } from '@/components/ui/alert-dialog';
14
+ import { Button } from '@/components/ui/button';
15
+ import {
16
+ Table,
17
+ TableBody,
18
+ TableCell,
19
+ TableHead,
20
+ TableHeader,
21
+ TableRow,
22
+ } from '@/components/ui/table';
23
+ import { formatDateTime } from '@/lib/format-date';
24
+ import { parseDeviceType } from '@/lib/session';
25
+ import { useApp } from '@hed-hog/next-app-provider';
26
+ import { Calendar, Clock, MapPin, Trash2 } from 'lucide-react';
27
+ import { useState } from 'react';
28
+
29
+ /**
30
+ * Precisa ir tanto para o `usePagination` quanto para o `PaginationFooter`: o
31
+ * hook usa `pageSizeOptions[0]` como tamanho inicial e o rodape tem uma lista
32
+ * propria por padrao. Passando so para um, o seletor mostra um numero e a
33
+ * tabela renderiza outro.
34
+ */
35
+ export const DEVICE_PAGE_SIZES = [10, 25, 50, 100] as const;
36
+
37
+ /**
38
+ * Um dispositivo conhecido. A `device_key` nao vem da API de proposito: e
39
+ * derivada do browser_id, entao expo-la entregaria o valor que identifica o
40
+ * dispositivo nas requisicoes.
41
+ */
42
+ export type UserDevice = {
43
+ id: number;
44
+ browser_name: string | null;
45
+ os_name: string | null;
46
+ user_agent: string | null;
47
+ last_ip: string | null;
48
+ last_seen_at: string;
49
+ created_at: string;
50
+ };
51
+
52
+ type Props = {
53
+ devices: UserDevice[];
54
+ /** Ausente deixa a lista somente leitura (sem coluna de acao). */
55
+ onForget?: (deviceId: number) => Promise<void> | void;
56
+ emptyLabel: string;
57
+ t: (key: string) => string;
58
+ };
59
+
60
+ /**
61
+ * Lista de dispositivos, compartilhada entre a aba de Conta (o proprio usuario)
62
+ * e o sheet de usuarios do admin. So apresentacao: quem busca os dados e quem
63
+ * remove fica com quem usa, porque as duas telas leem de origens diferentes —
64
+ * a Conta chama /user-device, o sheet recebe embutido no detalhe do usuario.
65
+ */
66
+ export function UserDevicesList({ devices, onForget, emptyLabel, t }: Props) {
67
+ const { currentLocaleCode, getSettingValue } = useApp();
68
+ const [deviceToForget, setDeviceToForget] = useState<number | null>(null);
69
+
70
+ if (!devices.length) {
71
+ return (
72
+ <div className="rounded-md border py-8 text-center text-sm text-muted-foreground">
73
+ {emptyLabel}
74
+ </div>
75
+ );
76
+ }
77
+
78
+ const stamp = (value: string) =>
79
+ formatDateTime(String(value), getSettingValue, currentLocaleCode);
80
+
81
+ return (
82
+ <>
83
+ <div className="rounded-md border">
84
+ <Table>
85
+ <TableHeader>
86
+ <TableRow>
87
+ <TableHead>{t('device')}</TableHead>
88
+ <TableHead>{t('browser')}</TableHead>
89
+ <TableHead>{t('lastIp')}</TableHead>
90
+ <TableHead>{t('lastSeen')}</TableHead>
91
+ <TableHead>{t('firstSeen')}</TableHead>
92
+ {onForget ? (
93
+ <TableHead className="text-right">{t('action')}</TableHead>
94
+ ) : null}
95
+ </TableRow>
96
+ </TableHeader>
97
+ <TableBody>
98
+ {devices.map((device) => (
99
+ <TableRow key={device.id}>
100
+ <TableCell>
101
+ <div className="flex items-center gap-2">
102
+ <OsIcon name={device.os_name} size={16} />
103
+ <div>
104
+ <p className="font-medium capitalize">
105
+ {parseDeviceType(device.user_agent ?? '')}
106
+ </p>
107
+ <p className="text-xs text-muted-foreground">
108
+ {device.os_name || t('unknown')}
109
+ </p>
110
+ </div>
111
+ </div>
112
+ </TableCell>
113
+ <TableCell>
114
+ <div className="flex items-center gap-2">
115
+ <BrowserIcon name={device.browser_name} size={16} />
116
+ <span>{device.browser_name || t('unknown')}</span>
117
+ </div>
118
+ </TableCell>
119
+ <TableCell>
120
+ <div className="flex items-center gap-2">
121
+ <MapPin className="size-4 text-muted-foreground" />
122
+ <span className="text-sm">
123
+ {device.last_ip === '::1'
124
+ ? '127.0.0.1'
125
+ : device.last_ip || '—'}
126
+ </span>
127
+ </div>
128
+ </TableCell>
129
+ <TableCell>
130
+ <div className="flex items-center gap-2">
131
+ <Clock className="size-4 text-muted-foreground" />
132
+ <span className="text-sm">{stamp(device.last_seen_at)}</span>
133
+ </div>
134
+ </TableCell>
135
+ <TableCell>
136
+ <div className="flex items-center gap-2">
137
+ <Calendar className="size-4 text-muted-foreground" />
138
+ <span className="text-sm">{stamp(device.created_at)}</span>
139
+ </div>
140
+ </TableCell>
141
+ {onForget ? (
142
+ <TableCell className="text-right">
143
+ <Button
144
+ variant="ghost"
145
+ size="sm"
146
+ className="cursor-pointer"
147
+ onClick={() => setDeviceToForget(device.id)}
148
+ >
149
+ <Trash2 className="mr-2 size-4" />
150
+ {t('forget')}
151
+ </Button>
152
+ </TableCell>
153
+ ) : null}
154
+ </TableRow>
155
+ ))}
156
+ </TableBody>
157
+ </Table>
158
+ </div>
159
+
160
+ <AlertDialog
161
+ open={deviceToForget !== null}
162
+ onOpenChange={(open) => !open && setDeviceToForget(null)}
163
+ >
164
+ <AlertDialogContent>
165
+ <AlertDialogHeader>
166
+ <AlertDialogTitle>{t('forgetTitle')}</AlertDialogTitle>
167
+ <AlertDialogDescription>
168
+ {t('forgetDescription')}
169
+ </AlertDialogDescription>
170
+ </AlertDialogHeader>
171
+ <AlertDialogFooter>
172
+ <AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
173
+ <AlertDialogAction
174
+ onClick={async () => {
175
+ if (deviceToForget !== null) await onForget?.(deviceToForget);
176
+ setDeviceToForget(null);
177
+ }}
178
+ >
179
+ {t('forget')}
180
+ </AlertDialogAction>
181
+ </AlertDialogFooter>
182
+ </AlertDialogContent>
183
+ </AlertDialog>
184
+ </>
185
+ );
186
+ }
@@ -0,0 +1,5 @@
1
+ import { KnownDevices } from '../components/known-devices';
2
+
3
+ export default function DevicesPage() {
4
+ return <KnownDevices />;
5
+ }
@@ -8,6 +8,7 @@ import {
8
8
  Lock,
9
9
  LucideIcon,
10
10
  Mail,
11
+ MonitorSmartphone,
11
12
  Shield,
12
13
  Smartphone,
13
14
  User,
@@ -38,6 +39,7 @@ export default function AccountLayout({ children }: LayoutProps) {
38
39
  { value: '2fa', label: t('tab2fa'), icon: Shield },
39
40
  { value: 'accounts', label: t('tabAccounts'), icon: LinkIcon },
40
41
  { value: 'sessions', label: t('tabSessions'), icon: Smartphone },
42
+ { value: 'devices', label: t('tabDevices'), icon: MonitorSmartphone },
41
43
  { value: 'tokens', label: t('tabTokens'), icon: KeyRound },
42
44
  ];
43
45