@hed-hog/core 0.0.390 → 0.0.393

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/README.md +39 -0
  2. package/dist/access-log/access-log.controller.d.ts.map +1 -1
  3. package/dist/access-log/access-log.controller.js +1 -1
  4. package/dist/access-log/access-log.controller.js.map +1 -1
  5. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  6. package/dist/access-log/access-log.interceptor.js +8 -5
  7. package/dist/access-log/access-log.interceptor.js.map +1 -1
  8. package/dist/access-log/access-log.service.d.ts +4 -0
  9. package/dist/access-log/access-log.service.d.ts.map +1 -1
  10. package/dist/access-log/access-log.service.js +31 -4
  11. package/dist/access-log/access-log.service.js.map +1 -1
  12. package/dist/access-log/dto/query-access-log.dto.d.ts +2 -0
  13. package/dist/access-log/dto/query-access-log.dto.d.ts.map +1 -1
  14. package/dist/access-log/dto/query-access-log.dto.js +5 -0
  15. package/dist/access-log/dto/query-access-log.dto.js.map +1 -1
  16. package/dist/ai/ai.controller.d.ts +12 -8
  17. package/dist/ai/ai.controller.d.ts.map +1 -1
  18. package/dist/ai/ai.controller.js +17 -2
  19. package/dist/ai/ai.controller.js.map +1 -1
  20. package/dist/ai/ai.module.d.ts.map +1 -1
  21. package/dist/ai/ai.module.js +3 -2
  22. package/dist/ai/ai.module.js.map +1 -1
  23. package/dist/ai/ai.service.d.ts +30 -1
  24. package/dist/ai/ai.service.d.ts.map +1 -1
  25. package/dist/ai/ai.service.js +97 -3
  26. package/dist/ai/ai.service.js.map +1 -1
  27. package/dist/ai/dto/richtext-edit-html.dto.d.ts +5 -0
  28. package/dist/ai/dto/richtext-edit-html.dto.d.ts.map +1 -0
  29. package/dist/ai/dto/richtext-edit-html.dto.js +27 -0
  30. package/dist/ai/dto/richtext-edit-html.dto.js.map +1 -0
  31. package/dist/ai/richtext-ai.prompts.d.ts +5 -0
  32. package/dist/ai/richtext-ai.prompts.d.ts.map +1 -0
  33. package/dist/ai/richtext-ai.prompts.js +57 -0
  34. package/dist/ai/richtext-ai.prompts.js.map +1 -0
  35. package/dist/ai/richtext-ai.service.d.ts +34 -0
  36. package/dist/ai/richtext-ai.service.d.ts.map +1 -0
  37. package/dist/ai/richtext-ai.service.js +95 -0
  38. package/dist/ai/richtext-ai.service.js.map +1 -0
  39. package/dist/ai-instruction/ai-instruction.controller.d.ts +5 -5
  40. package/dist/auth/auth.controller.d.ts +66 -7
  41. package/dist/auth/auth.controller.d.ts.map +1 -1
  42. package/dist/auth/auth.controller.js +67 -23
  43. package/dist/auth/auth.controller.js.map +1 -1
  44. package/dist/auth/auth.module.d.ts.map +1 -1
  45. package/dist/auth/auth.module.js +2 -0
  46. package/dist/auth/auth.module.js.map +1 -1
  47. package/dist/auth/auth.service.d.ts +136 -4
  48. package/dist/auth/auth.service.d.ts.map +1 -1
  49. package/dist/auth/auth.service.js +246 -48
  50. package/dist/auth/auth.service.js.map +1 -1
  51. package/dist/auth/dto/confirm-email.dto.d.ts +4 -0
  52. package/dist/auth/dto/confirm-email.dto.d.ts.map +1 -0
  53. package/dist/auth/dto/confirm-email.dto.js +22 -0
  54. package/dist/auth/dto/confirm-email.dto.js.map +1 -0
  55. package/dist/auth/dto/resend-confirmation.dto.d.ts +8 -0
  56. package/dist/auth/dto/resend-confirmation.dto.d.ts.map +1 -0
  57. package/dist/auth/dto/resend-confirmation.dto.js +36 -0
  58. package/dist/auth/dto/resend-confirmation.dto.js.map +1 -0
  59. package/dist/authorize/authorize.controller.d.ts +1 -1
  60. package/dist/authorize/authorize.controller.d.ts.map +1 -1
  61. package/dist/authorize/authorize.controller.js +4 -2
  62. package/dist/authorize/authorize.controller.js.map +1 -1
  63. package/dist/authorize/authorize.service.d.ts +7 -0
  64. package/dist/authorize/authorize.service.d.ts.map +1 -1
  65. package/dist/authorize/authorize.service.js +1 -1
  66. package/dist/authorize/authorize.service.js.map +1 -1
  67. package/dist/challenge/challenge.service.d.ts +18 -5
  68. package/dist/challenge/challenge.service.d.ts.map +1 -1
  69. package/dist/challenge/challenge.service.js +50 -3
  70. package/dist/challenge/challenge.service.js.map +1 -1
  71. package/dist/core.module.d.ts.map +1 -1
  72. package/dist/core.module.js +17 -0
  73. package/dist/core.module.js.map +1 -1
  74. package/dist/dashboard/dashboard/dashboard.controller.d.ts +6 -6
  75. package/dist/dashboard/dashboard/dashboard.service.d.ts +6 -6
  76. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +4 -4
  77. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +4 -4
  78. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +1 -1
  79. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +1 -1
  80. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +2 -2
  81. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +2 -2
  82. package/dist/dashboard/dashboard-item/dashboard-item.controller.d.ts +2 -2
  83. package/dist/dashboard/dashboard-item/dashboard-item.service.d.ts +2 -2
  84. package/dist/file/file.controller.d.ts +8 -1
  85. package/dist/file/file.controller.d.ts.map +1 -1
  86. package/dist/file/file.controller.js +45 -7
  87. package/dist/file/file.controller.js.map +1 -1
  88. package/dist/file/file.service.d.ts +128 -1
  89. package/dist/file/file.service.d.ts.map +1 -1
  90. package/dist/file/file.service.js +319 -8
  91. package/dist/file/file.service.js.map +1 -1
  92. package/dist/file/image-optimization.service.d.ts +43 -0
  93. package/dist/file/image-optimization.service.d.ts.map +1 -1
  94. package/dist/file/image-optimization.service.js +154 -0
  95. package/dist/file/image-optimization.service.js.map +1 -1
  96. package/dist/file/provider/abstract.provider.d.ts +16 -0
  97. package/dist/file/provider/abstract.provider.d.ts.map +1 -1
  98. package/dist/file/provider/abstract.provider.js +4 -0
  99. package/dist/file/provider/abstract.provider.js.map +1 -1
  100. package/dist/file/provider/gcs.provider.d.ts +2 -0
  101. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  102. package/dist/file/provider/gcs.provider.js +10 -0
  103. package/dist/file/provider/gcs.provider.js.map +1 -1
  104. package/dist/file/provider/local.provider.d.ts +1 -0
  105. package/dist/file/provider/local.provider.d.ts.map +1 -1
  106. package/dist/file/provider/local.provider.js +13 -0
  107. package/dist/file/provider/local.provider.js.map +1 -1
  108. package/dist/file/provider/s3.provider.d.ts +5 -0
  109. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  110. package/dist/file/provider/s3.provider.js +15 -0
  111. package/dist/file/provider/s3.provider.js.map +1 -1
  112. package/dist/file/provider/spaces.provider.d.ts +5 -0
  113. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  114. package/dist/file/provider/spaces.provider.js +16 -0
  115. package/dist/file/provider/spaces.provider.js.map +1 -1
  116. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts +4 -0
  117. package/dist/impersonation/dto/redeem-impersonation.dto.d.ts.map +1 -0
  118. package/dist/impersonation/dto/redeem-impersonation.dto.js +23 -0
  119. package/dist/impersonation/dto/redeem-impersonation.dto.js.map +1 -0
  120. package/dist/impersonation/dto/start-impersonation.dto.d.ts +7 -0
  121. package/dist/impersonation/dto/start-impersonation.dto.d.ts.map +1 -0
  122. package/dist/impersonation/dto/start-impersonation.dto.js +27 -0
  123. package/dist/impersonation/dto/start-impersonation.dto.js.map +1 -0
  124. package/dist/impersonation/impersonation-restrictions.d.ts +26 -0
  125. package/dist/impersonation/impersonation-restrictions.d.ts.map +1 -0
  126. package/dist/impersonation/impersonation-restrictions.js +76 -0
  127. package/dist/impersonation/impersonation-restrictions.js.map +1 -0
  128. package/dist/impersonation/impersonation.controller.d.ts +90 -0
  129. package/dist/impersonation/impersonation.controller.d.ts.map +1 -0
  130. package/dist/impersonation/impersonation.controller.js +135 -0
  131. package/dist/impersonation/impersonation.controller.js.map +1 -0
  132. package/dist/impersonation/impersonation.module.d.ts +8 -0
  133. package/dist/impersonation/impersonation.module.d.ts.map +1 -0
  134. package/dist/impersonation/impersonation.module.js +44 -0
  135. package/dist/impersonation/impersonation.module.js.map +1 -0
  136. package/dist/impersonation/impersonation.service.d.ts +164 -0
  137. package/dist/impersonation/impersonation.service.d.ts.map +1 -0
  138. package/dist/impersonation/impersonation.service.js +408 -0
  139. package/dist/impersonation/impersonation.service.js.map +1 -0
  140. package/dist/index.d.ts +17 -0
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +19 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/integration/services/outbox-polling.coordinator.d.ts +2 -0
  145. package/dist/integration/services/outbox-polling.coordinator.d.ts.map +1 -1
  146. package/dist/integration/services/outbox-polling.coordinator.js +50 -30
  147. package/dist/integration/services/outbox-polling.coordinator.js.map +1 -1
  148. package/dist/integration/services/outbox.service.d.ts.map +1 -1
  149. package/dist/integration/services/outbox.service.js +5 -1
  150. package/dist/integration/services/outbox.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  152. package/dist/integration-profile/integration-profile.secrets.js +3 -0
  153. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  154. package/dist/integration-profile/integration-profile.utils.d.ts +14 -0
  155. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  156. package/dist/integration-profile/integration-profile.utils.js +26 -0
  157. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  158. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -1
  159. package/dist/integration-profile/testers/email.testers.js +28 -0
  160. package/dist/integration-profile/testers/email.testers.js.map +1 -1
  161. package/dist/language/en.json +11 -0
  162. package/dist/language/pt.json +11 -0
  163. package/dist/mail/dto/send.dto.d.ts +38 -1
  164. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  165. package/dist/mail/dto/send.dto.js +10 -0
  166. package/dist/mail/dto/send.dto.js.map +1 -1
  167. package/dist/mail/mail.controller.d.ts +1 -0
  168. package/dist/mail/mail.controller.d.ts.map +1 -1
  169. package/dist/mail/mail.module.d.ts.map +1 -1
  170. package/dist/mail/mail.module.js +2 -0
  171. package/dist/mail/mail.module.js.map +1 -1
  172. package/dist/mail/mail.service.d.ts +48 -4
  173. package/dist/mail/mail.service.d.ts.map +1 -1
  174. package/dist/mail/mail.service.js +187 -35
  175. package/dist/mail/mail.service.js.map +1 -1
  176. package/dist/mail-delivery/mail-delivery-provision.service.d.ts +21 -0
  177. package/dist/mail-delivery/mail-delivery-provision.service.d.ts.map +1 -0
  178. package/dist/mail-delivery/mail-delivery-provision.service.js +64 -0
  179. package/dist/mail-delivery/mail-delivery-provision.service.js.map +1 -0
  180. package/dist/mail-delivery/mail-delivery.constants.d.ts +17 -0
  181. package/dist/mail-delivery/mail-delivery.constants.d.ts.map +1 -0
  182. package/dist/mail-delivery/mail-delivery.constants.js +31 -0
  183. package/dist/mail-delivery/mail-delivery.constants.js.map +1 -0
  184. package/dist/mail-delivery/mail-delivery.controller.d.ts +19 -0
  185. package/dist/mail-delivery/mail-delivery.controller.d.ts.map +1 -0
  186. package/dist/mail-delivery/mail-delivery.controller.js +53 -0
  187. package/dist/mail-delivery/mail-delivery.controller.js.map +1 -0
  188. package/dist/mail-delivery/mail-delivery.module.d.ts +7 -0
  189. package/dist/mail-delivery/mail-delivery.module.d.ts.map +1 -0
  190. package/dist/mail-delivery/mail-delivery.module.js +36 -0
  191. package/dist/mail-delivery/mail-delivery.module.js.map +1 -0
  192. package/dist/mail-delivery/mail-delivery.service.d.ts +38 -0
  193. package/dist/mail-delivery/mail-delivery.service.d.ts.map +1 -0
  194. package/dist/mail-delivery/mail-delivery.service.js +140 -0
  195. package/dist/mail-delivery/mail-delivery.service.js.map +1 -0
  196. package/dist/mail-delivery/ses-event.parser.d.ts +27 -0
  197. package/dist/mail-delivery/ses-event.parser.d.ts.map +1 -0
  198. package/dist/mail-delivery/ses-event.parser.js +131 -0
  199. package/dist/mail-delivery/ses-event.parser.js.map +1 -0
  200. package/dist/mail-delivery/ses-webhook.service.d.ts +58 -0
  201. package/dist/mail-delivery/ses-webhook.service.d.ts.map +1 -0
  202. package/dist/mail-delivery/ses-webhook.service.js +214 -0
  203. package/dist/mail-delivery/ses-webhook.service.js.map +1 -0
  204. package/dist/mail-delivery/sns-message.util.d.ts +38 -0
  205. package/dist/mail-delivery/sns-message.util.d.ts.map +1 -0
  206. package/dist/mail-delivery/sns-message.util.js +109 -0
  207. package/dist/mail-delivery/sns-message.util.js.map +1 -0
  208. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts +5 -0
  209. package/dist/mail-sent/dto/query-mail-sent-list.dto.d.ts.map +1 -1
  210. package/dist/mail-sent/dto/query-mail-sent-list.dto.js +6 -0
  211. package/dist/mail-sent/dto/query-mail-sent-list.dto.js.map +1 -1
  212. package/dist/mail-sent/mail-sent.controller.d.ts +39 -0
  213. package/dist/mail-sent/mail-sent.controller.d.ts.map +1 -1
  214. package/dist/mail-sent/mail-sent.service.d.ts +39 -0
  215. package/dist/mail-sent/mail-sent.service.d.ts.map +1 -1
  216. package/dist/mail-sent/mail-sent.service.js +45 -4
  217. package/dist/mail-sent/mail-sent.service.js.map +1 -1
  218. package/dist/mailbox/mailbox-fetcher.registry.d.ts +100 -0
  219. package/dist/mailbox/mailbox-fetcher.registry.d.ts.map +1 -0
  220. package/dist/mailbox/mailbox-fetcher.registry.js +59 -0
  221. package/dist/mailbox/mailbox-fetcher.registry.js.map +1 -0
  222. package/dist/mailbox/mailbox.module.d.ts +8 -0
  223. package/dist/mailbox/mailbox.module.d.ts.map +1 -0
  224. package/dist/mailbox/mailbox.module.js +27 -0
  225. package/dist/mailbox/mailbox.module.js.map +1 -0
  226. package/dist/meeting/meeting-provider.registry.d.ts +155 -0
  227. package/dist/meeting/meeting-provider.registry.d.ts.map +1 -0
  228. package/dist/meeting/meeting-provider.registry.js +53 -0
  229. package/dist/meeting/meeting-provider.registry.js.map +1 -0
  230. package/dist/meeting/meeting.constants.d.ts +47 -0
  231. package/dist/meeting/meeting.constants.d.ts.map +1 -0
  232. package/dist/meeting/meeting.constants.js +21 -0
  233. package/dist/meeting/meeting.constants.js.map +1 -0
  234. package/dist/meeting/meeting.module.d.ts +7 -0
  235. package/dist/meeting/meeting.module.d.ts.map +1 -0
  236. package/dist/meeting/meeting.module.js +26 -0
  237. package/dist/meeting/meeting.module.js.map +1 -0
  238. package/dist/middlewares/locale-injection.middleware.d.ts +12 -0
  239. package/dist/middlewares/locale-injection.middleware.d.ts.map +1 -1
  240. package/dist/middlewares/locale-injection.middleware.js +20 -4
  241. package/dist/middlewares/locale-injection.middleware.js.map +1 -1
  242. package/dist/oauth/providers/google.provider.d.ts +7 -0
  243. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  244. package/dist/oauth/providers/google.provider.js +43 -13
  245. package/dist/oauth/providers/google.provider.js.map +1 -1
  246. package/dist/observability/monitored-cron.d.ts +34 -0
  247. package/dist/observability/monitored-cron.d.ts.map +1 -0
  248. package/dist/observability/monitored-cron.js +118 -0
  249. package/dist/observability/monitored-cron.js.map +1 -0
  250. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts +12 -0
  251. package/dist/pairing/dto/approve-reverse-pairing.dto.d.ts.map +1 -0
  252. package/dist/pairing/dto/approve-reverse-pairing.dto.js +30 -0
  253. package/dist/pairing/dto/approve-reverse-pairing.dto.js.map +1 -0
  254. package/dist/pairing/dto/claim-pairing.dto.d.ts +11 -0
  255. package/dist/pairing/dto/claim-pairing.dto.d.ts.map +1 -0
  256. package/dist/pairing/dto/claim-pairing.dto.js +35 -0
  257. package/dist/pairing/dto/claim-pairing.dto.js.map +1 -0
  258. package/dist/pairing/dto/confirm-pairing.dto.d.ts +10 -0
  259. package/dist/pairing/dto/confirm-pairing.dto.d.ts.map +1 -0
  260. package/dist/pairing/dto/confirm-pairing.dto.js +24 -0
  261. package/dist/pairing/dto/confirm-pairing.dto.js.map +1 -0
  262. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts +5 -0
  263. package/dist/pairing/dto/poll-reverse-pairing.dto.d.ts.map +1 -0
  264. package/dist/pairing/dto/poll-reverse-pairing.dto.js +24 -0
  265. package/dist/pairing/dto/poll-reverse-pairing.dto.js.map +1 -0
  266. package/dist/pairing/dto/start-pairing.dto.d.ts +11 -0
  267. package/dist/pairing/dto/start-pairing.dto.d.ts.map +1 -0
  268. package/dist/pairing/dto/start-pairing.dto.js +30 -0
  269. package/dist/pairing/dto/start-pairing.dto.js.map +1 -0
  270. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts +24 -0
  271. package/dist/pairing/dto/start-reverse-pairing.dto.d.ts.map +1 -0
  272. package/dist/pairing/dto/start-reverse-pairing.dto.js +47 -0
  273. package/dist/pairing/dto/start-reverse-pairing.dto.js.map +1 -0
  274. package/dist/pairing/pairing.controller.d.ts +118 -0
  275. package/dist/pairing/pairing.controller.d.ts.map +1 -0
  276. package/dist/pairing/pairing.controller.js +296 -0
  277. package/dist/pairing/pairing.controller.js.map +1 -0
  278. package/dist/pairing/pairing.module.d.ts +10 -0
  279. package/dist/pairing/pairing.module.d.ts.map +1 -0
  280. package/dist/pairing/pairing.module.js +40 -0
  281. package/dist/pairing/pairing.module.js.map +1 -0
  282. package/dist/pairing/pairing.service.d.ts +238 -0
  283. package/dist/pairing/pairing.service.d.ts.map +1 -0
  284. package/dist/pairing/pairing.service.js +495 -0
  285. package/dist/pairing/pairing.service.js.map +1 -0
  286. package/dist/pdf/pdf.service.d.ts +3 -6
  287. package/dist/pdf/pdf.service.d.ts.map +1 -1
  288. package/dist/pdf/pdf.service.js +8 -20
  289. package/dist/pdf/pdf.service.js.map +1 -1
  290. package/dist/profile/profile.controller.d.ts +11 -0
  291. package/dist/profile/profile.controller.d.ts.map +1 -1
  292. package/dist/profile/profile.service.d.ts +11 -0
  293. package/dist/profile/profile.service.d.ts.map +1 -1
  294. package/dist/profile/profile.service.js +20 -11
  295. package/dist/profile/profile.service.js.map +1 -1
  296. package/dist/redis/redis-pubsub.d.ts.map +1 -1
  297. package/dist/redis/redis-pubsub.js +44 -0
  298. package/dist/redis/redis-pubsub.js.map +1 -1
  299. package/dist/role/guards/role.guard.d.ts +5 -0
  300. package/dist/role/guards/role.guard.d.ts.map +1 -1
  301. package/dist/role/guards/role.guard.js +41 -39
  302. package/dist/role/guards/role.guard.js.map +1 -1
  303. package/dist/session/session.controller.d.ts +2 -0
  304. package/dist/session/session.controller.d.ts.map +1 -1
  305. package/dist/session/session.service.d.ts +39 -0
  306. package/dist/session/session.service.d.ts.map +1 -1
  307. package/dist/session/session.service.js +91 -8
  308. package/dist/session/session.service.js.map +1 -1
  309. package/dist/setting/brand-asset.service.d.ts +48 -0
  310. package/dist/setting/brand-asset.service.d.ts.map +1 -0
  311. package/dist/setting/brand-asset.service.js +130 -0
  312. package/dist/setting/brand-asset.service.js.map +1 -0
  313. package/dist/setting/setting.module.d.ts.map +1 -1
  314. package/dist/setting/setting.module.js +3 -2
  315. package/dist/setting/setting.module.js.map +1 -1
  316. package/dist/setting/setting.service.d.ts +33 -0
  317. package/dist/setting/setting.service.d.ts.map +1 -1
  318. package/dist/setting/setting.service.js +88 -0
  319. package/dist/setting/setting.service.js.map +1 -1
  320. package/dist/token/token.service.d.ts +23 -2
  321. package/dist/token/token.service.d.ts.map +1 -1
  322. package/dist/token/token.service.js +39 -4
  323. package/dist/token/token.service.js.map +1 -1
  324. package/dist/user/user.controller.d.ts +8 -22
  325. package/dist/user/user.controller.d.ts.map +1 -1
  326. package/dist/user/user.service.d.ts +44 -24
  327. package/dist/user/user.service.d.ts.map +1 -1
  328. package/dist/user/user.service.js +89 -6
  329. package/dist/user/user.service.js.map +1 -1
  330. package/dist/user-device/user-device.controller.d.ts +54 -0
  331. package/dist/user-device/user-device.controller.d.ts.map +1 -0
  332. package/dist/user-device/user-device.controller.js +103 -0
  333. package/dist/user-device/user-device.controller.js.map +1 -0
  334. package/dist/user-device/user-device.module.d.ts +3 -0
  335. package/dist/user-device/user-device.module.d.ts.map +1 -0
  336. package/dist/user-device/user-device.module.js +26 -0
  337. package/dist/user-device/user-device.module.js.map +1 -0
  338. package/dist/user-device/user-device.service.d.ts +71 -0
  339. package/dist/user-device/user-device.service.d.ts.map +1 -0
  340. package/dist/user-device/user-device.service.js +158 -0
  341. package/dist/user-device/user-device.service.js.map +1 -0
  342. package/dist/utils/device-key.d.ts +23 -0
  343. package/dist/utils/device-key.d.ts.map +1 -0
  344. package/dist/utils/device-key.js +31 -0
  345. package/dist/utils/device-key.js.map +1 -0
  346. package/dist/utils/geo-ip.d.ts +38 -0
  347. package/dist/utils/geo-ip.d.ts.map +1 -0
  348. package/dist/utils/geo-ip.js +122 -0
  349. package/dist/utils/geo-ip.js.map +1 -0
  350. package/dist/utils/locale-context.d.ts +10 -7
  351. package/dist/utils/locale-context.d.ts.map +1 -1
  352. package/dist/utils/locale-context.js +14 -8
  353. package/dist/utils/locale-context.js.map +1 -1
  354. package/dist/utils/security-event-vars.d.ts +56 -0
  355. package/dist/utils/security-event-vars.d.ts.map +1 -0
  356. package/dist/utils/security-event-vars.js +109 -0
  357. package/dist/utils/security-event-vars.js.map +1 -0
  358. package/dist/utils/user-agent.d.ts +19 -0
  359. package/dist/utils/user-agent.d.ts.map +1 -0
  360. package/dist/utils/user-agent.js +36 -0
  361. package/dist/utils/user-agent.js.map +1 -0
  362. package/dist/webhook-integration/webhook-integration.service.d.ts +5 -0
  363. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  364. package/dist/webhook-integration/webhook-integration.service.js +50 -4
  365. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  366. package/dist/webhook-integration/webhook-protocol.registry.d.ts +6 -0
  367. package/dist/webhook-integration/webhook-protocol.registry.d.ts.map +1 -1
  368. package/dist/webhook-integration/webhook-protocol.registry.js.map +1 -1
  369. package/hedhog/data/integration_provider.yaml +9 -0
  370. package/hedhog/data/mail.yaml +198 -62
  371. package/hedhog/data/mail_layout.yaml +43 -33
  372. package/hedhog/data/menu.yaml +2 -0
  373. package/hedhog/data/role.yaml +16 -0
  374. package/hedhog/data/route.yaml +172 -3
  375. package/hedhog/data/setting_group.yaml +178 -2
  376. package/hedhog/data/setting_subgroup.yaml +8 -0
  377. package/hedhog/frontend/app/_lib/hooks/use-core-access.test.tsx.ejs +125 -0
  378. package/hedhog/frontend/app/_lib/hooks/use-core-access.ts.ejs +29 -0
  379. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.test.ts.ejs +71 -0
  380. package/hedhog/frontend/app/_lib/hooks/use-impersonation-access.ts.ejs +32 -0
  381. package/hedhog/frontend/app/access-log/page.tsx.ejs +96 -22
  382. package/hedhog/frontend/app/account/components/active-sessions.tsx.ejs +17 -1
  383. package/hedhog/frontend/app/account/components/known-devices.tsx.ejs +89 -0
  384. package/hedhog/frontend/app/account/components/profile-form.tsx.ejs +2 -1
  385. package/hedhog/frontend/app/account/components/user-devices-list.tsx.ejs +186 -0
  386. package/hedhog/frontend/app/account/devices/page.tsx.ejs +5 -0
  387. package/hedhog/frontend/app/account/layout.tsx.ejs +2 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.test.tsx.ejs +298 -0
  389. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +376 -1236
  390. package/hedhog/frontend/app/mail/log/page.tsx.ejs +210 -3
  391. package/hedhog/frontend/app/mail/template/page.tsx.ejs +190 -132
  392. package/hedhog/frontend/app/users/active-session.tsx.ejs +58 -11
  393. package/hedhog/frontend/app/users/page.tsx.ejs +69 -10
  394. package/hedhog/frontend/app/users/simulate-access-dialog.test.tsx.ejs +202 -0
  395. package/hedhog/frontend/app/users/simulate-access-dialog.tsx.ejs +170 -0
  396. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +19 -0
  397. package/hedhog/frontend/app/users/user-devices.tsx.ejs +89 -0
  398. package/hedhog/frontend/app/users/user-logs.tsx.ejs +13 -1
  399. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +46 -24
  400. package/hedhog/frontend/messages/en.json +146 -48
  401. package/hedhog/frontend/messages/pt.json +146 -48
  402. package/hedhog/table/access_log.yaml +7 -0
  403. package/hedhog/table/file.yaml +8 -0
  404. package/hedhog/table/mail_delivery_event.yaml +76 -0
  405. package/hedhog/table/mail_sent.yaml +23 -0
  406. package/hedhog/table/user_activity.yaml +9 -0
  407. package/hedhog/table/user_device.yaml +52 -0
  408. package/hedhog/table/user_session.yaml +20 -1
  409. package/package.json +6 -5
  410. package/src/access-log/access-log.controller.ts +1 -0
  411. package/src/access-log/access-log.interceptor.ts +5 -1
  412. package/src/access-log/access-log.service.spec.ts +33 -2
  413. package/src/access-log/access-log.service.ts +38 -0
  414. package/src/access-log/dto/query-access-log.dto.ts +5 -0
  415. package/src/ai/ai.controller.ts +9 -0
  416. package/src/ai/ai.module.ts +3 -2
  417. package/src/ai/ai.service.ts +150 -13
  418. package/src/ai/dto/richtext-edit-html.dto.ts +11 -0
  419. package/src/ai/richtext-ai.prompts.ts +57 -0
  420. package/src/ai/richtext-ai.service.ts +109 -0
  421. package/src/auth/auth.controller.ts +47 -8
  422. package/src/auth/auth.module.ts +2 -0
  423. package/src/auth/auth.service.spec.ts +383 -6
  424. package/src/auth/auth.service.ts +345 -56
  425. package/src/auth/dto/confirm-email.dto.ts +7 -0
  426. package/src/auth/dto/resend-confirmation.dto.ts +20 -0
  427. package/src/authorize/authorize.controller.ts +2 -0
  428. package/src/authorize/authorize.service.spec.ts +23 -0
  429. package/src/authorize/authorize.service.ts +8 -0
  430. package/src/challenge/challenge.service.spec.ts +73 -0
  431. package/src/challenge/challenge.service.ts +62 -3
  432. package/src/core.module.ts +19 -0
  433. package/src/file/file.controller.ts +54 -4
  434. package/src/file/file.service.spec.ts +275 -105
  435. package/src/file/file.service.ts +409 -14
  436. package/src/file/image-optimization.service.spec.ts +92 -0
  437. package/src/file/image-optimization.service.ts +202 -0
  438. package/src/file/provider/abstract.provider.ts +23 -0
  439. package/src/file/provider/gcs.provider.ts +17 -0
  440. package/src/file/provider/local.provider.ts +21 -0
  441. package/src/file/provider/s3.provider.ts +25 -0
  442. package/src/file/provider/spaces.provider.ts +26 -0
  443. package/src/impersonation/dto/redeem-impersonation.dto.ts +8 -0
  444. package/src/impersonation/dto/start-impersonation.dto.ts +13 -0
  445. package/src/impersonation/impersonation-restrictions.ts +84 -0
  446. package/src/impersonation/impersonation.controller.ts +109 -0
  447. package/src/impersonation/impersonation.module.ts +31 -0
  448. package/src/impersonation/impersonation.service.spec.ts +427 -0
  449. package/src/impersonation/impersonation.service.ts +577 -0
  450. package/src/index.ts +21 -0
  451. package/src/integration/services/outbox-polling.coordinator.spec.ts +90 -0
  452. package/src/integration/services/outbox-polling.coordinator.ts +62 -40
  453. package/src/integration/services/outbox.service.spec.ts +8 -0
  454. package/src/integration/services/outbox.service.ts +19 -13
  455. package/src/integration-profile/integration-profile.secrets.ts +3 -0
  456. package/src/integration-profile/integration-profile.utils.ts +38 -0
  457. package/src/integration-profile/testers/email.testers.ts +34 -1
  458. package/src/language/en.json +11 -0
  459. package/src/language/pt.json +11 -0
  460. package/src/mail/dto/send.dto.ts +46 -2
  461. package/src/mail/mail-template.spec.ts +79 -2
  462. package/src/mail/mail.module.ts +2 -0
  463. package/src/mail/mail.service.spec.ts +307 -6
  464. package/src/mail/mail.service.ts +227 -30
  465. package/src/mail-delivery/mail-delivery-provision.service.ts +66 -0
  466. package/src/mail-delivery/mail-delivery.constants.ts +31 -0
  467. package/src/mail-delivery/mail-delivery.controller.ts +38 -0
  468. package/src/mail-delivery/mail-delivery.module.ts +23 -0
  469. package/src/mail-delivery/mail-delivery.service.spec.ts +136 -0
  470. package/src/mail-delivery/mail-delivery.service.ts +152 -0
  471. package/src/mail-delivery/ses-event.parser.spec.ts +140 -0
  472. package/src/mail-delivery/ses-event.parser.ts +191 -0
  473. package/src/mail-delivery/ses-webhook.service.spec.ts +170 -0
  474. package/src/mail-delivery/ses-webhook.service.ts +240 -0
  475. package/src/mail-delivery/sns-message.util.spec.ts +163 -0
  476. package/src/mail-delivery/sns-message.util.ts +125 -0
  477. package/src/mail-sent/dto/query-mail-sent-list.dto.ts +15 -0
  478. package/src/mail-sent/mail-sent.service.ts +46 -2
  479. package/src/mailbox/mailbox-fetcher.registry.ts +132 -0
  480. package/src/mailbox/mailbox.module.ts +14 -0
  481. package/src/meeting/meeting-provider.registry.ts +193 -0
  482. package/src/meeting/meeting.constants.ts +53 -0
  483. package/src/meeting/meeting.module.ts +13 -0
  484. package/src/middlewares/locale-injection.middleware.ts +25 -6
  485. package/src/oauth/providers/google.provider.ts +49 -15
  486. package/src/observability/monitored-cron.spec.ts +102 -0
  487. package/src/observability/monitored-cron.ts +110 -0
  488. package/src/pairing/dto/approve-reverse-pairing.dto.ts +20 -0
  489. package/src/pairing/dto/claim-pairing.dto.ts +19 -0
  490. package/src/pairing/dto/confirm-pairing.dto.ts +14 -0
  491. package/src/pairing/dto/poll-reverse-pairing.dto.ts +9 -0
  492. package/src/pairing/dto/start-pairing.dto.ts +19 -0
  493. package/src/pairing/dto/start-reverse-pairing.dto.ts +45 -0
  494. package/src/pairing/pairing.controller.ts +246 -0
  495. package/src/pairing/pairing.module.ts +27 -0
  496. package/src/pairing/pairing.service.spec.ts +462 -0
  497. package/src/pairing/pairing.service.ts +614 -0
  498. package/src/pdf/pdf.service.ts +6 -24
  499. package/src/profile/profile.service.ts +26 -8
  500. package/src/redis/redis-pubsub.ts +11 -0
  501. package/src/role/guards/role.guard.spec.ts +75 -0
  502. package/src/role/guards/role.guard.ts +64 -46
  503. package/src/session/session.service.spec.ts +81 -0
  504. package/src/session/session.service.ts +93 -6
  505. package/src/setting/brand-asset.service.ts +137 -0
  506. package/src/setting/setting.module.ts +3 -2
  507. package/src/setting/setting.service.spec.ts +79 -1
  508. package/src/setting/setting.service.ts +109 -1
  509. package/src/token/token.service.spec.ts +62 -0
  510. package/src/token/token.service.ts +57 -3
  511. package/src/user/user.service.spec.ts +33 -2
  512. package/src/user/user.service.ts +108 -6
  513. package/src/user-device/user-device.controller.ts +77 -0
  514. package/src/user-device/user-device.module.ts +13 -0
  515. package/src/user-device/user-device.service.spec.ts +234 -0
  516. package/src/user-device/user-device.service.ts +168 -0
  517. package/src/utils/device-key.spec.ts +57 -0
  518. package/src/utils/device-key.ts +32 -0
  519. package/src/utils/geo-ip.spec.ts +134 -0
  520. package/src/utils/geo-ip.ts +93 -0
  521. package/src/utils/locale-context.spec.ts +27 -1
  522. package/src/utils/locale-context.ts +15 -12
  523. package/src/utils/security-event-vars.spec.ts +161 -0
  524. package/src/utils/security-event-vars.ts +153 -0
  525. package/src/utils/user-agent.spec.ts +37 -0
  526. package/src/utils/user-agent.ts +46 -0
  527. package/src/webhook-integration/webhook-integration.service.ts +57 -3
  528. package/src/webhook-integration/webhook-protocol.registry.ts +6 -0
