@hed-hog/core 0.0.387 → 0.0.390

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 (535) hide show
  1. package/README.md +12 -11
  2. package/dist/access-log/access-log.interceptor.d.ts.map +1 -1
  3. package/dist/access-log/access-log.interceptor.js +4 -2
  4. package/dist/access-log/access-log.interceptor.js.map +1 -1
  5. package/dist/ai/ai-attachment.fragments.d.ts +23 -0
  6. package/dist/ai/ai-attachment.fragments.d.ts.map +1 -0
  7. package/dist/ai/ai-attachment.fragments.js +34 -0
  8. package/dist/ai/ai-attachment.fragments.js.map +1 -0
  9. package/dist/ai/ai.controller.d.ts +7 -16
  10. package/dist/ai/ai.controller.d.ts.map +1 -1
  11. package/dist/ai/ai.prompts.d.ts +16 -0
  12. package/dist/ai/ai.prompts.d.ts.map +1 -0
  13. package/dist/ai/ai.prompts.js +37 -0
  14. package/dist/ai/ai.prompts.js.map +1 -0
  15. package/dist/ai/ai.service.d.ts +63 -12
  16. package/dist/ai/ai.service.d.ts.map +1 -1
  17. package/dist/ai/ai.service.js +215 -88
  18. package/dist/ai/ai.service.js.map +1 -1
  19. package/dist/ai/dto/chat.dto.d.ts +4 -1
  20. package/dist/ai/dto/chat.dto.d.ts.map +1 -1
  21. package/dist/ai/dto/chat.dto.js +23 -1
  22. package/dist/ai/dto/chat.dto.js.map +1 -1
  23. package/dist/ai/dto/create-agent.dto.d.ts +1 -1
  24. package/dist/ai/dto/create-agent.dto.d.ts.map +1 -1
  25. package/dist/ai/dto/create-agent.dto.js +1 -1
  26. package/dist/ai/dto/create-agent.dto.js.map +1 -1
  27. package/dist/ai/dto/update-agent.dto.d.ts +1 -1
  28. package/dist/ai/dto/update-agent.dto.d.ts.map +1 -1
  29. package/dist/ai/dto/update-agent.dto.js +1 -1
  30. package/dist/ai/dto/update-agent.dto.js.map +1 -1
  31. package/dist/ai-instruction/ai-instruction.controller.d.ts +61 -7
  32. package/dist/ai-instruction/ai-instruction.controller.d.ts.map +1 -1
  33. package/dist/ai-instruction/ai-instruction.controller.js +63 -20
  34. package/dist/ai-instruction/ai-instruction.controller.js.map +1 -1
  35. package/dist/ai-instruction/ai-instruction.module.d.ts.map +1 -1
  36. package/dist/ai-instruction/ai-instruction.module.js +13 -2
  37. package/dist/ai-instruction/ai-instruction.module.js.map +1 -1
  38. package/dist/ai-instruction/ai-instruction.service.d.ts +131 -6
  39. package/dist/ai-instruction/ai-instruction.service.d.ts.map +1 -1
  40. package/dist/ai-instruction/ai-instruction.service.js +533 -60
  41. package/dist/ai-instruction/ai-instruction.service.js.map +1 -1
  42. package/dist/ai-instruction/ai-prompt-builder.service.d.ts +8 -0
  43. package/dist/ai-instruction/ai-prompt-builder.service.d.ts.map +1 -1
  44. package/dist/ai-instruction/ai-prompt-builder.service.js +10 -1
  45. package/dist/ai-instruction/ai-prompt-builder.service.js.map +1 -1
  46. package/dist/ai-instruction/ai-prompt-registry.d.ts +45 -0
  47. package/dist/ai-instruction/ai-prompt-registry.d.ts.map +1 -0
  48. package/dist/ai-instruction/ai-prompt-registry.js +49 -0
  49. package/dist/ai-instruction/ai-prompt-registry.js.map +1 -0
  50. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts +89 -0
  51. package/dist/ai-instruction/ai-prompt-resolver.service.d.ts.map +1 -0
  52. package/dist/ai-instruction/ai-prompt-resolver.service.js +222 -0
  53. package/dist/ai-instruction/ai-prompt-resolver.service.js.map +1 -0
  54. package/dist/ai-instruction/ai-prompt-template.util.d.ts +70 -0
  55. package/dist/ai-instruction/ai-prompt-template.util.d.ts.map +1 -0
  56. package/dist/ai-instruction/ai-prompt-template.util.js +154 -0
  57. package/dist/ai-instruction/ai-prompt-template.util.js.map +1 -0
  58. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts +5 -0
  59. package/dist/ai-instruction/dto/create-ai-instruction.dto.d.ts.map +1 -1
  60. package/dist/ai-instruction/dto/create-ai-instruction.dto.js +27 -1
  61. package/dist/ai-instruction/dto/create-ai-instruction.dto.js.map +1 -1
  62. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts +14 -0
  63. package/dist/ai-instruction/dto/preview-ai-instruction.dto.d.ts.map +1 -0
  64. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js +42 -0
  65. package/dist/ai-instruction/dto/preview-ai-instruction.dto.js.map +1 -0
  66. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts +11 -0
  67. package/dist/ai-instruction/dto/update-ai-instruction.dto.d.ts.map +1 -1
  68. package/dist/ai-instruction/dto/update-ai-instruction.dto.js +34 -0
  69. package/dist/ai-instruction/dto/update-ai-instruction.dto.js.map +1 -1
  70. package/dist/ai-instruction/types/ai-instruction.types.d.ts +13 -1
  71. package/dist/ai-instruction/types/ai-instruction.types.d.ts.map +1 -1
  72. package/dist/ai-instruction/types/ai-instruction.types.js +10 -0
  73. package/dist/ai-instruction/types/ai-instruction.types.js.map +1 -1
  74. package/dist/auth/auth.controller.d.ts +24 -9
  75. package/dist/auth/auth.controller.d.ts.map +1 -1
  76. package/dist/auth/auth.controller.js +49 -21
  77. package/dist/auth/auth.controller.js.map +1 -1
  78. package/dist/auth/auth.service.d.ts +33 -8
  79. package/dist/auth/auth.service.d.ts.map +1 -1
  80. package/dist/auth/auth.service.js +78 -29
  81. package/dist/auth/auth.service.js.map +1 -1
  82. package/dist/captcha/captcha.module.d.ts +3 -0
  83. package/dist/captcha/captcha.module.d.ts.map +1 -0
  84. package/dist/captcha/captcha.module.js +23 -0
  85. package/dist/captcha/captcha.module.js.map +1 -0
  86. package/dist/captcha/captcha.service.d.ts +38 -0
  87. package/dist/captcha/captcha.service.d.ts.map +1 -0
  88. package/dist/captcha/captcha.service.js +144 -0
  89. package/dist/captcha/captcha.service.js.map +1 -0
  90. package/dist/challenge/challenge.service.d.ts +16 -7
  91. package/dist/challenge/challenge.service.d.ts.map +1 -1
  92. package/dist/challenge/challenge.service.js +64 -16
  93. package/dist/challenge/challenge.service.js.map +1 -1
  94. package/dist/core.module.d.ts +3 -1
  95. package/dist/core.module.d.ts.map +1 -1
  96. package/dist/core.module.js +29 -2
  97. package/dist/core.module.js.map +1 -1
  98. package/dist/dashboard/dashboard/dashboard.controller.d.ts +15 -15
  99. package/dist/dashboard/dashboard/dashboard.service.d.ts +15 -15
  100. package/dist/dashboard/dashboard-component/dashboard-component.controller.d.ts +6 -6
  101. package/dist/dashboard/dashboard-component/dashboard-component.service.d.ts +6 -6
  102. package/dist/dashboard/dashboard-component-role/dashboard-component-role.controller.d.ts +20 -20
  103. package/dist/dashboard/dashboard-component-role/dashboard-component-role.service.d.ts +20 -20
  104. package/dist/dashboard/dashboard-core/dashboard-core.controller.d.ts +1 -1
  105. package/dist/dashboard/dashboard-core/dashboard-core.service.d.ts +1 -1
  106. package/dist/dashboard/dashboard-role/dashboard-role.controller.d.ts +13 -13
  107. package/dist/dashboard/dashboard-role/dashboard-role.service.d.ts +13 -13
  108. package/dist/file/file.service.d.ts +6 -0
  109. package/dist/file/file.service.d.ts.map +1 -1
  110. package/dist/file/file.service.js +44 -4
  111. package/dist/file/file.service.js.map +1 -1
  112. package/dist/incident/incident-notifier.service.d.ts +36 -0
  113. package/dist/incident/incident-notifier.service.d.ts.map +1 -0
  114. package/dist/incident/incident-notifier.service.js +208 -0
  115. package/dist/incident/incident-notifier.service.js.map +1 -0
  116. package/dist/incident/incident.controller.d.ts +23 -0
  117. package/dist/incident/incident.controller.d.ts.map +1 -0
  118. package/dist/incident/incident.controller.js +75 -0
  119. package/dist/incident/incident.controller.js.map +1 -0
  120. package/dist/incident/incident.module.d.ts +7 -0
  121. package/dist/incident/incident.module.d.ts.map +1 -0
  122. package/dist/incident/incident.module.js +32 -0
  123. package/dist/incident/incident.module.js.map +1 -0
  124. package/dist/incident/incident.service.d.ts +72 -0
  125. package/dist/incident/incident.service.d.ts.map +1 -0
  126. package/dist/incident/incident.service.js +184 -0
  127. package/dist/incident/incident.service.js.map +1 -0
  128. package/dist/index.d.ts +26 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +30 -0
  131. package/dist/index.js.map +1 -1
  132. package/dist/integration-profile/dto/test.dto.d.ts +13 -0
  133. package/dist/integration-profile/dto/test.dto.d.ts.map +1 -0
  134. package/dist/integration-profile/dto/test.dto.js +26 -0
  135. package/dist/integration-profile/dto/test.dto.js.map +1 -0
  136. package/dist/integration-profile/integration-profile.controller.d.ts +12 -1
  137. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  138. package/dist/integration-profile/integration-profile.controller.js +9 -1
  139. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  140. package/dist/integration-profile/integration-profile.module.d.ts +6 -1
  141. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -1
  142. package/dist/integration-profile/integration-profile.module.js +26 -3
  143. package/dist/integration-profile/integration-profile.module.js.map +1 -1
  144. package/dist/integration-profile/integration-profile.secrets.d.ts.map +1 -1
  145. package/dist/integration-profile/integration-profile.secrets.js +9 -1
  146. package/dist/integration-profile/integration-profile.secrets.js.map +1 -1
  147. package/dist/integration-profile/integration-profile.service.d.ts +26 -7
  148. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  149. package/dist/integration-profile/integration-profile.service.js +94 -91
  150. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  151. package/dist/integration-profile/integration-profile.utils.d.ts +13 -1
  152. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  153. package/dist/integration-profile/integration-profile.utils.js +20 -5
  154. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  155. package/dist/integration-profile/testers/ai.testers.d.ts +3 -0
  156. package/dist/integration-profile/testers/ai.testers.d.ts.map +1 -0
  157. package/dist/integration-profile/testers/ai.testers.js +125 -0
  158. package/dist/integration-profile/testers/ai.testers.js.map +1 -0
  159. package/dist/integration-profile/testers/captcha.testers.d.ts +3 -0
  160. package/dist/integration-profile/testers/captcha.testers.d.ts.map +1 -0
  161. package/dist/integration-profile/testers/captcha.testers.js +121 -0
  162. package/dist/integration-profile/testers/captcha.testers.js.map +1 -0
  163. package/dist/integration-profile/testers/email.testers.d.ts +3 -0
  164. package/dist/integration-profile/testers/email.testers.d.ts.map +1 -0
  165. package/dist/integration-profile/testers/email.testers.js +128 -0
  166. package/dist/integration-profile/testers/email.testers.js.map +1 -0
  167. package/dist/integration-profile/testers/http-probe.d.ts +57 -0
  168. package/dist/integration-profile/testers/http-probe.d.ts.map +1 -0
  169. package/dist/integration-profile/testers/http-probe.js +140 -0
  170. package/dist/integration-profile/testers/http-probe.js.map +1 -0
  171. package/dist/integration-profile/testers/index.d.ts +12 -0
  172. package/dist/integration-profile/testers/index.d.ts.map +1 -0
  173. package/dist/integration-profile/testers/index.js +45 -0
  174. package/dist/integration-profile/testers/index.js.map +1 -0
  175. package/dist/integration-profile/testers/infrastructure.testers.d.ts +3 -0
  176. package/dist/integration-profile/testers/infrastructure.testers.d.ts.map +1 -0
  177. package/dist/integration-profile/testers/infrastructure.testers.js +105 -0
  178. package/dist/integration-profile/testers/infrastructure.testers.js.map +1 -0
  179. package/dist/integration-profile/testers/integration-tester.registry.d.ts +17 -0
  180. package/dist/integration-profile/testers/integration-tester.registry.d.ts.map +1 -0
  181. package/dist/integration-profile/testers/integration-tester.registry.js +52 -0
  182. package/dist/integration-profile/testers/integration-tester.registry.js.map +1 -0
  183. package/dist/integration-profile/testers/integration-tester.types.d.ts +55 -0
  184. package/dist/integration-profile/testers/integration-tester.types.d.ts.map +1 -0
  185. package/dist/integration-profile/testers/integration-tester.types.js +16 -0
  186. package/dist/integration-profile/testers/integration-tester.types.js.map +1 -0
  187. package/dist/integration-profile/testers/messaging.testers.d.ts +3 -0
  188. package/dist/integration-profile/testers/messaging.testers.d.ts.map +1 -0
  189. package/dist/integration-profile/testers/messaging.testers.js +70 -0
  190. package/dist/integration-profile/testers/messaging.testers.js.map +1 -0
  191. package/dist/integration-profile/testers/oauth.testers.d.ts +3 -0
  192. package/dist/integration-profile/testers/oauth.testers.d.ts.map +1 -0
  193. package/dist/integration-profile/testers/oauth.testers.js +285 -0
  194. package/dist/integration-profile/testers/oauth.testers.js.map +1 -0
  195. package/dist/integration-profile/testers/payment.testers.d.ts +3 -0
  196. package/dist/integration-profile/testers/payment.testers.d.ts.map +1 -0
  197. package/dist/integration-profile/testers/payment.testers.js +169 -0
  198. package/dist/integration-profile/testers/payment.testers.js.map +1 -0
  199. package/dist/integration-profile/testers/storage.testers.d.ts +3 -0
  200. package/dist/integration-profile/testers/storage.testers.d.ts.map +1 -0
  201. package/dist/integration-profile/testers/storage.testers.js +208 -0
  202. package/dist/integration-profile/testers/storage.testers.js.map +1 -0
  203. package/dist/language/en.json +4 -2
  204. package/dist/language/pt.json +4 -2
  205. package/dist/leaked-password/leaked-password.controller.d.ts +24 -0
  206. package/dist/leaked-password/leaked-password.controller.d.ts.map +1 -0
  207. package/dist/leaked-password/leaked-password.controller.js +78 -0
  208. package/dist/leaked-password/leaked-password.controller.js.map +1 -0
  209. package/dist/leaked-password/leaked-password.module.d.ts +8 -0
  210. package/dist/leaked-password/leaked-password.module.d.ts.map +1 -0
  211. package/dist/leaked-password/leaked-password.module.js +31 -0
  212. package/dist/leaked-password/leaked-password.module.js.map +1 -0
  213. package/dist/leaked-password/leaked-password.service.d.ts +64 -0
  214. package/dist/leaked-password/leaked-password.service.d.ts.map +1 -0
  215. package/dist/leaked-password/leaked-password.service.js +218 -0
  216. package/dist/leaked-password/leaked-password.service.js.map +1 -0
  217. package/dist/locale/locale.controller.d.ts +1 -1
  218. package/dist/locale/locale.service.d.ts +1 -1
  219. package/dist/mail/dto/preview.dto.d.ts +18 -0
  220. package/dist/mail/dto/preview.dto.d.ts.map +1 -0
  221. package/dist/mail/dto/preview.dto.js +66 -0
  222. package/dist/mail/dto/preview.dto.js.map +1 -0
  223. package/dist/mail/dto/send.dto.d.ts +7 -0
  224. package/dist/mail/dto/send.dto.d.ts.map +1 -1
  225. package/dist/mail/dto/send.dto.js +10 -0
  226. package/dist/mail/dto/send.dto.js.map +1 -1
  227. package/dist/mail/mail.controller.d.ts +4 -0
  228. package/dist/mail/mail.controller.d.ts.map +1 -1
  229. package/dist/mail/mail.controller.js +16 -2
  230. package/dist/mail/mail.controller.js.map +1 -1
  231. package/dist/mail/mail.service.d.ts +23 -2
  232. package/dist/mail/mail.service.d.ts.map +1 -1
  233. package/dist/mail/mail.service.js +102 -13
  234. package/dist/mail/mail.service.js.map +1 -1
  235. package/dist/mail-layout/dto/update.dto.d.ts +17 -0
  236. package/dist/mail-layout/dto/update.dto.d.ts.map +1 -0
  237. package/dist/mail-layout/dto/update.dto.js +27 -0
  238. package/dist/mail-layout/dto/update.dto.js.map +1 -0
  239. package/dist/mail-layout/mail-layout.controller.d.ts +15 -0
  240. package/dist/mail-layout/mail-layout.controller.d.ts.map +1 -0
  241. package/dist/mail-layout/mail-layout.controller.js +71 -0
  242. package/dist/mail-layout/mail-layout.controller.js.map +1 -0
  243. package/dist/mail-layout/mail-layout.module.d.ts +3 -0
  244. package/dist/mail-layout/mail-layout.module.d.ts.map +1 -0
  245. package/dist/mail-layout/mail-layout.module.js +35 -0
  246. package/dist/mail-layout/mail-layout.module.js.map +1 -0
  247. package/dist/mail-layout/mail-layout.service.d.ts +27 -0
  248. package/dist/mail-layout/mail-layout.service.d.ts.map +1 -0
  249. package/dist/mail-layout/mail-layout.service.js +80 -0
  250. package/dist/mail-layout/mail-layout.service.js.map +1 -0
  251. package/dist/mcp-chat/mcp-chat.controller.d.ts +2 -2
  252. package/dist/mcp-chat/mcp-chat.service.d.ts +24 -2
  253. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  254. package/dist/mcp-chat/mcp-chat.service.js +80 -24
  255. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  256. package/dist/menu/menu.controller.d.ts +3 -3
  257. package/dist/menu/menu.service.d.ts +3 -3
  258. package/dist/notification/notification.controller.d.ts +12 -12
  259. package/dist/notification/notification.service.d.ts +12 -12
  260. package/dist/oauth/oauth-token-broker.service.d.ts +72 -0
  261. package/dist/oauth/oauth-token-broker.service.d.ts.map +1 -0
  262. package/dist/oauth/oauth-token-broker.service.js +258 -0
  263. package/dist/oauth/oauth-token-broker.service.js.map +1 -0
  264. package/dist/oauth/oauth.module.d.ts.map +1 -1
  265. package/dist/oauth/oauth.module.js +3 -1
  266. package/dist/oauth/oauth.module.js.map +1 -1
  267. package/dist/oauth/oauth.service.d.ts +2 -0
  268. package/dist/oauth/oauth.service.d.ts.map +1 -1
  269. package/dist/oauth/oauth.service.js +6 -3
  270. package/dist/oauth/oauth.service.js.map +1 -1
  271. package/dist/pdf/pdf.service.d.ts +8 -0
  272. package/dist/pdf/pdf.service.d.ts.map +1 -1
  273. package/dist/pdf/pdf.service.js +52 -0
  274. package/dist/pdf/pdf.service.js.map +1 -1
  275. package/dist/presence/presence.controller.d.ts +16 -0
  276. package/dist/presence/presence.controller.d.ts.map +1 -0
  277. package/dist/presence/presence.controller.js +60 -0
  278. package/dist/presence/presence.controller.js.map +1 -0
  279. package/dist/presence/presence.listener.d.ts +29 -0
  280. package/dist/presence/presence.listener.d.ts.map +1 -0
  281. package/dist/presence/presence.listener.js +82 -0
  282. package/dist/presence/presence.listener.js.map +1 -0
  283. package/dist/presence/presence.module.d.ts +3 -0
  284. package/dist/presence/presence.module.d.ts.map +1 -0
  285. package/dist/presence/presence.module.js +32 -0
  286. package/dist/presence/presence.module.js.map +1 -0
  287. package/dist/presence/presence.service.d.ts +42 -0
  288. package/dist/presence/presence.service.d.ts.map +1 -0
  289. package/dist/presence/presence.service.js +147 -0
  290. package/dist/presence/presence.service.js.map +1 -0
  291. package/dist/profile/dto/delete-account.dto.d.ts +8 -0
  292. package/dist/profile/dto/delete-account.dto.d.ts.map +1 -0
  293. package/dist/profile/dto/delete-account.dto.js +22 -0
  294. package/dist/profile/dto/delete-account.dto.js.map +1 -0
  295. package/dist/profile/events/profile-account-deleted.event.d.ts +12 -0
  296. package/dist/profile/events/profile-account-deleted.event.d.ts.map +1 -0
  297. package/dist/profile/events/profile-account-deleted.event.js +17 -0
  298. package/dist/profile/events/profile-account-deleted.event.js.map +1 -0
  299. package/dist/profile/profile.controller.d.ts +21 -15
  300. package/dist/profile/profile.controller.d.ts.map +1 -1
  301. package/dist/profile/profile.controller.js +75 -46
  302. package/dist/profile/profile.controller.js.map +1 -1
  303. package/dist/profile/profile.service.d.ts +30 -18
  304. package/dist/profile/profile.service.d.ts.map +1 -1
  305. package/dist/profile/profile.service.js +113 -37
  306. package/dist/profile/profile.service.js.map +1 -1
  307. package/dist/realtime/realtime.gateway.d.ts +48 -0
  308. package/dist/realtime/realtime.gateway.d.ts.map +1 -0
  309. package/dist/realtime/realtime.gateway.js +156 -0
  310. package/dist/realtime/realtime.gateway.js.map +1 -0
  311. package/dist/realtime/realtime.module.d.ts +8 -0
  312. package/dist/realtime/realtime.module.d.ts.map +1 -0
  313. package/dist/realtime/realtime.module.js +29 -0
  314. package/dist/realtime/realtime.module.js.map +1 -0
  315. package/dist/realtime/realtime.service.d.ts +51 -0
  316. package/dist/realtime/realtime.service.d.ts.map +1 -0
  317. package/dist/realtime/realtime.service.js +177 -0
  318. package/dist/realtime/realtime.service.js.map +1 -0
  319. package/dist/redis/redis-pubsub.d.ts +27 -0
  320. package/dist/redis/redis-pubsub.d.ts.map +1 -0
  321. package/dist/redis/redis-pubsub.js +140 -0
  322. package/dist/redis/redis-pubsub.js.map +1 -0
  323. package/dist/role/role.controller.d.ts +3 -3
  324. package/dist/role/role.service.d.ts +3 -3
  325. package/dist/setting/dto/create.dto.d.ts.map +1 -1
  326. package/dist/setting/dto/create.dto.js +9 -2
  327. package/dist/setting/dto/create.dto.js.map +1 -1
  328. package/dist/setting/setting.controller.d.ts +5 -5
  329. package/dist/setting/setting.service.d.ts +8 -5
  330. package/dist/setting/setting.service.d.ts.map +1 -1
  331. package/dist/setting/setting.service.js +25 -1
  332. package/dist/setting/setting.service.js.map +1 -1
  333. package/dist/user/dto/create-with-email-and-password.dto.d.ts +2 -0
  334. package/dist/user/dto/create-with-email-and-password.dto.d.ts.map +1 -1
  335. package/dist/user/dto/create-with-email-and-password.dto.js +5 -0
  336. package/dist/user/dto/create-with-email-and-password.dto.js.map +1 -1
  337. package/dist/user/dto/query-user-mail-list.dto.d.ts +5 -0
  338. package/dist/user/dto/query-user-mail-list.dto.d.ts.map +1 -0
  339. package/dist/user/dto/query-user-mail-list.dto.js +25 -0
  340. package/dist/user/dto/query-user-mail-list.dto.js.map +1 -0
  341. package/dist/user/user-credential.util.d.ts +19 -0
  342. package/dist/user/user-credential.util.d.ts.map +1 -0
  343. package/dist/user/user-credential.util.js +32 -0
  344. package/dist/user/user-credential.util.js.map +1 -0
  345. package/dist/user/user.controller.d.ts +17 -5
  346. package/dist/user/user.controller.d.ts.map +1 -1
  347. package/dist/user/user.controller.js +18 -3
  348. package/dist/user/user.controller.js.map +1 -1
  349. package/dist/user/user.service.d.ts +28 -6
  350. package/dist/user/user.service.d.ts.map +1 -1
  351. package/dist/user/user.service.js +204 -13
  352. package/dist/user/user.service.js.map +1 -1
  353. package/dist/validators/is-strong-password-with-settings.validator.d.ts.map +1 -1
  354. package/dist/validators/is-strong-password-with-settings.validator.js +11 -0
  355. package/dist/validators/is-strong-password-with-settings.validator.js.map +1 -1
  356. package/dist/validators/service-locator.d.ts +9 -0
  357. package/dist/validators/service-locator.d.ts.map +1 -1
  358. package/dist/validators/service-locator.js +12 -0
  359. package/dist/validators/service-locator.js.map +1 -1
  360. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts +3 -0
  361. package/dist/whatsapp-sender/whatsapp-sender.module.d.ts.map +1 -0
  362. package/dist/whatsapp-sender/whatsapp-sender.module.js +23 -0
  363. package/dist/whatsapp-sender/whatsapp-sender.module.js.map +1 -0
  364. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts +50 -0
  365. package/dist/whatsapp-sender/whatsapp-sender.service.d.ts.map +1 -0
  366. package/dist/whatsapp-sender/whatsapp-sender.service.js +224 -0
  367. package/dist/whatsapp-sender/whatsapp-sender.service.js.map +1 -0
  368. package/hedhog/data/integration_provider.yaml +16 -0
  369. package/hedhog/data/mail.yaml +88 -61
  370. package/hedhog/data/mail_layout.yaml +188 -0
  371. package/hedhog/data/menu.yaml +67 -3
  372. package/hedhog/data/route.yaml +135 -0
  373. package/hedhog/data/setting_group.yaml +139 -1
  374. package/hedhog/frontend/app/account/components/mfa-setup-dialog.tsx.ejs +36 -0
  375. package/hedhog/frontend/app/account/components/two-factor-auth.tsx.ejs +2 -0
  376. package/hedhog/frontend/app/account/hooks/use-mfa-setup.ts.ejs +10 -7
  377. package/hedhog/frontend/app/ai_agent/page.tsx.ejs +7 -4
  378. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-form.tsx.ejs +580 -0
  379. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-preview.tsx.ejs +150 -0
  380. package/hedhog/frontend/app/ai_instruction/_components/ai-instruction-versions.tsx.ejs +199 -0
  381. package/hedhog/frontend/app/ai_instruction/_components/prompt-diff.tsx.ejs +63 -0
  382. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.spec.ts.ejs +59 -0
  383. package/hedhog/frontend/app/ai_instruction/_lib/line-diff.ts.ejs +72 -0
  384. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.spec.ts.ejs +170 -0
  385. package/hedhog/frontend/app/ai_instruction/_lib/prompt-template.ts.ejs +167 -0
  386. package/hedhog/frontend/app/ai_instruction/_lib/types.ts.ejs +99 -0
  387. package/hedhog/frontend/app/ai_instruction/page.tsx.ejs +422 -0
  388. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +51 -4
  389. package/hedhog/frontend/app/incidents/page.tsx.ejs +400 -0
  390. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +153 -84
  391. package/hedhog/frontend/app/mail/layout/page.tsx.ejs +344 -0
  392. package/hedhog/frontend/app/mail/template/page.tsx.ejs +155 -121
  393. package/hedhog/frontend/app/mcp_chat/page.tsx.ejs +129 -17
  394. package/hedhog/frontend/app/pages/_components/page-delete-dialog.tsx.ejs +343 -0
  395. package/hedhog/frontend/app/pages/_components/page-detail-sheet.tsx.ejs +438 -0
  396. package/hedhog/frontend/app/pages/_components/page-wizard-sheet.tsx.ejs +1009 -0
  397. package/hedhog/frontend/app/pages/_components/system-page-types.ts.ejs +56 -0
  398. package/hedhog/frontend/app/pages/_components/use-system-pages.ts.ejs +171 -0
  399. package/hedhog/frontend/app/pages/page.tsx.ejs +514 -0
  400. package/hedhog/frontend/app/users/page.tsx.ejs +119 -13
  401. package/hedhog/frontend/app/users/user-activity-timeline.tsx.ejs +231 -0
  402. package/hedhog/frontend/app/users/user-mails.tsx.ejs +399 -0
  403. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +60 -75
  404. package/hedhog/frontend/messages/en.json +499 -3
  405. package/hedhog/frontend/messages/pt.json +499 -3
  406. package/hedhog/table/ai_agent.yaml +1 -0
  407. package/hedhog/table/ai_instruction.yaml +15 -0
  408. package/hedhog/table/leaked_password.yaml +18 -0
  409. package/hedhog/table/mail_layout.yaml +35 -0
  410. package/hedhog/table/mail_var.yaml +4 -0
  411. package/hedhog/table/setting.yaml +1 -0
  412. package/hedhog/table/system_incident.yaml +59 -0
  413. package/hedhog/table/user_presence.yaml +36 -0
  414. package/package.json +8 -4
  415. package/src/access-log/access-log.interceptor.ts +4 -2
  416. package/src/ai/ai-attachment.fragments.spec.ts +319 -0
  417. package/src/ai/ai-attachment.fragments.ts +35 -0
  418. package/src/ai/ai.prompts.ts +38 -0
  419. package/src/ai/ai.service.spec.ts +280 -0
  420. package/src/ai/ai.service.ts +338 -82
  421. package/src/ai/dto/chat.dto.ts +33 -3
  422. package/src/ai/dto/create-agent.dto.ts +2 -2
  423. package/src/ai/dto/update-agent.dto.ts +2 -2
  424. package/src/ai-instruction/ai-instruction.controller.ts +40 -9
  425. package/src/ai-instruction/ai-instruction.module.ts +13 -2
  426. package/src/ai-instruction/ai-instruction.service.spec.ts +658 -0
  427. package/src/ai-instruction/ai-instruction.service.ts +661 -62
  428. package/src/ai-instruction/ai-prompt-builder.service.ts +10 -1
  429. package/src/ai-instruction/ai-prompt-registry.ts +87 -0
  430. package/src/ai-instruction/ai-prompt-resolver.service.spec.ts +375 -0
  431. package/src/ai-instruction/ai-prompt-resolver.service.ts +295 -0
  432. package/src/ai-instruction/ai-prompt-template.util.spec.ts +162 -0
  433. package/src/ai-instruction/ai-prompt-template.util.ts +199 -0
  434. package/src/ai-instruction/dto/create-ai-instruction.dto.ts +39 -3
  435. package/src/ai-instruction/dto/preview-ai-instruction.dto.ts +26 -0
  436. package/src/ai-instruction/dto/update-ai-instruction.dto.ts +42 -1
  437. package/src/ai-instruction/types/ai-instruction.types.ts +24 -1
  438. package/src/auth/auth.controller.ts +31 -5
  439. package/src/auth/auth.service.spec.ts +3 -1
  440. package/src/auth/auth.service.ts +101 -36
  441. package/src/captcha/captcha.module.ts +10 -0
  442. package/src/captcha/captcha.service.spec.ts +210 -0
  443. package/src/captcha/captcha.service.ts +191 -0
  444. package/src/challenge/challenge.service.spec.ts +39 -4
  445. package/src/challenge/challenge.service.ts +72 -17
  446. package/src/core.module.ts +27 -1
  447. package/src/file/file.service.spec.ts +105 -0
  448. package/src/file/file.service.ts +60 -3
  449. package/src/incident/incident-notifier.service.spec.ts +205 -0
  450. package/src/incident/incident-notifier.service.ts +241 -0
  451. package/src/incident/incident.controller.ts +38 -0
  452. package/src/incident/incident.module.ts +19 -0
  453. package/src/incident/incident.service.spec.ts +150 -0
  454. package/src/incident/incident.service.ts +209 -0
  455. package/src/index.ts +37 -0
  456. package/src/integration-profile/dto/test.dto.ts +19 -0
  457. package/src/integration-profile/integration-profile.controller.ts +10 -1
  458. package/src/integration-profile/integration-profile.module.spec.ts +51 -0
  459. package/src/integration-profile/integration-profile.module.ts +23 -4
  460. package/src/integration-profile/integration-profile.secrets.ts +9 -1
  461. package/src/integration-profile/integration-profile.service.spec.ts +279 -0
  462. package/src/integration-profile/integration-profile.service.ts +120 -124
  463. package/src/integration-profile/integration-profile.utils.spec.ts +22 -0
  464. package/src/integration-profile/integration-profile.utils.ts +40 -3
  465. package/src/integration-profile/testers/ai.testers.ts +165 -0
  466. package/src/integration-profile/testers/captcha.testers.spec.ts +92 -0
  467. package/src/integration-profile/testers/captcha.testers.ts +139 -0
  468. package/src/integration-profile/testers/email.testers.ts +161 -0
  469. package/src/integration-profile/testers/http-probe.spec.ts +153 -0
  470. package/src/integration-profile/testers/http-probe.ts +171 -0
  471. package/src/integration-profile/testers/index.ts +30 -0
  472. package/src/integration-profile/testers/infrastructure.testers.ts +122 -0
  473. package/src/integration-profile/testers/integration-tester.registry.spec.ts +75 -0
  474. package/src/integration-profile/testers/integration-tester.registry.ts +43 -0
  475. package/src/integration-profile/testers/integration-tester.types.ts +61 -0
  476. package/src/integration-profile/testers/messaging.testers.ts +102 -0
  477. package/src/integration-profile/testers/oauth.testers.spec.ts +220 -0
  478. package/src/integration-profile/testers/oauth.testers.ts +305 -0
  479. package/src/integration-profile/testers/payment.testers.ts +185 -0
  480. package/src/integration-profile/testers/remaining.testers.spec.ts +401 -0
  481. package/src/integration-profile/testers/storage.testers.spec.ts +185 -0
  482. package/src/integration-profile/testers/storage.testers.ts +226 -0
  483. package/src/integration-profile/testers/tester-coverage.spec.ts +58 -0
  484. package/src/language/en.json +4 -2
  485. package/src/language/pt.json +4 -2
  486. package/src/leaked-password/README.md +83 -0
  487. package/src/leaked-password/leaked-password.controller.ts +52 -0
  488. package/src/leaked-password/leaked-password.module.ts +18 -0
  489. package/src/leaked-password/leaked-password.service.ts +242 -0
  490. package/src/leaked-password/seed/build-seed-sql.mjs +180 -0
  491. package/src/leaked-password/seed/common-passwords.txt +335 -0
  492. package/src/mail/dto/preview.dto.ts +50 -0
  493. package/src/mail/dto/send.dto.ts +14 -1
  494. package/src/mail/mail-template.spec.ts +197 -0
  495. package/src/mail/mail.controller.ts +9 -2
  496. package/src/mail/mail.service.spec.ts +116 -1
  497. package/src/mail/mail.service.ts +118 -12
  498. package/src/mail-layout/dto/update.dto.ts +19 -0
  499. package/src/mail-layout/mail-layout.controller.ts +47 -0
  500. package/src/mail-layout/mail-layout.module.ts +22 -0
  501. package/src/mail-layout/mail-layout.service.ts +94 -0
  502. package/src/mcp-chat/mcp-chat.service.spec.ts +236 -0
  503. package/src/mcp-chat/mcp-chat.service.ts +122 -26
  504. package/src/oauth/oauth-token-broker.service.spec.ts +172 -0
  505. package/src/oauth/oauth-token-broker.service.ts +363 -0
  506. package/src/oauth/oauth.module.ts +3 -1
  507. package/src/oauth/oauth.service.ts +8 -2
  508. package/src/pdf/pdf.service.ts +75 -0
  509. package/src/presence/presence.controller.ts +29 -0
  510. package/src/presence/presence.listener.ts +53 -0
  511. package/src/presence/presence.module.ts +19 -0
  512. package/src/presence/presence.service.spec.ts +97 -0
  513. package/src/presence/presence.service.ts +160 -0
  514. package/src/profile/dto/delete-account.dto.ts +11 -0
  515. package/src/profile/events/profile-account-deleted.event.ts +13 -0
  516. package/src/profile/profile.controller.ts +46 -34
  517. package/src/profile/profile.service.ts +138 -38
  518. package/src/realtime/realtime.gateway.ts +138 -0
  519. package/src/realtime/realtime.module.ts +16 -0
  520. package/src/realtime/realtime.service.spec.ts +50 -0
  521. package/src/realtime/realtime.service.ts +187 -0
  522. package/src/redis/redis-pubsub.ts +179 -0
  523. package/src/setting/dto/create.dto.ts +13 -2
  524. package/src/setting/setting.service.spec.ts +56 -0
  525. package/src/setting/setting.service.ts +29 -1
  526. package/src/user/dto/create-with-email-and-password.dto.ts +6 -1
  527. package/src/user/dto/query-user-mail-list.dto.ts +10 -0
  528. package/src/user/user-credential.util.ts +35 -0
  529. package/src/user/user.controller.ts +17 -2
  530. package/src/user/user.service.spec.ts +153 -0
  531. package/src/user/user.service.ts +255 -12
  532. package/src/validators/is-strong-password-with-settings.validator.ts +12 -0
  533. package/src/validators/service-locator.ts +15 -0
  534. package/src/whatsapp-sender/whatsapp-sender.module.ts +10 -0
  535. package/src/whatsapp-sender/whatsapp-sender.service.ts +313 -0
