@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
@@ -1,9 +1,15 @@
1
- import { getLocaleText } from '@hed-hog/api-locale';
1
+ import {
2
+ getLocaleFromContext,
3
+ getLocaleText,
4
+ isLocaleDetectedFromContext,
5
+ normalizeLocaleCode,
6
+ } from '@hed-hog/api-locale';
2
7
  import { PrismaService } from '@hed-hog/api-prisma';
3
8
  import { User } from '@hed-hog/api-types';
4
9
  import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/server';
5
10
  import {
6
11
  BadRequestException,
12
+ ForbiddenException,
7
13
  forwardRef,
8
14
  Inject,
9
15
  Injectable,
@@ -17,8 +23,11 @@ import { SecurityService } from '../security/security.service';
17
23
  import { SessionService } from '../session/session.service';
18
24
  import { SettingService } from '../setting/setting.service';
19
25
  import { TokenService } from '../token/token.service';
26
+ import { UserDeviceService } from '../user-device/user-device.service';
20
27
  import { hasPendingPasswordReset } from '../user/user-credential.util';
28
+ import { CreateWithEmailAndPasswordDTO } from '../user/dto/create-with-email-and-password.dto';
21
29
  import { UserService } from '../user/user.service';
30
+ import { buildSecurityEventVars } from '../utils/security-event-vars';
22
31
  import { resolveWebAuthnOrigin } from '../utils/webauthn-origin';
23
32
  import { ForgetDTO } from './dto/forget.dto';
24
33
  import { LoginDTO } from './dto/login.dto';
@@ -44,6 +53,8 @@ export class AuthService {
44
53
  private readonly user: UserService,
45
54
  @Inject(forwardRef(() => ChallengeService))
46
55
  private readonly challenge: ChallengeService,
56
+ @Inject(forwardRef(() => UserDeviceService))
57
+ private readonly userDevice: UserDeviceService,
47
58
  ) { }
48
59
 
49
60
  async getAuthenticationPayload(locale: string, userId: number, ipAddress: string, userAgent: string, browserId?: string | null) {
@@ -51,6 +62,18 @@ export class AuthService {
51
62
  const payload = { sub: userId, sessionId: session.id };
52
63
  const accessToken = await this.token.createAccessToken(payload);
53
64
  await this.prisma.user.update({ where: { id: userId }, data: { last_login_at: new Date() } });
65
+
66
+ // Unico ponto por onde todo caminho de autenticacao passa (senha, MFA,
67
+ // WebAuthn, recovery code, OAuth, pairing), entao e aqui que o dispositivo
68
+ // e aprendido. Quem checa `isNewDevice` faz isso antes de chegar aqui.
69
+ await this.userDevice.remember(userId, { userAgent, browserId, ipAddress });
70
+
71
+ // O idioma e aprendido junto com o dispositivo, e pelo mesmo motivo: e o
72
+ // unico momento em que o cliente se apresenta. Fica antes do alerta de
73
+ // dispositivo desconhecido (disparado por quem chamou este metodo), entao o
74
+ // proprio e-mail do primeiro login ja sai traduzido.
75
+ await this.persistDetectedLanguage(locale, userId);
76
+
54
77
  this.logger.debug(`[AuthService] Session created userId=${userId} sessionId=${session.id}`);
55
78
 
56
79
  return {
@@ -61,7 +84,66 @@ export class AuthService {
61
84
 
62
85
  }
63
86
 
64
- async issueDesktopToken(locale: string, userId: number, ipAddress: string, userAgent: string) {
87
+ /**
88
+ * Adota o idioma do cliente como preferencia do usuario - uma unica vez.
89
+ *
90
+ * Escrita write-once de proposito: sobrescrever a cada login faria um acesso
91
+ * de aparelho emprestado, ou de um navegador em outro idioma, apagar a
92
+ * escolha que o usuario fez na tela de preferencias.
93
+ *
94
+ * Nunca lanca. Idioma e acabamento; login que falha por causa dele seria uma
95
+ * troca pessima.
96
+ */
97
+ private async persistDetectedLanguage(locale: string, userId: number) {
98
+ try {
99
+ // `detected` distingue a escolha do cliente do default do middleware.
100
+ // Sem isso, todo cliente que nao manda `Accept-Language` congelaria `en`.
101
+ if (!isLocaleDetectedFromContext()) {
102
+ return;
103
+ }
104
+
105
+ const code = normalizeLocaleCode(getLocaleFromContext());
106
+
107
+ if (!code) {
108
+ return;
109
+ }
110
+
111
+ // `setSettingUserValue` valida que a setting aceita override, mas nao o
112
+ // valor. Sem esta checagem um navegador em `de-DE` gravaria `de`, e todo
113
+ // e-mail seguinte procuraria um template inexistente.
114
+ const allowed = await this.setting.getSettingListValues('language');
115
+
116
+ if (allowed.length && !allowed.includes(code)) {
117
+ return;
118
+ }
119
+
120
+ if (await this.setting.getUserSettingValue(userId, 'language')) {
121
+ return;
122
+ }
123
+
124
+ await this.setting.setSettingUserValue(locale, userId, 'language', code);
125
+ } catch (error) {
126
+ this.logger.warn(
127
+ `[AuthService] Could not persist detected language for userId=${userId}: ${error}`,
128
+ );
129
+ }
130
+ }
131
+
132
+ async issueDesktopToken(locale: string, userId: number, ipAddress: string, userAgent: string, isImpersonating = false) {
133
+ // Aqui o `userId` vem de @User(), que numa simulacao e o ALVO — sem esta
134
+ // guarda, o operador cunharia para ele uma sessao completa, RENOVAVEL e de
135
+ // 30 dias, furando o teto da simulacao e saindo da trilha de auditoria. O
136
+ // RoleGuard ja bloqueia a rota; isto e a segunda barreira.
137
+ if (isImpersonating) {
138
+ throw new ForbiddenException(
139
+ getLocaleText(
140
+ 'impersonation.restrictedRoute',
141
+ locale,
142
+ 'This action is not available during an access simulation.',
143
+ ),
144
+ );
145
+ }
146
+
65
147
  const { accessToken, refreshToken } = await this.getAuthenticationPayload(locale, userId, ipAddress, userAgent);
66
148
  return { accessToken, refreshToken };
67
149
  }
@@ -279,7 +361,10 @@ export class AuthService {
279
361
  user_identifier_id: true,
280
362
  user_identifier: {
281
363
  select: {
282
- user_id: true
364
+ user_id: true,
365
+ // Destinatario do alerta de dispositivo novo: o endereco que o
366
+ // usuario de fato verificou para entrar.
367
+ value: true
283
368
  }
284
369
  }
285
370
  }
@@ -320,14 +405,54 @@ export class AuthService {
320
405
 
321
406
  const user = await this.user.findUserById(locale, challenge.user_identifier.user_id);
322
407
 
323
- return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId, origin);
408
+ return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId, origin, challenge.user_identifier.value);
324
409
 
325
410
  } catch (error) {
326
411
  throw error;
327
412
  }
328
413
  }
329
414
 
330
- private async login(locale: string, user: User, ipAddress: string, userAgent: string, res: any, browserId?: string | null, origin?: string) {
415
+ /**
416
+ * Dispara o alerta de acesso de dispositivo desconhecido.
417
+ *
418
+ * O `await` aqui cobre so a resolucao do app (leitura de setting memoizada);
419
+ * o envio em si continua sem await de proposito, para nao somar a latencia do
420
+ * SMTP ao tempo de resposta do login.
421
+ */
422
+ private async sendNewDeviceLoginMail(
423
+ locale: string,
424
+ user: { name?: string } | null | undefined,
425
+ email: string | null | undefined,
426
+ ipAddress: string,
427
+ userAgent: string,
428
+ origin?: string,
429
+ ): Promise<void> {
430
+ // Tokens de desafio emitidos antes de o `email` passar a ser gravado no
431
+ // payload chegam aqui sem destinatario; alertar ninguem e melhor do que
432
+ // deixar o envio explodir dentro do fluxo de login.
433
+ if (!email) {
434
+ return;
435
+ }
436
+
437
+ const app = await this.challenge.resolveAppKeyFromOrigin(origin);
438
+
439
+ // As variaveis sao formatadas aqui (data e nome do pais saem do `Intl`),
440
+ // entao o idioma do destinatario tem que ser resolvido ANTES do envio -
441
+ // senao o corpo sai em portugues com a data em ingles.
442
+ const recipientLocale = await this.mail.resolveRecipientLocale(email, locale);
443
+
444
+ this.mail.sendTemplatedMail(recipientLocale, {
445
+ email,
446
+ slug: 'auth-login-new-device',
447
+ variables: {
448
+ name: user?.name ?? '',
449
+ ...buildSecurityEventVars({ ipAddress, userAgent, locale: recipientLocale }),
450
+ },
451
+ app,
452
+ });
453
+ }
454
+
455
+ private async login(locale: string, user: User, ipAddress: string, userAgent: string, res: any, browserId?: string | null, origin?: string, loginEmail?: string) {
331
456
 
332
457
  // Conta excluída pelo próprio usuário: os identificadores já foram
333
458
  // anonimizados, mas a checagem fecha qualquer caminho remanescente.
@@ -352,6 +477,8 @@ export class AuthService {
352
477
  throw new BadRequestException(getLocaleText('accessDenied', locale, 'Access denied.'));
353
478
  }
354
479
 
480
+ const newDevice = await this.userDevice.isNewDevice(user.id, userAgent, browserId);
481
+
355
482
  const { accessToken, refreshToken, session } = await this.getAuthenticationPayload(
356
483
  locale,
357
484
  user.id,
@@ -360,26 +487,16 @@ export class AuthService {
360
487
  browserId,
361
488
  );
362
489
 
363
- const app = await this.challenge.resolveAppKeyFromOrigin(origin);
364
-
365
- for (const emailObj of emails) {
490
+ if (newDevice) {
491
+ // So o endereco usado para entrar. Avisar todos os identificadores
492
+ // contava, para um e-mail secundario, que o principal tinha sido usado.
493
+ const wanted = (loginEmail ?? '').trim().toLowerCase();
494
+ const recipient =
495
+ emails.find((e) => e.value?.toLowerCase() === wanted)?.value ??
496
+ emails[0].value;
366
497
 
367
- this.mail.sendTemplatedMail(
368
- locale,
369
- {
370
- email: emailObj.value,
371
- slug: 'auth-login-new-device',
372
- variables: {
373
- name: user.name,
374
- ipAddress,
375
- userAgent,
376
- location: 'Unknown',
377
- },
378
- app,
379
- }
380
- );
381
-
382
- }
498
+ await this.sendNewDeviceLoginMail(locale, user, recipient, ipAddress, userAgent, origin);
499
+ }
383
500
 
384
501
  await this.user.registerUserActivity(user.id, "login")
385
502
 
@@ -466,7 +583,55 @@ export class AuthService {
466
583
 
467
584
  }
468
585
 
469
- return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId, origin);
586
+ return this.login(locale, user as unknown as User, ipAddress, userAgent, res, browserId, origin, email);
587
+ }
588
+
589
+ /**
590
+ * Cadastro por e-mail e senha.
591
+ *
592
+ * Com `require-email-verification` desligada (o default), a conta já nasce
593
+ * utilizável e o e-mail de confirmação é apenas informativo — devolver a
594
+ * pessoa para a tela de login só a obrigaria a digitar de novo o que acabou
595
+ * de digitar, então o cadastro já termina com a sessão emitida.
596
+ *
597
+ * Com a setting ligada, `loginWithEmailAndPassword` barraria o acesso logo em
598
+ * seguida; nesse caso não emitimos sessão e devolvemos `hasChallenge` para o
599
+ * app mandar a pessoa confirmar o endereço antes.
600
+ */
601
+ async signupWithEmailAndPassword(
602
+ res: any,
603
+ locale: string,
604
+ ipAddress: string,
605
+ userAgent: string,
606
+ data: CreateWithEmailAndPasswordDTO,
607
+ browserId?: string | null,
608
+ origin?: string,
609
+ ) {
610
+ const { user } = await this.user.createWithEmailAndPassword(locale, data);
611
+
612
+ const settings = await this.setting.getSettingValues([
613
+ 'require-email-verification',
614
+ ]);
615
+
616
+ if (settings['require-email-verification'] === true) {
617
+ return { user, hasChallenge: true };
618
+ }
619
+
620
+ // `isNewDevice` retorna false quando o usuário ainda não tem dispositivo
621
+ // nenhum, então o auto-login não dispara o alerta de "novo acesso" — que
622
+ // logo após o cadastro pareceria um aviso de invasão.
623
+ const session = await this.login(
624
+ locale,
625
+ user as unknown as User,
626
+ ipAddress,
627
+ userAgent,
628
+ res,
629
+ browserId,
630
+ origin,
631
+ data.email,
632
+ );
633
+
634
+ return { user, hasChallenge: false, ...session };
470
635
  }
471
636
 
472
637
  async verifyRoles(_locale: string, userId: number) {
@@ -484,13 +649,46 @@ export class AuthService {
484
649
  })
485
650
  }
486
651
 
487
- async verifyUser(locale: string, userId: number) {
652
+ async verifyUser(locale: string, userId: number, sessionId?: number | null) {
488
653
  const user = await this.user.findUserById(locale, userId);
489
654
  const requiresPasswordReset = this.hasPendingPasswordReset(user);
490
655
  delete user.user_credential;
491
656
  return {
492
657
  ...user,
493
658
  requires_password_reset: requiresPasswordReset,
659
+ // Fonte de verdade do banner de simulacao no cliente: a sessionStorage do
660
+ // navegador e so cache e e gravavel por quem estiver na aba.
661
+ impersonation: await this.describeImpersonation(sessionId),
662
+ };
663
+ }
664
+
665
+ private async describeImpersonation(sessionId?: number | null) {
666
+ if (!sessionId) {
667
+ return null;
668
+ }
669
+
670
+ const session = await this.prisma.user_session.findUnique({
671
+ where: { id: sessionId },
672
+ select: {
673
+ impersonator_user_id: true,
674
+ impersonation_reason: true,
675
+ expires_at: true,
676
+ },
677
+ });
678
+
679
+ if (!session?.impersonator_user_id) {
680
+ return null;
681
+ }
682
+
683
+ const operator = await this.prisma.user.findUnique({
684
+ where: { id: session.impersonator_user_id },
685
+ select: { id: true, name: true },
686
+ });
687
+
688
+ return {
689
+ by: operator ?? { id: session.impersonator_user_id, name: '' },
690
+ reason: session.impersonation_reason,
691
+ expires_at: session.expires_at.toISOString(),
494
692
  };
495
693
  }
496
694
 
@@ -531,6 +729,17 @@ export class AuthService {
531
729
  }
532
730
 
533
731
  async logout(res, req, refreshToken: string) {
732
+ // Numa sessao simulada o cookie `rt` da mesma origem e o do OPERADOR: limpar
733
+ // e revogar por hash aqui derrubaria a sessao real dele. O RoleGuard ja
734
+ // bloqueia esta rota durante uma simulacao (impersonation-restrictions.ts);
735
+ // isto e a segunda barreira, no caso de a rota ser alcancada por outro
736
+ // caminho. Sair de uma simulacao e POST /impersonation/stop.
737
+ if (req?.auth?.imp === true) {
738
+ throw new ForbiddenException(
739
+ 'This action is not available during an access simulation.',
740
+ );
741
+ }
742
+
534
743
  const result = await this.session.deleteRefreshTokenCookie(res, refreshToken);
535
744
  const logoutUserId = req.auth?.sub ?? result?.userId ?? null;
536
745
 
@@ -628,6 +837,96 @@ export class AuthService {
628
837
  return { userId: targetUserId, token, hasPassword: passwordCount > 0 };
629
838
  }
630
839
 
840
+ /**
841
+ * Confirma o e-mail a partir do link enviado no cadastro. Não emite sessão: o
842
+ * link só prova o controle da caixa, quem quiser entrar passa pelo login.
843
+ *
844
+ * Idempotente de propósito — clientes de e-mail fazem pré-fetch do link e o
845
+ * usuário costuma clicar de novo depois, e nesses casos ver "link inválido"
846
+ * assusta mais do que ajuda.
847
+ */
848
+ async confirmEmail(locale: string, token: string) {
849
+ const { userIdentifierId } = await this.token.verifyEmailConfirmationToken(
850
+ locale,
851
+ token,
852
+ );
853
+
854
+ const identifier = await this.prisma.user_identifier.findUnique({
855
+ where: { id: userIdentifierId },
856
+ include: { user: { select: { id: true, name: true } } },
857
+ });
858
+
859
+ if (!identifier) {
860
+ throw new BadRequestException(
861
+ getLocaleText(
862
+ 'confirmEmail.invalidOrExpired',
863
+ locale,
864
+ 'This confirmation link is invalid or has expired.',
865
+ ),
866
+ );
867
+ }
868
+
869
+ if (identifier.verified_at) {
870
+ return {
871
+ confirmed: true,
872
+ alreadyConfirmed: true,
873
+ email: identifier.value,
874
+ name: identifier.user?.name ?? null,
875
+ };
876
+ }
877
+
878
+ await this.prisma.user_identifier.update({
879
+ where: { id: identifier.id },
880
+ data: { verified_at: new Date(), enabled: true },
881
+ });
882
+
883
+ await this.user.registerUserActivity(identifier.user_id, 'confirmEmail');
884
+
885
+ return {
886
+ confirmed: true,
887
+ alreadyConfirmed: false,
888
+ email: identifier.value,
889
+ name: identifier.user?.name ?? null,
890
+ };
891
+ }
892
+
893
+ /**
894
+ * Reenvia o link de confirmação do cadastro.
895
+ *
896
+ * Responde `{ sent: true }` mesmo quando o e-mail não existe ou já está
897
+ * confirmado: qualquer diferença na resposta transformaria a rota num
898
+ * oráculo para descobrir quais endereços têm conta.
899
+ */
900
+ async resendEmailConfirmation(
901
+ locale: string,
902
+ email: string,
903
+ app?: string,
904
+ origin?: string,
905
+ ) {
906
+ try {
907
+ const user = await this.user.findUserByEmail(locale, email);
908
+
909
+ if (user) {
910
+ await this.challenge.sendEmailConfirmationLink(
911
+ locale,
912
+ user.id,
913
+ email,
914
+ app,
915
+ origin,
916
+ );
917
+ }
918
+ } catch (error: any) {
919
+ // `sendEmailConfirmationLink` lança NotFound quando o endereço já está
920
+ // verificado — e uma falha de envio não pode virar erro para o cliente,
921
+ // sob pena de vazar a existência da conta.
922
+ this.logger.warn(
923
+ 'Resend of signup confirmation email failed: ' + (error?.message ?? error),
924
+ );
925
+ }
926
+
927
+ return { sent: true };
928
+ }
929
+
631
930
  /**
632
931
  * Valida (sem consumir) um token de convite de criação de conta e retorna os
633
932
  * dados básicos para pré-preencher o formulário no app de treinamentos.
@@ -851,6 +1150,8 @@ export class AuthService {
851
1150
  throw new BadRequestException(getLocaleText('invalidMfaCode', locale, 'Invalid MFA code.'));
852
1151
  }
853
1152
 
1153
+ const newDevice = await this.userDevice.isNewDevice(user.id, userAgent, browserId);
1154
+
854
1155
  const { accessToken, refreshToken, session } = await this.getAuthenticationPayload(
855
1156
  locale,
856
1157
  user.id,
@@ -859,20 +1160,9 @@ export class AuthService {
859
1160
  browserId,
860
1161
  );
861
1162
 
862
- this.mail.sendTemplatedMail(
863
- locale,
864
- {
865
- email: payload.email,
866
- slug: 'auth-login-new-device',
867
- variables: {
868
- name: user.name,
869
- ipAddress,
870
- userAgent,
871
- location: 'Unknown',
872
- },
873
- app: await this.challenge.resolveAppKeyFromOrigin(origin),
874
- }
875
- );
1163
+ if (newDevice) {
1164
+ await this.sendNewDeviceLoginMail(locale, user, payload.email, ipAddress, userAgent, origin);
1165
+ }
876
1166
 
877
1167
  await this.user.registerUserActivity(user.id, "login");
878
1168
  const requiresPasswordReset = await this.getRequiresPasswordResetByUserId(
@@ -881,7 +1171,7 @@ export class AuthService {
881
1171
  return { accessToken, refreshToken, session, requiresPasswordReset };
882
1172
  }
883
1173
 
884
- async verifyMfaRecoveryCode(locale: string, mfaToken: string, recoveryCode: string, ipAddress: string, userAgent: string, browserId?: string | null) {
1174
+ async verifyMfaRecoveryCode(locale: string, mfaToken: string, recoveryCode: string, ipAddress: string, userAgent: string, browserId?: string | null, origin?: string) {
885
1175
  const payload = await this.token.verifyMfaChallengeToken(mfaToken);
886
1176
  const user = await this.prisma.user.findUnique({
887
1177
  where: { id: payload.userId },
@@ -909,6 +1199,8 @@ export class AuthService {
909
1199
  throw new BadRequestException(getLocaleText('invalidRecoveryCode', locale, 'Invalid recovery code.'));
910
1200
  }
911
1201
 
1202
+ const newDevice = await this.userDevice.isNewDevice(user.id, userAgent, browserId);
1203
+
912
1204
  const { accessToken, refreshToken, session } = await this.getAuthenticationPayload(
913
1205
  locale,
914
1206
  user.id,
@@ -917,6 +1209,12 @@ export class AuthService {
917
1209
  browserId,
918
1210
  );
919
1211
 
1212
+ // Entrar por codigo de recuperacao e o caminho mais sensivel dos tres, e era
1213
+ // justamente o unico que nao avisava ninguem.
1214
+ if (newDevice) {
1215
+ await this.sendNewDeviceLoginMail(locale, user, payload.email, ipAddress, userAgent, origin);
1216
+ }
1217
+
920
1218
  await this.user.registerUserActivity(user.id, "login");
921
1219
  const requiresPasswordReset = await this.getRequiresPasswordResetByUserId(
922
1220
  user.id,
@@ -1058,6 +1356,8 @@ export class AuthService {
1058
1356
  data: { verified_at: new Date() }
1059
1357
  });
1060
1358
 
1359
+ const newDevice = await this.userDevice.isNewDevice(userId, userAgent, browserId);
1360
+
1061
1361
  const { accessToken, refreshToken, session } = await this.getAuthenticationPayload(
1062
1362
  locale,
1063
1363
  userId,
@@ -1066,21 +1366,10 @@ export class AuthService {
1066
1366
  browserId,
1067
1367
  );
1068
1368
 
1069
- const user = await this.prisma.user.findFirst({ where: { id: userId } });
1070
- this.mail.sendTemplatedMail(
1071
- locale,
1072
- {
1073
- email: payload.email,
1074
- slug: 'auth-login-new-device',
1075
- variables: {
1076
- name: user.name,
1077
- ipAddress,
1078
- userAgent,
1079
- location: 'Unknown',
1080
- },
1081
- app: await this.challenge.resolveAppKeyFromOrigin(requestOrigin),
1082
- }
1083
- );
1369
+ if (newDevice) {
1370
+ const user = await this.prisma.user.findFirst({ where: { id: userId } });
1371
+ await this.sendNewDeviceLoginMail(locale, user, payload.email, ipAddress, userAgent, requestOrigin);
1372
+ }
1084
1373
 
1085
1374
  await this.user.registerUserActivity(userId, 'login');
1086
1375
  const requiresPasswordReset = await this.getRequiresPasswordResetByUserId(
@@ -0,0 +1,7 @@
1
+ import { IsNotEmpty, IsString } from 'class-validator';
2
+
3
+ export class ConfirmEmailDTO {
4
+ @IsString()
5
+ @IsNotEmpty()
6
+ token: string;
7
+ }
@@ -0,0 +1,20 @@
1
+ import { getLocaleText } from '@hed-hog/api-locale';
2
+ import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
3
+ import { IsEmailWithSettings } from '../../validators/is-email-with-settings.validator';
4
+
5
+ export class ResendConfirmationDTO {
6
+ @IsNotEmpty({ message: (args) => getLocaleText('validation.currentEmailRequired', args.value) })
7
+ @IsEmailWithSettings({ message: (args) => getLocaleText('validation.currentEmailInvalid', args.value) })
8
+ set email(value: string) {
9
+ this._email = value?.trim().toLowerCase();
10
+ }
11
+ get email(): string {
12
+ return this._email;
13
+ }
14
+ private _email: string;
15
+
16
+ /** Chave do app de origem (ex.: "class"), usada para montar a URL do link. */
17
+ @IsOptional()
18
+ @IsString()
19
+ app?: string;
20
+ }
@@ -85,6 +85,7 @@ export class AuthorizeController {
85
85
  @Locale() locale: string,
86
86
  @Ip() ipAddress: string,
87
87
  @Headers('user-agent') userAgent: string,
88
+ @Headers('x-browser-id') browserId: string,
88
89
  ) {
89
90
  return this.service.exchangeCode({
90
91
  code: body.code,
@@ -94,6 +95,7 @@ export class AuthorizeController {
94
95
  locale,
95
96
  ipAddress,
96
97
  userAgent,
98
+ browserId: browserId || null,
97
99
  });
98
100
  }
99
101
  }
@@ -191,6 +191,29 @@ describe('AuthorizeService', () => {
191
191
  7,
192
192
  '127.0.0.1',
193
193
  'jest',
194
+ undefined,
195
+ );
196
+ });
197
+
198
+ it('repassa o browserId, que identifica o dispositivo da extensão', async () => {
199
+ const verifier = makeVerifier();
200
+ deps.prisma.$queryRaw.mockResolvedValue([
201
+ { user_id: 7, code_challenge: sha256b64url(verifier), scope: 'vaults' },
202
+ ] as never);
203
+
204
+ await service.exchangeCode({
205
+ ...validExchangeParams('CODE', verifier),
206
+ browserId: 'ext-install-1',
207
+ });
208
+
209
+ // Sem isto o dispositivo cairia no User-Agent — o do Chrome inteiro, que
210
+ // muda a cada atualização do navegador.
211
+ expect(deps.auth.getAuthenticationPayload).toHaveBeenCalledWith(
212
+ 'en',
213
+ 7,
214
+ '127.0.0.1',
215
+ 'jest',
216
+ 'ext-install-1',
194
217
  );
195
218
  });
196
219
 
@@ -168,6 +168,13 @@ export class AuthorizeService {
168
168
  locale: string;
169
169
  ipAddress: string;
170
170
  userAgent: string;
171
+ /**
172
+ * Id estavel da instalacao do cliente (header `X-Browser-ID`). Sem ele o
173
+ * dispositivo e identificado pelo User-Agent, que numa extensao e o do
174
+ * navegador inteiro — muda a cada atualizacao do Chrome e enche a lista de
175
+ * dispositivos do usuario de entradas repetidas.
176
+ */
177
+ browserId?: string | null;
171
178
  }) {
172
179
  const client = await this.getClient(params.clientId);
173
180
 
@@ -212,6 +219,7 @@ export class AuthorizeService {
212
219
  row.user_id,
213
220
  params.ipAddress,
214
221
  params.userAgent,
222
+ params.browserId,
215
223
  );
216
224
 
217
225
  return {