@@ -0,0 +1,134 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+
3
+ jest.mock('geoip-lite', () => ({
4
+ __esModule: true,
5
+ lookup: jest.fn(),
6
+ }));
7
+
8
+ import * as geoip from 'geoip-lite';
9
+ import { isPrivateIp, lookupGeo, normalizeIp } from './geo-ip';
10
+
11
+ const mockLookup = geoip.lookup as unknown as jest.Mock;
12
+
13
+ const GEO_SAMPLE = {
14
+ range: [0, 1],
15
+ country: 'BR',
16
+ region: 'SP',
17
+ eu: '0',
18
+ timezone: 'America/Sao_Paulo',
19
+ city: 'Sao Paulo',
20
+ ll: [-23.5, -46.6],
21
+ metro: 0,
22
+ area: 10,
23
+ };
24
+
25
+ describe('normalizeIp', () => {
26
+ it('strips the IPv4-mapped IPv6 prefix', () => {
27
+ expect(normalizeIp('::ffff:189.1.2.3')).toBe('189.1.2.3');
28
+ expect(normalizeIp('::FFFF:189.1.2.3')).toBe('189.1.2.3');
29
+ });
30
+
31
+ it('strips a trailing port from IPv4 and brackets from IPv6', () => {
32
+ expect(normalizeIp('189.1.2.3:51000')).toBe('189.1.2.3');
33
+ expect(normalizeIp('[2001:db8::1]:443')).toBe('2001:db8::1');
34
+ });
35
+
36
+ it('keeps a bare IPv6 untouched and returns null for empty input', () => {
37
+ expect(normalizeIp('2001:db8::1')).toBe('2001:db8::1');
38
+ expect(normalizeIp('')).toBeNull();
39
+ expect(normalizeIp(' ')).toBeNull();
40
+ expect(normalizeIp(null)).toBeNull();
41
+ expect(normalizeIp(undefined)).toBeNull();
42
+ });
43
+ });
44
+
45
+ describe('isPrivateIp', () => {
46
+ it.each([
47
+ ['10.244.1.7'],
48
+ ['10.0.0.1'],
49
+ ['127.0.0.1'],
50
+ ['192.168.1.1'],
51
+ ['169.254.1.1'],
52
+ ['172.16.0.1'],
53
+ ['172.31.255.254'],
54
+ ['100.64.0.1'],
55
+ ['100.127.255.255'],
56
+ ['0.0.0.0'],
57
+ ['::1'],
58
+ ['::'],
59
+ ['fd00::1'],
60
+ ['fe80::1'],
61
+ ['unknown'],
62
+ [''],
63
+ ])('treats %s as private', (ip) => {
64
+ expect(isPrivateIp(ip)).toBe(true);
65
+ });
66
+
67
+ it.each([
68
+ ['8.8.8.8'],
69
+ ['189.1.2.3'],
70
+ // Only 172.16.0.0/12 is private: everything outside 16-31 is public.
71
+ ['172.15.0.1'],
72
+ ['172.32.0.1'],
73
+ ['100.63.255.255'],
74
+ ['100.128.0.1'],
75
+ ['2001:db8::1'],
76
+ ])('treats %s as public', (ip) => {
77
+ expect(isPrivateIp(ip)).toBe(false);
78
+ });
79
+
80
+ it('normalizes before deciding', () => {
81
+ expect(isPrivateIp('::ffff:10.244.1.7')).toBe(true);
82
+ expect(isPrivateIp('::ffff:8.8.8.8')).toBe(false);
83
+ });
84
+ });
85
+
86
+ describe('lookupGeo', () => {
87
+ beforeEach(() => {
88
+ mockLookup.mockReset();
89
+ });
90
+
91
+ it('never queries the database for a private address', () => {
92
+ expect(lookupGeo('10.244.1.7')).toBeNull();
93
+ expect(lookupGeo('172.20.0.1')).toBeNull();
94
+ expect(lookupGeo(null)).toBeNull();
95
+ expect(mockLookup).not.toHaveBeenCalled();
96
+ });
97
+
98
+ it('queries 172.32.0.1, which is public despite the 172 prefix', () => {
99
+ mockLookup.mockReturnValue(null);
100
+ lookupGeo('172.32.0.1');
101
+ expect(mockLookup).toHaveBeenCalledWith('172.32.0.1');
102
+ });
103
+
104
+ it('normalizes the address before the lookup', () => {
105
+ mockLookup.mockReturnValue(GEO_SAMPLE);
106
+ lookupGeo('::ffff:189.1.2.3');
107
+ expect(mockLookup).toHaveBeenCalledWith('189.1.2.3');
108
+ });
109
+
110
+ it('maps the four fields', () => {
111
+ mockLookup.mockReturnValue(GEO_SAMPLE);
112
+ expect(lookupGeo('189.1.2.3')).toEqual({
113
+ country: 'BR',
114
+ region: 'SP',
115
+ city: 'Sao Paulo',
116
+ timezone: 'America/Sao_Paulo',
117
+ });
118
+ });
119
+
120
+ it('turns missing fields into null instead of undefined', () => {
121
+ mockLookup.mockReturnValue({ ...GEO_SAMPLE, region: '', city: '', timezone: undefined });
122
+ expect(lookupGeo('189.1.2.3')).toEqual({
123
+ country: 'BR',
124
+ region: null,
125
+ city: null,
126
+ timezone: null,
127
+ });
128
+ });
129
+
130
+ it('returns null when the database has no entry', () => {
131
+ mockLookup.mockReturnValue(null);
132
+ expect(lookupGeo('8.8.8.8')).toBeNull();
133
+ });
134
+ });
@@ -0,0 +1,93 @@
1
+ import * as geoip from 'geoip-lite';
2
+
3
+ export interface GeoLocation {
4
+ /** ISO 3166-1 alpha-2, e.g. 'BR'. */
5
+ country: string | null;
6
+ /** Subdivision code, e.g. 'SP'. */
7
+ region: string | null;
8
+ city: string | null;
9
+ /** IANA timezone, e.g. 'America/Sao_Paulo'. */
10
+ timezone: string | null;
11
+ }
12
+
13
+ /**
14
+ * Strips the IPv4-mapped IPv6 prefix and any trailing port, and unwraps the
15
+ * bracket notation used for IPv6 hosts.
16
+ *
17
+ * Without this, `::ffff:189.1.2.3` — what a dual-stack socket reports — is
18
+ * looked up in the IPv6 database and always misses.
19
+ */
20
+ export function normalizeIp(ip?: string | null): string | null {
21
+ const raw = (ip ?? '').trim();
22
+ if (!raw) return null;
23
+
24
+ // `[2001:db8::1]:443` -> `2001:db8::1`
25
+ const bracketed = raw.match(/^\[(.+)\](?::\d+)?$/);
26
+ const value = bracketed ? bracketed[1] : raw;
27
+
28
+ const unmapped = value.replace(/^::ffff:/i, '');
29
+
30
+ // `189.1.2.3:51000` -> `189.1.2.3` (only for IPv4; a bare IPv6 has many colons)
31
+ const withoutPort = unmapped.match(/^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/);
32
+
33
+ return (withoutPort ? withoutPort[1] : unmapped) || null;
34
+ }
35
+
36
+ /**
37
+ * Loopback, private, link-local and CGNAT ranges — plus the placeholders the
38
+ * callers use when they could not resolve anything.
39
+ *
40
+ * The cluster pod CIDR (10.244.x.x) falls here, which is exactly what every
41
+ * ticket created before the client IP started being forwarded has stored.
42
+ *
43
+ * Careful with 172: only 172.16.0.0/12 is private, so the second octet has to
44
+ * be between 16 and 31. Blocking the whole `172.` prefix — as the copy in
45
+ * lms/platforma/handlers/lesson-heartbeat.handler.ts does — silently discards
46
+ * public addresses such as 172.32.5.9.
47
+ */
48
+ export function isPrivateIp(ip?: string | null): boolean {
49
+ const value = normalizeIp(ip);
50
+ if (!value) return true;
51
+
52
+ const lower = value.toLowerCase();
53
+ if (lower === 'unknown' || lower === '::' || lower === '0.0.0.0') return true;
54
+
55
+ // IPv6: loopback, unique local (fc00::/7) and link-local (fe80::/10).
56
+ if (lower === '::1') return true;
57
+ if (/^f[cd]/.test(lower) && lower.includes(':')) return true;
58
+ if (/^fe[89ab]/.test(lower) && lower.includes(':')) return true;
59
+
60
+ const v4 = lower.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
61
+ if (!v4) return false;
62
+
63
+ const [a, b] = [Number(v4[1]), Number(v4[2])];
64
+ if (a === 0 || a === 10 || a === 127) return true;
65
+ if (a === 172 && b >= 16 && b <= 31) return true;
66
+ if (a === 192 && b === 168) return true;
67
+ if (a === 169 && b === 254) return true;
68
+ // CGNAT 100.64.0.0/10
69
+ if (a === 100 && b >= 64 && b <= 127) return true;
70
+
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Offline lookup against the MaxMind-lite database bundled with geoip-lite.
76
+ *
77
+ * Returns null for empty, private or unknown addresses — never an invented
78
+ * country, which is what makes it safe to render the result directly.
79
+ */
80
+ export function lookupGeo(ip?: string | null): GeoLocation | null {
81
+ const value = normalizeIp(ip);
82
+ if (!value || isPrivateIp(value)) return null;
83
+
84
+ const geo = geoip.lookup(value);
85
+ if (!geo) return null;
86
+
87
+ return {
88
+ country: geo.country || null,
89
+ region: geo.region || null,
90
+ city: geo.city || null,
91
+ timezone: geo.timezone || null,
92
+ };
93
+ }
@@ -1,5 +1,9 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
- import { getLocaleFromContext, localeStorage } from './locale-context';
2
+ import {
3
+ getLocaleFromContext,
4
+ isLocaleDetectedFromContext,
5
+ localeStorage,
6
+ } from './locale-context';
3
7
 
4
8
  describe('locale-context', () => {
5
9
  it('retorna "en" quando não há contexto', () => {
@@ -19,4 +23,26 @@ describe('locale-context', () => {
19
23
  );
20
24
  expect(result).toBe('en');
21
25
  });
26
+
27
+ // A guarda que impede gravar o default do middleware como se fosse escolha
28
+ // do usuario. Sem ela, todo cliente sem `Accept-Language` congelaria `en`.
29
+ describe('isLocaleDetectedFromContext', () => {
30
+ it('é falso fora de qualquer contexto', () => {
31
+ expect(isLocaleDetectedFromContext()).toBe(false);
32
+ });
33
+
34
+ it('é falso quando o locale é apenas o default do middleware', () => {
35
+ const result = localeStorage.run({ locale: 'en', detected: false }, () =>
36
+ isLocaleDetectedFromContext(),
37
+ );
38
+ expect(result).toBe(false);
39
+ });
40
+
41
+ it('é verdadeiro quando o cliente declarou um idioma suportado', () => {
42
+ const result = localeStorage.run({ locale: 'pt', detected: true }, () =>
43
+ isLocaleDetectedFromContext(),
44
+ );
45
+ expect(result).toBe(true);
46
+ });
47
+ });
22
48
  });
@@ -1,12 +1,15 @@
1
- import { AsyncLocalStorage } from 'async_hooks';
2
-
3
- interface LocaleContext {
4
- locale: string;
5
- }
6
-
7
- export const localeStorage = new AsyncLocalStorage<LocaleContext>();
8
-
9
- export function getLocaleFromContext(): string {
10
- const context = localeStorage.getStore();
11
- return context?.locale || 'en';
12
- }
1
+ /**
2
+ * O contexto de locale mora em `@hed-hog/api-locale`, junto do middleware que o
3
+ * popula. Este arquivo continua existindo como reexport porque `@hed-hog/core`
4
+ * e a fachada que as outras libraries importam (`operations`, validators).
5
+ *
6
+ * Manter uma copia do `AsyncLocalStorage` aqui criaria DOIS storages: o
7
+ * middleware escreveria num e `getLocaleFromContext()` leria o outro, sempre
8
+ * vazio.
9
+ */
10
+ export {
11
+ getLocaleFromContext,
12
+ isLocaleDetectedFromContext,
13
+ localeStorage,
14
+ type LocaleContext,
15
+ } from '@hed-hog/api-locale';
@@ -0,0 +1,161 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+
3
+ jest.mock('geoip-lite', () => ({
4
+ __esModule: true,
5
+ lookup: jest.fn(),
6
+ }));
7
+
8
+ import * as geoip from 'geoip-lite';
9
+ import {
10
+ buildSecurityEventVars,
11
+ countryFlagEmoji,
12
+ formatEventDateTime,
13
+ } from './security-event-vars';
14
+
15
+ const mockLookup = geoip.lookup as unknown as jest.Mock;
16
+
17
+ const GEO_SAMPLE = {
18
+ range: [0, 1],
19
+ country: 'BR',
20
+ region: 'SP',
21
+ eu: '0',
22
+ timezone: 'America/Sao_Paulo',
23
+ city: 'Sao Paulo',
24
+ ll: [-23.5, -46.6],
25
+ metro: 0,
26
+ area: 10,
27
+ };
28
+
29
+ const CHROME_UA =
30
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36';
31
+
32
+ const AT = new Date('2026-08-08T15:04:00.000Z');
33
+
34
+ describe('countryFlagEmoji', () => {
35
+ it('monta o par de regional indicators', () => {
36
+ expect(countryFlagEmoji('BR')).toBe('\u{1F1E7}\u{1F1F7}');
37
+ expect(countryFlagEmoji('US')).toBe('\u{1F1FA}\u{1F1F8}');
38
+ });
39
+
40
+ it('aceita minúsculas e espaços', () => {
41
+ expect(countryFlagEmoji(' br ')).toBe('\u{1F1E7}\u{1F1F7}');
42
+ });
43
+
44
+ it('devolve string vazia para qualquer coisa que não seja um ISO2', () => {
45
+ expect(countryFlagEmoji('BRA')).toBe('');
46
+ expect(countryFlagEmoji('B')).toBe('');
47
+ expect(countryFlagEmoji('12')).toBe('');
48
+ expect(countryFlagEmoji('')).toBe('');
49
+ expect(countryFlagEmoji(null)).toBe('');
50
+ expect(countryFlagEmoji(undefined)).toBe('');
51
+ });
52
+ });
53
+
54
+ describe('formatEventDateTime', () => {
55
+ it('formata no fuso informado', () => {
56
+ // 15:04 UTC = 12:04 em America/Sao_Paulo.
57
+ expect(formatEventDateTime(AT, 'pt', 'America/Sao_Paulo')).toContain('12:04');
58
+ });
59
+
60
+ it('cai para America/Sao_Paulo sem fuso', () => {
61
+ expect(formatEventDateTime(AT, 'pt', null)).toBe(
62
+ formatEventDateTime(AT, 'pt', 'America/Sao_Paulo'),
63
+ );
64
+ });
65
+
66
+ it('não lança com locale ou fuso inválidos, que vêm de fora', () => {
67
+ expect(() => formatEventDateTime(AT, 'pt_BR!!', 'Nao/Existe')).not.toThrow();
68
+ expect(formatEventDateTime(AT, 'pt_BR!!', 'Nao/Existe')).toEqual(expect.any(String));
69
+ });
70
+ });
71
+
72
+ describe('buildSecurityEventVars', () => {
73
+ beforeEach(() => {
74
+ mockLookup.mockReset();
75
+ });
76
+
77
+ it('traduz IP e User-Agent nos campos do cartão', () => {
78
+ mockLookup.mockReturnValue(GEO_SAMPLE);
79
+
80
+ const vars = buildSecurityEventVars({
81
+ ipAddress: '189.1.2.3',
82
+ userAgent: CHROME_UA,
83
+ locale: 'pt',
84
+ at: AT,
85
+ });
86
+
87
+ expect(vars).toMatchObject({
88
+ ipAddress: '189.1.2.3',
89
+ userAgent: CHROME_UA,
90
+ browser: 'Chrome 151',
91
+ os: 'Windows 10',
92
+ location: 'Sao Paulo, Brasil',
93
+ countryFlag: '\u{1F1E7}\u{1F1F7}',
94
+ });
95
+ expect(vars.datetime).toContain('12:04');
96
+ });
97
+
98
+ it('localiza o nome do país pelo locale', () => {
99
+ mockLookup.mockReturnValue(GEO_SAMPLE);
100
+
101
+ expect(
102
+ buildSecurityEventVars({ ipAddress: '189.1.2.3', locale: 'en', at: AT }).location,
103
+ ).toBe('Sao Paulo, Brazil');
104
+ });
105
+
106
+ it('corta a versão do navegador no major', () => {
107
+ mockLookup.mockReturnValue(null);
108
+
109
+ expect(
110
+ buildSecurityEventVars({ ipAddress: '189.1.2.3', userAgent: CHROME_UA, at: AT }).browser,
111
+ ).toBe('Chrome 151');
112
+ });
113
+
114
+ it('devolve string vazia — nunca "Unknown" — para IP privado', () => {
115
+ const vars = buildSecurityEventVars({
116
+ ipAddress: '10.244.1.7',
117
+ userAgent: CHROME_UA,
118
+ locale: 'pt',
119
+ at: AT,
120
+ });
121
+
122
+ expect(mockLookup).not.toHaveBeenCalled();
123
+ expect(vars.location).toBe('');
124
+ expect(vars.countryFlag).toBe('');
125
+ // Sem geo o fuso cai no padrão, mas a data continua preenchida.
126
+ expect(vars.datetime).toEqual(expect.any(String));
127
+ });
128
+
129
+ it('zera browser e os quando o User-Agent não identifica nada', () => {
130
+ mockLookup.mockReturnValue(null);
131
+
132
+ const vars = buildSecurityEventVars({
133
+ ipAddress: '189.1.2.3',
134
+ userAgent: 'curl/8.4.0',
135
+ at: AT,
136
+ });
137
+
138
+ // O template usa {{#unless browser}}{{#unless os}} para cair no UA cru.
139
+ expect(vars.browser).toBe('');
140
+ expect(vars.os).toBe('');
141
+ expect(vars.userAgent).toBe('curl/8.4.0');
142
+ });
143
+
144
+ it('não devolve null em campo nenhum, mesmo sem entrada alguma', () => {
145
+ const vars = buildSecurityEventVars({ at: AT });
146
+
147
+ for (const [key, value] of Object.entries(vars)) {
148
+ expect(typeof value).toBe('string');
149
+ expect(value).not.toBeNull();
150
+ if (key !== 'datetime') expect(value).toBe('');
151
+ }
152
+ });
153
+
154
+ it('usa o fuso do geoip para o horário', () => {
155
+ mockLookup.mockReturnValue({ ...GEO_SAMPLE, timezone: 'UTC' });
156
+
157
+ expect(
158
+ buildSecurityEventVars({ ipAddress: '189.1.2.3', locale: 'pt', at: AT }).datetime,
159
+ ).toContain('15:04');
160
+ });
161
+ });
@@ -0,0 +1,153 @@
1
+ import { lookupGeo } from './geo-ip';
2
+ import { parseUserAgent } from './user-agent';
3
+
4
+ /**
5
+ * Fuso usado quando o geoip nao resolve (IP privado, base sem entrada). Sem
6
+ * ele o horario sairia em UTC, que e o pior dos mundos num alerta de seguranca:
7
+ * parece certo e esta tres horas errado.
8
+ */
9
+ const DEFAULT_TIME_ZONE = 'America/Sao_Paulo';
10
+
11
+ export interface SecurityEventContext {
12
+ ipAddress?: string | null;
13
+ userAgent?: string | null;
14
+ /** Codigo da tabela `locale` ('pt' | 'en' | 'es'), como chega do LocaleMiddleware. */
15
+ locale?: string | null;
16
+ /** Momento do evento. Default: agora. */
17
+ at?: Date;
18
+ /** Fuso quando o geoip nao resolve. Default: DEFAULT_TIME_ZONE. */
19
+ fallbackTimeZone?: string;
20
+ }
21
+
22
+ /**
23
+ * Variaveis prontas para os templates de e-mail de seguranca
24
+ * (`auth-login-new-device`, `auth-change-password-notification`).
25
+ *
26
+ * Todo campo e string, nunca null: `sendTemplatedMail` recebe um
27
+ * Record<string, string> e os templates usam `{{#if}}` para esconder a linha
28
+ * do cartao — string vazia e o que faz a linha sumir.
29
+ */
30
+ export interface SecurityEventVars {
31
+ ipAddress: string;
32
+ /** UA cru; o template so imprime quando nao conseguiu identificar navegador nem SO. */
33
+ userAgent: string;
34
+ /** "Chrome 151" */
35
+ browser: string;
36
+ /** "Windows 10" */
37
+ os: string;
38
+ /** "Sao Paulo, Brasil" */
39
+ location: string;
40
+ /** "🇧🇷" */
41
+ countryFlag: string;
42
+ /** "8 de agosto de 2026 as 12:04" */
43
+ datetime: string;
44
+ }
45
+
46
+ /**
47
+ * Par de regional indicators para um ISO 3166-1 alpha-2.
48
+ *
49
+ * Mesma conta do countryFlag() em apps/admin/src/components/browser-os-icon.tsx,
50
+ * reimplementada aqui porque aquele arquivo e 'use client' e vive no app.
51
+ *
52
+ * Emoji e a unica forma de bandeira que renderiza nos tres clientes que
53
+ * importam: o Gmail remove <svg> inline e bloqueia data: URI, e o Outlook
54
+ * desktop nao renderiza SVG de jeito nenhum.
55
+ */
56
+ export function countryFlagEmoji(code?: string | null): string {
57
+ const value = (code ?? '').trim().toUpperCase();
58
+ if (!/^[A-Z]{2}$/.test(value)) return '';
59
+
60
+ return [...value]
61
+ .map((c) => String.fromCodePoint(0x1f1e6 + c.charCodeAt(0) - 65))
62
+ .join('');
63
+ }
64
+
65
+ /**
66
+ * `Intl` lanca RangeError com tag malformada, e a tag vem da tabela `locale`,
67
+ * que nao tem allowlist. Valida uma vez aqui em vez de espalhar try/catch.
68
+ */
69
+ function safeLocale(locale?: string | null): string {
70
+ const value = (locale ?? '').trim();
71
+ if (!value) return 'en';
72
+
73
+ try {
74
+ new Intl.DateTimeFormat(value);
75
+ return value;
76
+ } catch {
77
+ return 'en';
78
+ }
79
+ }
80
+
81
+ /** Idem para o fuso, que vem da base do geoip-lite. */
82
+ function safeTimeZone(timeZone?: string | null): string {
83
+ const value = (timeZone ?? '').trim();
84
+ if (!value) return DEFAULT_TIME_ZONE;
85
+
86
+ try {
87
+ new Intl.DateTimeFormat('en', { timeZone: value });
88
+ return value;
89
+ } catch {
90
+ return DEFAULT_TIME_ZONE;
91
+ }
92
+ }
93
+
94
+ function countryName(code: string, locale: string): string {
95
+ try {
96
+ return new Intl.DisplayNames([locale], { type: 'region' }).of(code) ?? code;
97
+ } catch {
98
+ // Fica o ISO2, que ja e legivel o bastante para o destinatario.
99
+ return code;
100
+ }
101
+ }
102
+
103
+ export function formatEventDateTime(
104
+ at: Date,
105
+ locale?: string | null,
106
+ timeZone?: string | null,
107
+ ): string {
108
+ return new Intl.DateTimeFormat(safeLocale(locale), {
109
+ dateStyle: 'long',
110
+ timeStyle: 'short',
111
+ timeZone: safeTimeZone(timeZone),
112
+ }).format(at);
113
+ }
114
+
115
+ /**
116
+ * Traduz IP + User-Agent crus nas variaveis que o template de seguranca
117
+ * imprime, resolvendo geolocalizacao e navegador/SO pelos utilitarios que o
118
+ * access-log ja usa.
119
+ *
120
+ * O que nao foi possivel resolver volta como string vazia, e nao como um
121
+ * "Unknown" literal: o template esconde a linha inteira em vez de anunciar
122
+ * que o sistema nao sabe.
123
+ */
124
+ export function buildSecurityEventVars(
125
+ ctx: SecurityEventContext,
126
+ ): SecurityEventVars {
127
+ const at = ctx.at ?? new Date();
128
+ const locale = safeLocale(ctx.locale);
129
+ const geo = lookupGeo(ctx.ipAddress);
130
+ const ua = parseUserAgent(ctx.userAgent);
131
+
132
+ const country = geo?.country ? countryName(geo.country, locale) : '';
133
+ // `region` fica de fora: o geoip devolve o codigo curto ('SP'), que nao
134
+ // localiza e so polui a linha.
135
+ const location = [geo?.city, country].filter(Boolean).join(', ');
136
+
137
+ // So o major: "151" em vez de "151.0.0.0".
138
+ const browserVersion = (ua?.browser_version ?? '').split('.')[0];
139
+
140
+ return {
141
+ ipAddress: (ctx.ipAddress ?? '').trim(),
142
+ userAgent: (ctx.userAgent ?? '').trim(),
143
+ browser: [ua?.browser_name, browserVersion].filter(Boolean).join(' '),
144
+ os: [ua?.os_name, ua?.os_version].filter(Boolean).join(' '),
145
+ location,
146
+ countryFlag: countryFlagEmoji(geo?.country),
147
+ datetime: formatEventDateTime(
148
+ at,
149
+ locale,
150
+ geo?.timezone ?? ctx.fallbackTimeZone,
151
+ ),
152
+ };
153
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+
3
+ import { parseUserAgent } from './user-agent';
4
+
5
+ const CHROME_WINDOWS =
6
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
7
+
8
+ const SAFARI_IOS =
9
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1';
10
+
11
+ describe('parseUserAgent', () => {
12
+ it('parses a desktop Chrome on Windows', () => {
13
+ const result = parseUserAgent(CHROME_WINDOWS);
14
+ expect(result?.browser_name).toBe('Chrome');
15
+ expect(result?.browser_version).toMatch(/^131\./);
16
+ expect(result?.os_name).toBe('Windows');
17
+ });
18
+
19
+ it('parses mobile Safari on iOS', () => {
20
+ const result = parseUserAgent(SAFARI_IOS);
21
+ expect(result?.browser_name).toMatch(/Safari/);
22
+ expect(result?.os_name).toBe('iOS');
23
+ });
24
+
25
+ it('returns null for empty input', () => {
26
+ expect(parseUserAgent(null)).toBeNull();
27
+ expect(parseUserAgent(undefined)).toBeNull();
28
+ expect(parseUserAgent('')).toBeNull();
29
+ expect(parseUserAgent(' ')).toBeNull();
30
+ });
31
+
32
+ it('returns null when neither the browser nor the OS is identifiable', () => {
33
+ // What the API used to store while the site called it through a
34
+ // server-side fetch, with no browser involved.
35
+ expect(parseUserAgent('node')).toBeNull();
36
+ });
37
+ });
@@ -0,0 +1,46 @@
1
+ import { UAParser } from 'ua-parser-js';
2
+
3
+ export interface UserAgentInfo {
4
+ browser_name: string | null;
5
+ browser_version: string | null;
6
+ os_name: string | null;
7
+ os_version: string | null;
8
+ /** `mobile` | `tablet` | `desktop`. */
9
+ device_type: string | null;
10
+ }
11
+
12
+ /**
13
+ * Parses a raw User-Agent into the same four fields the access log already
14
+ * exposes, so the admin's BrowserIcon/OsIcon components match by name without
15
+ * any translation layer.
16
+ *
17
+ * Returns null when there is nothing to parse or when neither the browser nor
18
+ * the OS could be identified — the caller then skips the row entirely instead
19
+ * of rendering a line full of dashes.
20
+ */
21
+ export function parseUserAgent(userAgent?: string | null): UserAgentInfo | null {
22
+ const value = (userAgent ?? '').trim();
23
+ if (!value) return null;
24
+
25
+ const result = new UAParser(value).getResult();
26
+
27
+ const browserName = result.browser?.name ?? null;
28
+ const osName = result.os?.name ?? null;
29
+
30
+ // O ua-parser não devolve `device.type` para desktop — a ausência É o desktop.
31
+ // Mas isso só vale quando ele reconheceu *algo*: assumir "desktop" diante de
32
+ // um User-Agent que não identificou nada é o que fazia um app de celular
33
+ // aparecer como computador.
34
+ const deviceType =
35
+ result.device?.type ?? (browserName || osName ? 'desktop' : null);
36
+
37
+ const info: UserAgentInfo = {
38
+ browser_name: browserName,
39
+ browser_version: result.browser?.version ?? null,
40
+ os_name: osName,
41
+ os_version: result.os?.version ?? null,
42
+ device_type: deviceType,
43
+ };
44
+
45
+ return info.browser_name || info.os_name ? info : null;
46
+ }