@@ -0,0 +1,363 @@
1
+ import {
2
+ PrismaService,
3
+ user_account_provider_52222e2ecb_enum,
4
+ } from '@hed-hog/api-prisma';
5
+ import { HttpService } from '@nestjs/axios';
6
+ import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
7
+ import { lastValueFrom } from 'rxjs';
8
+ import { buildOAuthConfigFromIntegration } from '../integration-profile/integration-profile.utils';
9
+ import { SecurityService } from '../security/security.service';
10
+ import { SettingService } from '../setting/setting.service';
11
+
12
+ /**
13
+ * Reasons a brokered access token could not be produced. Consumers (e.g. the
14
+ * `microsoft` library) map these to HTTP responses so the UI can react — most
15
+ * importantly, `not_connected`/`refresh_revoked` should prompt the user to
16
+ * (re)connect their account, and `missing_scope` to re-consent.
17
+ */
18
+ export type OAuthTokenBrokerReason =
19
+ | 'not_connected'
20
+ | 'refresh_revoked'
21
+ | 'missing_scope'
22
+ | 'provider_not_configured'
23
+ | 'upstream_failure';
24
+
25
+ export class OAuthTokenBrokerError extends Error {
26
+ constructor(
27
+ readonly reason: OAuthTokenBrokerReason,
28
+ message: string,
29
+ readonly provider: string,
30
+ readonly cause?: unknown,
31
+ ) {
32
+ super(message);
33
+ this.name = 'OAuthTokenBrokerError';
34
+ }
35
+ }
36
+
37
+ export interface BrokeredAccessToken {
38
+ accessToken: string;
39
+ /** Absolute expiry of the access token. */
40
+ expiresAt: Date;
41
+ /** Scopes granted to the stored refresh token (best-effort, from `user_account.scopes`). */
42
+ scopes: string[];
43
+ }
44
+
45
+ interface ProviderTokenConfig {
46
+ tokenUrl: string;
47
+ clientId: string;
48
+ clientSecret: string;
49
+ }
50
+
51
+ interface CacheEntry {
52
+ accessToken: string;
53
+ expiresAt: Date;
54
+ scopes: string[];
55
+ }
56
+
57
+ /**
58
+ * Exchanges a user's stored (encrypted) OAuth **refresh token** for a fresh
59
+ * **access token**, so the backend can call provider APIs (Microsoft Graph,
60
+ * Google, ...) on that user's behalf — the "delegated" access mode.
61
+ *
62
+ * The refresh token is written by {@link OAuthService} during login/connect
63
+ * (encrypted with {@link SecurityService.encrypt} and stored as bytes in
64
+ * `user_account.refresh_token`); this service reads it back, decrypts it, runs
65
+ * a `grant_type=refresh_token` exchange, persists the rotated refresh token,
66
+ * and caches the access token in memory until shortly before it expires.
67
+ *
68
+ * It intentionally reuses the SAME `integration_profile` credentials already
69
+ * configured for OAuth login — no new provider, table, or setting is introduced.
70
+ */
71
+ @Injectable()
72
+ export class OAuthTokenBrokerService {
73
+ private readonly logger = new Logger(OAuthTokenBrokerService.name);
74
+
75
+ /** access-token cache keyed by `${provider}:${userId}`. */
76
+ private readonly cache = new Map<string, CacheEntry>();
77
+
78
+ /** Refresh a bit early so an in-flight request never rides an about-to-expire token. */
79
+ private static readonly EXPIRY_SKEW_MS = 60 * 1000;
80
+
81
+ constructor(
82
+ private readonly http: HttpService,
83
+ @Inject(forwardRef(() => SettingService))
84
+ private readonly setting: SettingService,
85
+ private readonly prisma: PrismaService,
86
+ private readonly security: SecurityService,
87
+ ) {}
88
+
89
+ /** Normalizes a provider to the enum form stored in `user_account.provider`. */
90
+ private toAccountProvider(
91
+ provider: string,
92
+ ): user_account_provider_52222e2ecb_enum {
93
+ return String(provider)
94
+ .toLowerCase()
95
+ .replace(/-/g, '_') as user_account_provider_52222e2ecb_enum;
96
+ }
97
+
98
+ /** Setting slug that points at the integration profile for this provider. */
99
+ private profileSettingSlug(provider: string): string | null {
100
+ switch (String(provider).toLowerCase().replace(/_/g, '-')) {
101
+ case 'microsoft-entra-id':
102
+ return 'oauth-microsoft-entra-id-profile-id';
103
+ case 'microsoft':
104
+ return 'oauth-microsoft-profile-id';
105
+ case 'google':
106
+ return 'oauth-google-profile-id';
107
+ default:
108
+ return null;
109
+ }
110
+ }
111
+
112
+ private async resolveProviderConfig(
113
+ provider: string,
114
+ ): Promise<ProviderTokenConfig> {
115
+ const settingSlug = this.profileSettingSlug(provider);
116
+ if (!settingSlug) {
117
+ throw new OAuthTokenBrokerError(
118
+ 'provider_not_configured',
119
+ `Token broker does not support provider "${provider}".`,
120
+ provider,
121
+ );
122
+ }
123
+
124
+ const settings = await this.setting.getSettingValues([settingSlug]);
125
+ const profileSlug = String(settings[settingSlug] ?? '').trim();
126
+ if (!profileSlug) {
127
+ throw new OAuthTokenBrokerError(
128
+ 'provider_not_configured',
129
+ `OAuth profile for "${provider}" is not configured.`,
130
+ provider,
131
+ );
132
+ }
133
+
134
+ const profile = await this.prisma.integration_profile.findUnique({
135
+ where: { slug: profileSlug },
136
+ include: { integration_provider: { select: { slug: true } } },
137
+ });
138
+ if (!profile) {
139
+ throw new OAuthTokenBrokerError(
140
+ 'provider_not_configured',
141
+ `OAuth profile "${profileSlug}" for "${provider}" was not found.`,
142
+ provider,
143
+ );
144
+ }
145
+
146
+ const { clientId, clientSecret, tenantId } = buildOAuthConfigFromIntegration(
147
+ profile.integration_provider.slug,
148
+ profile.config,
149
+ );
150
+
151
+ const normalized = String(provider).toLowerCase().replace(/_/g, '-');
152
+ let tokenUrl: string;
153
+ if (normalized === 'microsoft-entra-id') {
154
+ if (!tenantId) {
155
+ throw new OAuthTokenBrokerError(
156
+ 'provider_not_configured',
157
+ 'Microsoft Entra ID OAuth profile is missing tenant_id.',
158
+ provider,
159
+ );
160
+ }
161
+ tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
162
+ } else if (normalized === 'microsoft') {
163
+ tokenUrl = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';
164
+ } else {
165
+ // google
166
+ tokenUrl = 'https://oauth2.googleapis.com/token';
167
+ }
168
+
169
+ return { tokenUrl, clientId, clientSecret };
170
+ }
171
+
172
+ private async loadUserAccount(userId: number, provider: string) {
173
+ const account = await this.prisma.user_account.findFirst({
174
+ where: {
175
+ user_id: userId,
176
+ provider: this.toAccountProvider(provider),
177
+ },
178
+ select: {
179
+ id: true,
180
+ refresh_token: true,
181
+ scopes: true,
182
+ token_expires_at: true,
183
+ },
184
+ });
185
+
186
+ if (!account || !account.refresh_token) {
187
+ throw new OAuthTokenBrokerError(
188
+ 'not_connected',
189
+ `User ${userId} has no connected "${provider}" account.`,
190
+ provider,
191
+ );
192
+ }
193
+
194
+ return account;
195
+ }
196
+
197
+ private parseScopes(scopes?: string | null): string[] {
198
+ return String(scopes ?? '')
199
+ .split(/[\s,]+/)
200
+ .map((scope) => scope.trim())
201
+ .filter(Boolean);
202
+ }
203
+
204
+ /** Scopes granted to the stored refresh token, from `user_account.scopes`. */
205
+ async getGrantedScopes(userId: number, provider: string): Promise<string[]> {
206
+ const account = await this.prisma.user_account.findFirst({
207
+ where: { user_id: userId, provider: this.toAccountProvider(provider) },
208
+ select: { scopes: true },
209
+ });
210
+ return this.parseScopes(account?.scopes);
211
+ }
212
+
213
+ /** Whether the user has this provider connected with a usable refresh token. */
214
+ async isConnected(userId: number, provider: string): Promise<boolean> {
215
+ const account = await this.prisma.user_account.findFirst({
216
+ where: { user_id: userId, provider: this.toAccountProvider(provider) },
217
+ select: { refresh_token: true },
218
+ });
219
+ return Boolean(account?.refresh_token);
220
+ }
221
+
222
+ private cacheKey(userId: number, provider: string): string {
223
+ return `${String(provider).toLowerCase()}:${userId}`;
224
+ }
225
+
226
+ /**
227
+ * Returns a fresh access token for the user, refreshing (and rotating the
228
+ * stored refresh token) when the cache is cold or about to expire.
229
+ *
230
+ * @throws {OAuthTokenBrokerError} with a typed `reason` on any failure.
231
+ */
232
+ async getAccessToken(
233
+ userId: number,
234
+ provider: string,
235
+ ): Promise<BrokeredAccessToken> {
236
+ const key = this.cacheKey(userId, provider);
237
+ const cached = this.cache.get(key);
238
+ if (
239
+ cached &&
240
+ cached.expiresAt.getTime() - Date.now() > OAuthTokenBrokerService.EXPIRY_SKEW_MS
241
+ ) {
242
+ return { ...cached };
243
+ }
244
+
245
+ const [config, account] = await Promise.all([
246
+ this.resolveProviderConfig(provider),
247
+ this.loadUserAccount(userId, provider),
248
+ ]);
249
+
250
+ let decryptedRefreshToken: string;
251
+ try {
252
+ decryptedRefreshToken = this.security.decrypt(account.refresh_token.toString());
253
+ } catch (error) {
254
+ throw new OAuthTokenBrokerError(
255
+ 'refresh_revoked',
256
+ `Failed to decrypt stored refresh token for "${provider}".`,
257
+ provider,
258
+ error,
259
+ );
260
+ }
261
+
262
+ const grantedScopes = this.parseScopes(account.scopes);
263
+ const scopeParam = grantedScopes.includes('offline_access')
264
+ ? grantedScopes
265
+ : [...grantedScopes, 'offline_access'];
266
+
267
+ const body = new URLSearchParams({
268
+ client_id: config.clientId,
269
+ client_secret: config.clientSecret,
270
+ grant_type: 'refresh_token',
271
+ refresh_token: decryptedRefreshToken,
272
+ });
273
+ if (scopeParam.length > 1) {
274
+ body.set('scope', scopeParam.join(' '));
275
+ }
276
+
277
+ let token: {
278
+ access_token: string;
279
+ refresh_token?: string;
280
+ expires_in?: number;
281
+ scope?: string;
282
+ };
283
+ try {
284
+ const response = await lastValueFrom(
285
+ this.http.post(config.tokenUrl, body.toString(), {
286
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
287
+ }),
288
+ );
289
+ token = response.data;
290
+ } catch (error: any) {
291
+ const providerError = error?.response?.data?.error;
292
+ if (providerError === 'invalid_grant') {
293
+ // Refresh token revoked/expired — the user must reconnect.
294
+ this.cache.delete(key);
295
+ throw new OAuthTokenBrokerError(
296
+ 'refresh_revoked',
297
+ `Refresh token for "${provider}" is no longer valid. Reconnect the account.`,
298
+ provider,
299
+ error,
300
+ );
301
+ }
302
+ this.logger.warn(
303
+ `Token refresh failed: provider=${provider} userId=${userId} error=${providerError ?? error?.message ?? 'unknown'}`,
304
+ );
305
+ throw new OAuthTokenBrokerError(
306
+ 'upstream_failure',
307
+ `Unable to refresh "${provider}" access token.`,
308
+ provider,
309
+ error,
310
+ );
311
+ }
312
+
313
+ if (!token?.access_token) {
314
+ throw new OAuthTokenBrokerError(
315
+ 'upstream_failure',
316
+ `Provider "${provider}" returned no access token.`,
317
+ provider,
318
+ );
319
+ }
320
+
321
+ const expiresAt = new Date(
322
+ Date.now() + (token.expires_in ? token.expires_in * 1000 : 3600 * 1000),
323
+ );
324
+ const resolvedScopes = token.scope
325
+ ? this.parseScopes(token.scope)
326
+ : grantedScopes;
327
+
328
+ // Persist the rotated refresh token so the next call starts from the newest
329
+ // one (Microsoft rotates refresh tokens on every exchange).
330
+ if (token.refresh_token && token.refresh_token !== decryptedRefreshToken) {
331
+ try {
332
+ const encrypted = this.security.encrypt(token.refresh_token);
333
+ await this.prisma.user_account.update({
334
+ where: { id: account.id },
335
+ data: {
336
+ refresh_token: Buffer.from(encrypted),
337
+ token_expires_at: expiresAt,
338
+ ...(token.scope ? { scopes: resolvedScopes.join(',') } : {}),
339
+ },
340
+ });
341
+ } catch (error) {
342
+ // A persistence failure must not fail the request — the access token is
343
+ // already valid; log and continue (next refresh reuses the old token).
344
+ this.logger.warn(
345
+ `Failed to persist rotated refresh token: provider=${provider} userId=${userId} error=${error instanceof Error ? error.message : 'unknown'}`,
346
+ );
347
+ }
348
+ }
349
+
350
+ const entry: CacheEntry = {
351
+ accessToken: token.access_token,
352
+ expiresAt,
353
+ scopes: resolvedScopes,
354
+ };
355
+ this.cache.set(key, entry);
356
+ return { ...entry };
357
+ }
358
+
359
+ /** Drops any cached access token for a user/provider (e.g. after disconnect). */
360
+ invalidate(userId: number, provider: string): void {
361
+ this.cache.delete(this.cacheKey(userId, provider));
362
+ }
363
+ }
@@ -10,6 +10,7 @@ import { SecurityModule } from '../security/security.module';
10
10
  import { TokenModule } from '../token/token.module';
11
11
  import { UserModule } from '../user/user.module';
12
12
  import { OAuthCallbackCoordinatorService } from './oauth-callback-coordinator.service';
13
+ import { OAuthTokenBrokerService } from './oauth-token-broker.service';
13
14
  import { OAuthController } from './oauth.controller';
14
15
  import { OAuthService } from './oauth.service';
15
16
  import { AppleProvider } from './providers/apple.provider';
@@ -36,6 +37,7 @@ import { MicrosoftProvider } from './providers/microsoft.provider';
36
37
  controllers: [OAuthController],
37
38
  providers: [
38
39
  OAuthCallbackCoordinatorService,
40
+ OAuthTokenBrokerService,
39
41
  OAuthService,
40
42
  GoogleProvider,
41
43
  FacebookProvider,
@@ -45,6 +47,6 @@ import { MicrosoftProvider } from './providers/microsoft.provider';
45
47
  AppleProvider,
46
48
  LinkedinProvider,
47
49
  ],
48
- exports: [OAuthService],
50
+ exports: [OAuthService, OAuthTokenBrokerService],
49
51
  })
50
52
  export class OAuthModule {}
@@ -50,6 +50,8 @@ type HandleCallbackProps = {
50
50
  requestOrigin?: string;
51
51
  /** Referer header, used as a fallback when the Origin header is absent. */
52
52
  requestReferer?: string;
53
+ /** App key that initiated the flow (decoded from the signed web state), used for per-app email branding. */
54
+ app?: string;
53
55
  };
54
56
 
55
57
  @Injectable()
@@ -338,6 +340,7 @@ export class OAuthService {
338
340
  profile,
339
341
  userId,
340
342
  includeRefreshTokenInBody: true,
343
+ app: webState?.app,
341
344
  });
342
345
  })) as { accessToken: string; refreshToken?: string };
343
346
 
@@ -365,6 +368,7 @@ export class OAuthService {
365
368
  profile,
366
369
  userId,
367
370
  includeRefreshTokenInBody,
371
+ app: webState?.app,
368
372
  });
369
373
 
370
374
  this.logger.log(
@@ -741,12 +745,13 @@ export class OAuthService {
741
745
  profile,
742
746
  userId,
743
747
  includeRefreshTokenInBody,
748
+ app,
744
749
  }: Omit<HandleCallbackProps, 'code' | 'email' | 'redirectUri' | 'state'> & { profile: any }) {
745
750
  switch (type) {
746
751
  case 'login':
747
752
  return this.handleLogin(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
748
753
  case 'register':
749
- return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
754
+ return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody, app);
750
755
  case 'connect':
751
756
  return this.handleConnect(res, locale, ipAddress, userAgent, provider, profile, userId, includeRefreshTokenInBody);
752
757
  default:
@@ -896,7 +901,7 @@ export class OAuthService {
896
901
  }
897
902
  }
898
903
 
899
- private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean) {
904
+ private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean, app?: string) {
900
905
  const existingUser = await this.prisma.user_identifier.findFirst({
901
906
  where: {
902
907
  type: 'email',
@@ -959,6 +964,7 @@ export class OAuthService {
959
964
  email: profile.email,
960
965
  slug: 'auth-sign-up',
961
966
  variables: { name: profile.name },
967
+ app,
962
968
  });
963
969
 
964
970
  const pictureURL = profile.picture?.data?.url || profile.picture;
@@ -229,6 +229,81 @@ export class PdfService {
229
229
  }
230
230
  }
231
231
 
232
+ // ── HTML → PNG image ──────────────────────────────────────────────────────
233
+
234
+ /**
235
+ * Renders an HTML string to a PNG image buffer via a Playwright/Chromium
236
+ * screenshot. Shares the same layout contract as {@link generatePdfFromHtml}
237
+ * (the viewport matches `width`/`height`), so the image is a pixel-faithful
238
+ * preview of what the PDF would produce for the same HTML — used for
239
+ * certificate thumbnails on the web/app.
240
+ */
241
+ async generateImageFromHtml(html: string, options: PdfGenerateOptions = {}): Promise<Buffer> {
242
+ const executablePath = this.configService.get<string>('CHROMIUM_EXECUTABLE_PATH');
243
+ const noSandbox = this.configService.get<string>('CHROMIUM_NO_SANDBOX') === 'true';
244
+
245
+ const launchArgs = noSandbox
246
+ ? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
247
+ : [];
248
+
249
+ const width = parsePxSize(options.width, 794);
250
+ const height = parsePxSize(options.height, 1123);
251
+
252
+ let browser: any = null;
253
+
254
+ try {
255
+ const importPlaywright = new Function(
256
+ 'moduleName',
257
+ 'return import(moduleName);',
258
+ ) as (moduleName: string) => Promise<any>;
259
+
260
+ const playwright = await importPlaywright('playwright');
261
+
262
+ browser = await playwright.chromium.launch({
263
+ headless: true,
264
+ ...(executablePath ? { executablePath } : {}),
265
+ args: launchArgs,
266
+ });
267
+
268
+ const page = await browser.newPage();
269
+ await page.setViewportSize({ width, height });
270
+ await page.setContent(html, { waitUntil: 'networkidle' });
271
+
272
+ const imageBuffer = await page.screenshot({
273
+ type: 'png',
274
+ clip: { x: 0, y: 0, width, height },
275
+ });
276
+
277
+ return Buffer.from(imageBuffer);
278
+ } catch (error) {
279
+ const errorMessage = error instanceof Error ? error.message : String(error);
280
+ const errorStack = error instanceof Error ? (error.stack ?? errorMessage) : String(error);
281
+
282
+ const missingRuntime =
283
+ /Cannot find package ['"]playwright['"]|Cannot find module ['"]playwright['"]|Executable doesn't exist|browserType\.launch|Failed to launch|Please run the following command/i.test(
284
+ errorMessage,
285
+ );
286
+
287
+ this.logger.error(`Image generation failed. ${errorMessage}`, errorStack);
288
+
289
+ if (missingRuntime) {
290
+ throw new InternalServerErrorException(
291
+ 'Playwright runtime is not available. Run `pnpm --filter api run playwright:install` in the API environment.',
292
+ );
293
+ }
294
+
295
+ throw new InternalServerErrorException(`Image generation failed: ${errorMessage}`);
296
+ } finally {
297
+ if (browser) {
298
+ try {
299
+ await browser.close();
300
+ } catch {
301
+ // Ignore close errors
302
+ }
303
+ }
304
+ }
305
+ }
306
+
232
307
  // ── Template engine ───────────────────────────────────────────────────────
233
308
 
234
309
  /**
@@ -0,0 +1,29 @@
1
+ import { User } from '@hed-hog/api';
2
+ import { Controller, Get, Post, Query } from '@nestjs/common';
3
+ import { PresenceService } from './presence.service';
4
+
5
+ /**
6
+ * Presence endpoints — authenticated (no @Role): any signed-in user may send
7
+ * their own heartbeat and read presence for a set of users.
8
+ */
9
+ @Controller('presence')
10
+ export class PresenceController {
11
+ constructor(private readonly presence: PresenceService) {}
12
+
13
+ /** Browser heartbeat: marks the signed-in user as online in the app. */
14
+ @Post('heartbeat')
15
+ async heartbeat(@User() user: any) {
16
+ await this.presence.heartbeat(Number(user?.id) || 0);
17
+ return { ok: true };
18
+ }
19
+
20
+ /** Combined presence for `userIds` (comma-separated). */
21
+ @Get()
22
+ async getStatuses(@Query('userIds') userIds?: string) {
23
+ const ids = String(userIds ?? '')
24
+ .split(',')
25
+ .map((s) => Number(s.trim()))
26
+ .filter((n) => Number.isInteger(n) && n > 0);
27
+ return this.presence.getStatuses(ids);
28
+ }
29
+ }
@@ -0,0 +1,53 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { OnEvent } from '@nestjs/event-emitter';
3
+ import { Cron } from '@nestjs/schedule';
4
+ import { RealtimeService } from '../realtime/realtime.service';
5
+ import { REALTIME_EVENTS } from '../realtime/realtime.gateway';
6
+ import { PresenceService } from './presence.service';
7
+
8
+ /**
9
+ * Bridges the generic realtime layer to presence: a live socket IS the app
10
+ * heartbeat. On (first) connect the user goes online and the change is pushed;
11
+ * while connected, a cron keeps the heartbeat fresh (so it never lapses under
12
+ * the 60s window); on disconnect the new status is pushed.
13
+ */
14
+ @Injectable()
15
+ export class PresenceListener {
16
+ private readonly logger = new Logger(PresenceListener.name);
17
+
18
+ constructor(
19
+ private readonly presence: PresenceService,
20
+ private readonly realtime: RealtimeService,
21
+ ) {}
22
+
23
+ @OnEvent(REALTIME_EVENTS.userConnected)
24
+ async onConnected(payload: { userId: number; firstConnection: boolean }) {
25
+ await this.presence.heartbeat(payload.userId);
26
+ // Only push when the user actually transitioned to online (first socket).
27
+ if (payload.firstConnection) {
28
+ await this.presence.pushStatus(payload.userId).catch(() => undefined);
29
+ }
30
+ }
31
+
32
+ @OnEvent(REALTIME_EVENTS.userDisconnected)
33
+ async onDisconnected(payload: { userId: number; remainingLocalSockets: number }) {
34
+ // Recompute/push; if the user still has sockets elsewhere the heartbeat keeps
35
+ // them online, otherwise they go offline once the heartbeat window elapses.
36
+ if (payload.remainingLocalSockets === 0) {
37
+ await this.presence.pushStatus(payload.userId).catch(() => undefined);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Keeps the app heartbeat fresh for every locally-connected user. A connected
43
+ * socket must stay "online" past the 60s heartbeat window, so refresh every
44
+ * 30s. Silent (no push) — nothing changed.
45
+ */
46
+ @Cron('*/30 * * * * *')
47
+ async refreshHeartbeats() {
48
+ const userIds = this.realtime.getLocallyConnectedUserIds();
49
+ for (const userId of userIds) {
50
+ await this.presence.heartbeat(userId).catch(() => undefined);
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,19 @@
1
+ import { PrismaModule } from '@hed-hog/api-prisma';
2
+ import { forwardRef, Module } from '@nestjs/common';
3
+ import { RealtimeModule } from '../realtime/realtime.module';
4
+ import { SettingModule } from '../setting/setting.module';
5
+ import { PresenceController } from './presence.controller';
6
+ import { PresenceListener } from './presence.listener';
7
+ import { PresenceService } from './presence.service';
8
+
9
+ @Module({
10
+ imports: [
11
+ forwardRef(() => PrismaModule),
12
+ forwardRef(() => SettingModule),
13
+ forwardRef(() => RealtimeModule),
14
+ ],
15
+ controllers: [PresenceController],
16
+ providers: [PresenceService, PresenceListener],
17
+ exports: [PresenceService],
18
+ })
19
+ export class PresenceModule {}