@hed-hog/core 0.0.356 → 0.0.359

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 (250) hide show
  1. package/dist/auth/guards/auth.guard.d.ts +6 -1
  2. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  3. package/dist/auth/guards/auth.guard.js +33 -2
  4. package/dist/auth/guards/auth.guard.js.map +1 -1
  5. package/dist/file/file.controller.d.ts +4 -2
  6. package/dist/file/file.controller.d.ts.map +1 -1
  7. package/dist/file/file.controller.js +44 -38
  8. package/dist/file/file.controller.js.map +1 -1
  9. package/dist/file/file.service.d.ts +2 -2
  10. package/dist/file/file.service.d.ts.map +1 -1
  11. package/dist/file/file.service.js +25 -42
  12. package/dist/file/file.service.js.map +1 -1
  13. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  14. package/dist/file/provider/azure.provider.js +10 -15
  15. package/dist/file/provider/azure.provider.js.map +1 -1
  16. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  17. package/dist/file/provider/gcs.provider.js +8 -9
  18. package/dist/file/provider/gcs.provider.js.map +1 -1
  19. package/dist/file/provider/local.provider.d.ts.map +1 -1
  20. package/dist/file/provider/local.provider.js +11 -10
  21. package/dist/file/provider/local.provider.js.map +1 -1
  22. package/dist/file/provider/provider.factory.d.ts +1 -1
  23. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  24. package/dist/file/provider/provider.factory.js +16 -6
  25. package/dist/file/provider/provider.factory.js.map +1 -1
  26. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  27. package/dist/file/provider/s3.provider.js +4 -20
  28. package/dist/file/provider/s3.provider.js.map +1 -1
  29. package/dist/file/provider/spaces.provider.d.ts.map +1 -1
  30. package/dist/file/provider/spaces.provider.js +4 -19
  31. package/dist/file/provider/spaces.provider.js.map +1 -1
  32. package/dist/integration-profile/integration-profile.controller.d.ts +1 -1
  33. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
  34. package/dist/integration-profile/integration-profile.controller.js +4 -3
  35. package/dist/integration-profile/integration-profile.controller.js.map +1 -1
  36. package/dist/integration-profile/integration-profile.service.d.ts +1 -1
  37. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
  38. package/dist/integration-profile/integration-profile.service.js +7 -4
  39. package/dist/integration-profile/integration-profile.service.js.map +1 -1
  40. package/dist/integration-profile/integration-profile.utils.d.ts +6 -0
  41. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
  42. package/dist/integration-profile/integration-profile.utils.js +9 -0
  43. package/dist/integration-profile/integration-profile.utils.js.map +1 -1
  44. package/dist/mail/mail.module.d.ts.map +1 -1
  45. package/dist/mail/mail.module.js +9 -4
  46. package/dist/mail/mail.module.js.map +1 -1
  47. package/dist/mail/mail.service.d.ts +2 -1
  48. package/dist/mail/mail.service.d.ts.map +1 -1
  49. package/dist/mail/mail.service.js +11 -7
  50. package/dist/mail/mail.service.js.map +1 -1
  51. package/dist/mcp/guards/mcp-auth.guard.js +1 -1
  52. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  53. package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
  54. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
  55. package/dist/mcp/mcp-api-key.controller.js +1 -1
  56. package/dist/mcp/mcp-api-key.controller.js.map +1 -1
  57. package/dist/mcp/mcp-api-key.service.d.ts +2 -1
  58. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
  59. package/dist/mcp/mcp-api-key.service.js +6 -2
  60. package/dist/mcp/mcp-api-key.service.js.map +1 -1
  61. package/dist/mcp/mcp.module.d.ts.map +1 -1
  62. package/dist/mcp/mcp.module.js +1 -3
  63. package/dist/mcp/mcp.module.js.map +1 -1
  64. package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
  65. package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
  66. package/dist/oauth/oauth.controller.d.ts +4 -1
  67. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  68. package/dist/oauth/oauth.controller.js +31 -5
  69. package/dist/oauth/oauth.controller.js.map +1 -1
  70. package/dist/oauth/oauth.service.d.ts +12 -1
  71. package/dist/oauth/oauth.service.d.ts.map +1 -1
  72. package/dist/oauth/oauth.service.js +125 -23
  73. package/dist/oauth/oauth.service.js.map +1 -1
  74. package/dist/oauth/providers/abstract.provider.d.ts +1 -1
  75. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  76. package/dist/oauth/providers/facebook.provider.d.ts +5 -2
  77. package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
  78. package/dist/oauth/providers/facebook.provider.js +34 -19
  79. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  80. package/dist/oauth/providers/github.provider.d.ts +6 -2
  81. package/dist/oauth/providers/github.provider.d.ts.map +1 -1
  82. package/dist/oauth/providers/github.provider.js +43 -20
  83. package/dist/oauth/providers/github.provider.js.map +1 -1
  84. package/dist/oauth/providers/google.provider.d.ts +5 -2
  85. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  86. package/dist/oauth/providers/google.provider.js +34 -19
  87. package/dist/oauth/providers/google.provider.js.map +1 -1
  88. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
  89. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  90. package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
  91. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  92. package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
  93. package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
  94. package/dist/oauth/providers/microsoft.provider.js +34 -19
  95. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  96. package/dist/profile/profile.controller.js +1 -1
  97. package/dist/profile/profile.controller.js.map +1 -1
  98. package/dist/security/security.service.d.ts +2 -0
  99. package/dist/security/security.service.d.ts.map +1 -1
  100. package/dist/security/security.service.js +21 -0
  101. package/dist/security/security.service.js.map +1 -1
  102. package/dist/setting/setting.service.d.ts +26 -1
  103. package/dist/setting/setting.service.d.ts.map +1 -1
  104. package/dist/setting/setting.service.js +240 -5
  105. package/dist/setting/setting.service.js.map +1 -1
  106. package/dist/user/user.controller.d.ts +2 -1
  107. package/dist/user/user.controller.d.ts.map +1 -1
  108. package/dist/user/user.controller.js +5 -4
  109. package/dist/user/user.controller.js.map +1 -1
  110. package/dist/user/user.service.d.ts +4 -2
  111. package/dist/user/user.service.d.ts.map +1 -1
  112. package/dist/user/user.service.js +19 -4
  113. package/dist/user/user.service.js.map +1 -1
  114. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
  115. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
  116. package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
  117. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
  118. package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
  119. package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
  120. package/dist/webhook-integration/webhook-command.registry.js +36 -0
  121. package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
  122. package/dist/webhook-integration/webhook-integration.controller.d.ts +19 -0
  123. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  124. package/dist/webhook-integration/webhook-integration.controller.js +38 -0
  125. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  126. package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
  127. package/dist/webhook-integration/webhook-integration.module.js +5 -0
  128. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  129. package/dist/webhook-integration/webhook-integration.service.d.ts +25 -1
  130. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  131. package/dist/webhook-integration/webhook-integration.service.js +332 -23
  132. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  133. package/hedhog/data/integration_provider.yaml +16 -0
  134. package/hedhog/data/menu.yaml +0 -16
  135. package/hedhog/data/route.yaml +32 -383
  136. package/hedhog/data/setting_group.yaml +179 -174
  137. package/hedhog/data/setting_subgroup.yaml +16 -0
  138. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
  139. package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
  140. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
  141. package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
  142. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +414 -76
  143. package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
  144. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
  145. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +186 -18
  146. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +597 -21
  147. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +2 -2
  148. package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
  149. package/hedhog/frontend/messages/en.json +67 -3
  150. package/hedhog/frontend/messages/pt.json +67 -3
  151. package/hedhog/table/file.yaml +1 -1
  152. package/hedhog/table/mcp_api_key.yaml +4 -0
  153. package/hedhog/table/webhook_action.yaml +57 -1
  154. package/package.json +5 -4
  155. package/src/auth/guards/auth.guard.ts +32 -0
  156. package/src/file/file.controller.ts +51 -42
  157. package/src/file/file.service.ts +31 -52
  158. package/src/file/provider/azure.provider.ts +14 -28
  159. package/src/file/provider/gcs.provider.ts +9 -9
  160. package/src/file/provider/local.provider.ts +10 -13
  161. package/src/file/provider/provider.factory.ts +19 -7
  162. package/src/file/provider/s3.provider.ts +4 -20
  163. package/src/file/provider/spaces.provider.ts +4 -19
  164. package/src/integration-profile/integration-profile.controller.ts +2 -0
  165. package/src/integration-profile/integration-profile.service.ts +7 -2
  166. package/src/integration-profile/integration-profile.utils.ts +17 -0
  167. package/src/mail/mail.module.ts +14 -4
  168. package/src/mail/mail.service.ts +12 -7
  169. package/src/mcp/guards/mcp-auth.guard.ts +1 -1
  170. package/src/mcp/mcp-api-key.controller.ts +2 -2
  171. package/src/mcp/mcp-api-key.service.ts +6 -2
  172. package/src/mcp/mcp.module.ts +1 -3
  173. package/src/oauth/interfaces/OAuthProvider.ts +1 -1
  174. package/src/oauth/oauth.controller.ts +30 -2
  175. package/src/oauth/oauth.service.ts +194 -35
  176. package/src/oauth/providers/abstract.provider.ts +1 -1
  177. package/src/oauth/providers/facebook.provider.ts +36 -20
  178. package/src/oauth/providers/github.provider.ts +46 -23
  179. package/src/oauth/providers/google.provider.ts +36 -20
  180. package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
  181. package/src/oauth/providers/microsoft.provider.ts +36 -21
  182. package/src/profile/profile.controller.ts +2 -2
  183. package/src/security/security.service.ts +34 -1
  184. package/src/setting/setting.service.ts +307 -7
  185. package/src/user/user.controller.ts +4 -1
  186. package/src/user/user.service.ts +20 -2
  187. package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
  188. package/src/webhook-integration/webhook-command.registry.ts +42 -0
  189. package/src/webhook-integration/webhook-integration.controller.ts +20 -0
  190. package/src/webhook-integration/webhook-integration.module.ts +50 -45
  191. package/src/webhook-integration/webhook-integration.service.ts +374 -11
  192. package/dist/mail-profile/dto/create.dto.d.ts +0 -23
  193. package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
  194. package/dist/mail-profile/dto/create.dto.js +0 -139
  195. package/dist/mail-profile/dto/create.dto.js.map +0 -1
  196. package/dist/mail-profile/dto/update.dto.d.ts +0 -6
  197. package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
  198. package/dist/mail-profile/dto/update.dto.js +0 -9
  199. package/dist/mail-profile/dto/update.dto.js.map +0 -1
  200. package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
  201. package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
  202. package/dist/mail-profile/mail-profile.controller.js +0 -95
  203. package/dist/mail-profile/mail-profile.controller.js.map +0 -1
  204. package/dist/mail-profile/mail-profile.module.d.ts +0 -3
  205. package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
  206. package/dist/mail-profile/mail-profile.module.js +0 -31
  207. package/dist/mail-profile/mail-profile.module.js.map +0 -1
  208. package/dist/mail-profile/mail-profile.service.d.ts +0 -109
  209. package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
  210. package/dist/mail-profile/mail-profile.service.js +0 -278
  211. package/dist/mail-profile/mail-profile.service.js.map +0 -1
  212. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -20
  213. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
  214. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -96
  215. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
  216. package/dist/whatsapp-profile/dto/create.dto.d.ts +0 -11
  217. package/dist/whatsapp-profile/dto/create.dto.d.ts.map +0 -1
  218. package/dist/whatsapp-profile/dto/create.dto.js +0 -55
  219. package/dist/whatsapp-profile/dto/create.dto.js.map +0 -1
  220. package/dist/whatsapp-profile/dto/update.dto.d.ts +0 -6
  221. package/dist/whatsapp-profile/dto/update.dto.d.ts.map +0 -1
  222. package/dist/whatsapp-profile/dto/update.dto.js +0 -9
  223. package/dist/whatsapp-profile/dto/update.dto.js.map +0 -1
  224. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +0 -54
  225. package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +0 -1
  226. package/dist/whatsapp-profile/whatsapp-profile.controller.js +0 -85
  227. package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +0 -1
  228. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +0 -3
  229. package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +0 -1
  230. package/dist/whatsapp-profile/whatsapp-profile.module.js +0 -26
  231. package/dist/whatsapp-profile/whatsapp-profile.module.js.map +0 -1
  232. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +0 -63
  233. package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +0 -1
  234. package/dist/whatsapp-profile/whatsapp-profile.service.js +0 -138
  235. package/dist/whatsapp-profile/whatsapp-profile.service.js.map +0 -1
  236. package/hedhog/data/mail_profile.yaml +0 -18
  237. package/hedhog/data/whatsapp_profile.yaml +0 -6
  238. package/hedhog/table/mail_profile.yaml +0 -75
  239. package/hedhog/table/whatsapp_profile.yaml +0 -26
  240. package/src/mail-profile/dto/create.dto.ts +0 -145
  241. package/src/mail-profile/dto/update.dto.ts +0 -4
  242. package/src/mail-profile/mail-profile.controller.ts +0 -56
  243. package/src/mail-profile/mail-profile.module.ts +0 -18
  244. package/src/mail-profile/mail-profile.service.ts +0 -308
  245. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -69
  246. package/src/whatsapp-profile/dto/create.dto.ts +0 -36
  247. package/src/whatsapp-profile/dto/update.dto.ts +0 -4
  248. package/src/whatsapp-profile/whatsapp-profile.controller.ts +0 -51
  249. package/src/whatsapp-profile/whatsapp-profile.module.ts +0 -13
  250. package/src/whatsapp-profile/whatsapp-profile.service.ts +0 -160
@@ -29,6 +29,8 @@ import { UpdateDTO } from './dto/update.dto';
29
29
  export class MailService implements OnModuleInit {
30
30
  private readonly logger = new Logger(MailService.name);
31
31
  private readonly modelName = 'mail';
32
+ private readonly integrationProfileSettingSlug =
33
+ 'mail-integration-profile-id';
32
34
 
33
35
  constructor(
34
36
  @Inject(forwardRef(() => PrismaService))
@@ -59,11 +61,10 @@ export class MailService implements OnModuleInit {
59
61
  },
60
62
  });
61
63
 
62
- void this.syncConfiguredMailFlag();
63
64
  }
64
65
 
65
66
  private isMailConfigSlug(slug: string) {
66
- return slug === 'mail-profile-id';
67
+ return slug === this.integrationProfileSettingSlug;
67
68
  }
68
69
 
69
70
  private isSettingEnabled(value: unknown) {
@@ -72,8 +73,10 @@ export class MailService implements OnModuleInit {
72
73
 
73
74
  private async syncConfiguredMailFlag() {
74
75
  try {
75
- const settings = await this.setting.getSettingValues(['mail-profile-id']);
76
- const profileId = settings['mail-profile-id'];
76
+ const settings = await this.setting.getSettingValues([
77
+ this.integrationProfileSettingSlug,
78
+ ]);
79
+ const profileId = settings[this.integrationProfileSettingSlug];
77
80
 
78
81
  if (!profileId || Number(profileId) <= 0) {
79
82
  await this.setting.setValue('configured-mail', 'false');
@@ -101,7 +104,7 @@ export class MailService implements OnModuleInit {
101
104
  /**
102
105
  * Reload mail configuration from database and update MailMainService.
103
106
  * When profileId is provided, that profile is used directly.
104
- * Otherwise the global `mail-profile-id` setting is read.
107
+ * Otherwise the global integration profile setting is read.
105
108
  * Returns the active from address and the built config object.
106
109
  */
107
110
  async reloadConfig(profileId?: number): Promise<{ success: boolean; provider: string; from: string; config: any }> {
@@ -109,8 +112,10 @@ export class MailService implements OnModuleInit {
109
112
  let resolvedProfileId = profileId;
110
113
 
111
114
  if (!resolvedProfileId) {
112
- const settings = await this.setting.getSettingValues(['mail-profile-id']);
113
- resolvedProfileId = Number(settings['mail-profile-id']);
115
+ const settings = await this.setting.getSettingValues([
116
+ this.integrationProfileSettingSlug,
117
+ ]);
118
+ resolvedProfileId = Number(settings[this.integrationProfileSettingSlug]);
114
119
  }
115
120
 
116
121
  if (!resolvedProfileId || resolvedProfileId <= 0) {
@@ -38,7 +38,7 @@ export class McpAuthGuard implements CanActivate {
38
38
  private async verifyApiKey(request: Request, token: string): Promise<boolean> {
39
39
  const hash = this.security.hashWithPepper(token);
40
40
  const apiKey = await this.prisma.mcp_api_key.findFirst({
41
- where: { token_hash: hash, revoked_at: null },
41
+ where: { token_hash: hash, type: 'mcp', revoked_at: null },
42
42
  });
43
43
 
44
44
  if (!apiKey) {
@@ -27,9 +27,9 @@ export class McpApiKeyController {
27
27
  @Post()
28
28
  create(
29
29
  @User() { id }: { id: number },
30
- @Body() body: { name: string },
30
+ @Body() body: { name: string; type?: 'mcp' | 'api' },
31
31
  ) {
32
- return this.mcpApiKeyService.create(id, body.name);
32
+ return this.mcpApiKeyService.create(id, body.name, body.type);
33
33
  }
34
34
 
35
35
  @Delete(':id')
@@ -18,6 +18,7 @@ export class McpApiKeyService {
18
18
  select: {
19
19
  id: true,
20
20
  name: true,
21
+ type: true,
21
22
  token_prefix: true,
22
23
  last_used_at: true,
23
24
  expires_at: true,
@@ -27,8 +28,9 @@ export class McpApiKeyService {
27
28
  });
28
29
  }
29
30
 
30
- async create(userId: number, name: string) {
31
- const rawToken = 'hedhog_mcp_' + this.security.randomOpaque(36);
31
+ async create(userId: number, name: string, type: 'mcp' | 'api' = 'mcp') {
32
+ const prefix = type === 'api' ? 'hedhog_api_' : 'hedhog_mcp_';
33
+ const rawToken = prefix + this.security.randomOpaque(36);
32
34
  const tokenHash = this.security.hashWithPepper(rawToken);
33
35
  const tokenPrefix = rawToken.substring(0, 16);
34
36
 
@@ -36,12 +38,14 @@ export class McpApiKeyService {
36
38
  data: {
37
39
  user_id: userId,
38
40
  name,
41
+ type,
39
42
  token_hash: tokenHash,
40
43
  token_prefix: tokenPrefix,
41
44
  },
42
45
  select: {
43
46
  id: true,
44
47
  name: true,
48
+ type: true,
45
49
  token_prefix: true,
46
50
  created_at: true,
47
51
  },
@@ -37,7 +37,6 @@ import { CoreEventWebhooksMcpTools } from './tools/core-event-webhooks.mcp-tools
37
37
  import { CoreFilesMcpTools } from './tools/core-files.mcp-tools';
38
38
  import { CoreIntegrationEventsMcpTools } from './tools/core-integration-events.mcp-tools';
39
39
  import { CoreMailMcpTools } from './tools/core-mail.mcp-tools';
40
- import { CoreMailProfileMcpTools } from './tools/core-mail-profile.mcp-tools';
41
40
  import { CoreMailSentMcpTools } from './tools/core-mail-sent.mcp-tools';
42
41
  import { CoreMcpChatMcpTools } from './tools/core-mcp-chat.mcp-tools';
43
42
  import { CoreMenusMcpTools } from './tools/core-menus.mcp-tools';
@@ -98,7 +97,6 @@ import { AccessLogModule } from '../access-log/access-log.module';
98
97
  CoreSessionsMcpTools,
99
98
  CoreFilesMcpTools,
100
99
  CoreMailMcpTools,
101
- CoreMailProfileMcpTools,
102
100
  CoreMailSentMcpTools,
103
101
  CoreNotificationsMcpTools,
104
102
  CoreDashboardMcpTools,
@@ -110,6 +108,6 @@ import { AccessLogModule } from '../access-log/access-log.module';
110
108
  CoreMcpChatMcpTools,
111
109
  CoreSystemMcpTools,
112
110
  ],
113
- exports: [McpRegistryService, McpServerService],
111
+ exports: [McpRegistryService, McpServerService, McpApiKeyService],
114
112
  })
115
113
  export class McpModule {}
@@ -1,5 +1,5 @@
1
1
  export interface OAuthProvider {
2
2
  getAuthUrl(callbackPath: string): Promise<string>;
3
- getProfile(code: string, type?: string): Promise<any>;
3
+ getProfile(code: string, type?: string, redirectUri?: string): Promise<any>;
4
4
  getProviderType(): string;
5
5
  }
@@ -87,6 +87,21 @@ export class OAuthController {
87
87
  }
88
88
 
89
89
 
90
+ @Public()
91
+ @Get(':provider/mobile/auth-url')
92
+ async mobileAuthUrl(
93
+ @Param('provider') provider: string,
94
+ @Query('redirectUri') redirectUri: string,
95
+ ) {
96
+ const authUrl = await this.service.getMobileAuthUrl(
97
+ provider as user_account_provider_52222e2ecb_enum,
98
+ redirectUri,
99
+ );
100
+
101
+ return { authUrl };
102
+ }
103
+
104
+
90
105
  @Public()
91
106
  @Get(':provider/register')
92
107
  async register(@Param('provider') provider: string, @Res() res) {
@@ -101,7 +116,7 @@ export class OAuthController {
101
116
  @Public()
102
117
  @Get(':provider/connect')
103
118
  async connect(@Param('provider') provider: string, @Res() res) {
104
- const url = this.service.getAuthUrl(
119
+ const url = await this.service.getAuthUrl(
105
120
  provider as user_account_provider_52222e2ecb_enum,
106
121
  `/callback/${provider}/connect`,
107
122
  );
@@ -121,6 +136,8 @@ export class OAuthController {
121
136
  @Headers('x-forwarded-host') forwardedHost: string,
122
137
  @Param('provider') provider: string,
123
138
  @Query('code') code: string,
139
+ @Query('state') state: string,
140
+ @Query('redirectUri') redirectUri: string,
124
141
  @Res({ passthrough: true }) res,
125
142
  ) {
126
143
  this.logOAuthRequest('callback-login', provider, {
@@ -133,7 +150,18 @@ export class OAuthController {
133
150
  code,
134
151
  });
135
152
 
136
- return this.service.handleCallback({ res, locale, ipAddress, userAgent, provider: provider as user_account_provider_52222e2ecb_enum, code, type: 'login' });
153
+ return this.service.handleCallback({
154
+ res,
155
+ locale,
156
+ ipAddress,
157
+ userAgent,
158
+ provider: provider as user_account_provider_52222e2ecb_enum,
159
+ code,
160
+ state,
161
+ type: 'login',
162
+ redirectUri,
163
+ includeRefreshTokenInBody: Boolean(redirectUri),
164
+ });
137
165
  }
138
166
 
139
167
 
@@ -1,14 +1,14 @@
1
1
  import { getLocaleText } from '@hed-hog/api-locale';
2
- import { PrismaService, user_account_provider_52222e2ecb_enum } from '@hed-hog/api-prisma';
2
+ import { Prisma, PrismaService, user_account_provider_52222e2ecb_enum } from '@hed-hog/api-prisma';
3
3
  import {
4
- BadRequestException,
5
- ConflictException,
6
- Inject,
7
- Injectable,
8
- Logger,
9
- NotFoundException,
10
- ServiceUnavailableException,
11
- forwardRef
4
+ BadRequestException,
5
+ ConflictException,
6
+ Inject,
7
+ Injectable,
8
+ Logger,
9
+ NotFoundException,
10
+ ServiceUnavailableException,
11
+ forwardRef
12
12
  } from '@nestjs/common';
13
13
  import { AuthService } from '../auth/auth.service';
14
14
  import { FileService } from '../file/file.service';
@@ -21,9 +21,9 @@ import { UserService } from '../user/user.service';
21
21
  import { OAuthProvider } from './interfaces/OAuthProvider';
22
22
  import { OAuthCallbackCoordinatorService } from './oauth-callback-coordinator.service';
23
23
  import {
24
- OAuthCallbackAlreadyProcessedError,
25
- OAuthCallbackInProgressError,
26
- OAuthProviderException,
24
+ OAuthCallbackAlreadyProcessedError,
25
+ OAuthCallbackInProgressError,
26
+ OAuthProviderException,
27
27
  } from './oauth.errors';
28
28
  import { FacebookProvider } from './providers/facebook.provider';
29
29
  import { GithubProvider } from './providers/github.provider';
@@ -41,12 +41,16 @@ type HandleCallbackProps = {
41
41
  type: 'login' | 'register' | 'connect' | 'disconnect';
42
42
  userId?: number;
43
43
  email?: string;
44
+ redirectUri?: string;
45
+ state?: string;
46
+ includeRefreshTokenInBody?: boolean;
44
47
  };
45
48
 
46
49
  @Injectable()
47
50
  export class OAuthService {
48
51
  private readonly logger = new Logger(OAuthService.name);
49
52
  private providers: Map<string, OAuthProvider> = new Map();
53
+ private readonly mobileOAuthStateTtlMs = 10 * 60 * 1000;
50
54
 
51
55
  constructor(
52
56
  google: GoogleProvider,
@@ -73,10 +77,36 @@ export class OAuthService {
73
77
  this.providers.set(microsoftEntraId.getProviderType(), microsoftEntraId);
74
78
  }
75
79
 
76
- getAuthUrl(provider: user_account_provider_52222e2ecb_enum, callbackPath: string) {
80
+ async getAuthUrl(provider: user_account_provider_52222e2ecb_enum, callbackPath: string) {
81
+ const enabled = await this.isProviderEnabled(provider);
82
+ if (!enabled) {
83
+ throw new BadRequestException(`OAuth provider ${provider} is not enabled.`);
84
+ }
77
85
  return this.getProvider(provider).getAuthUrl(callbackPath);
78
86
  }
79
87
 
88
+ async getMobileAuthUrl(
89
+ provider: user_account_provider_52222e2ecb_enum,
90
+ redirectUri: string,
91
+ ) {
92
+ const sanitizedRedirectUri = String(redirectUri || '').trim();
93
+ if (!/^[a-z][a-z\d+.-]*:/i.test(sanitizedRedirectUri)) {
94
+ throw new BadRequestException('Invalid mobile redirect URI.');
95
+ }
96
+
97
+ const authUrl = await this.getAuthUrl(provider, sanitizedRedirectUri);
98
+ const flow = 'login';
99
+ const { token, signature } = await this.issueMobileState(
100
+ provider,
101
+ sanitizedRedirectUri,
102
+ flow,
103
+ );
104
+ const url = new URL(authUrl);
105
+ url.searchParams.set('state', `hhmob.${token}.${signature}.${flow}`);
106
+
107
+ return url.toString();
108
+ }
109
+
80
110
  async handleCallback({
81
111
  res,
82
112
  locale,
@@ -87,6 +117,9 @@ export class OAuthService {
87
117
  type,
88
118
  userId,
89
119
  email,
120
+ redirectUri,
121
+ state,
122
+ includeRefreshTokenInBody,
90
123
  }: HandleCallbackProps) {
91
124
  try {
92
125
  this.logger.log(
@@ -107,10 +140,23 @@ export class OAuthService {
107
140
  );
108
141
  }
109
142
 
143
+ if (
144
+ redirectUri &&
145
+ !(await this.consumeMobileState(state, provider, redirectUri))
146
+ ) {
147
+ throw new BadRequestException(
148
+ getLocaleText(
149
+ 'oauth.callback.invalidState',
150
+ locale,
151
+ 'OAuth callback state is invalid or expired. Start the sign-in flow again.',
152
+ ),
153
+ );
154
+ }
155
+
110
156
  const callbackKey = this.buildCallbackKey(provider, type, code);
111
157
 
112
158
  return await this.callbackCoordinator.execute(callbackKey, async () => {
113
- const profile = await this.getProvider(provider).getProfile(code, type);
159
+ const profile = await this.getProvider(provider).getProfile(code, type, redirectUri);
114
160
  const result = await this.finalizeCallback({
115
161
  res,
116
162
  locale,
@@ -120,6 +166,7 @@ export class OAuthService {
120
166
  type,
121
167
  profile,
122
168
  userId,
169
+ includeRefreshTokenInBody,
123
170
  });
124
171
 
125
172
  this.logger.log(
@@ -143,19 +190,37 @@ export class OAuthService {
143
190
  return prov;
144
191
  }
145
192
 
146
- private async getProviderScopes(provider: user_account_provider_52222e2ecb_enum) {
147
- const settingKey = `${String(provider).toLowerCase().replace(/-/g, '_')}_scopes`;
148
-
149
- const settings = await this.setting.getSettingValues([
150
- 'oauth-mfa',
151
- 'google_scopes',
152
- 'facebook_scopes',
153
- 'github_scopes',
154
- 'microsoft_scopes',
155
- 'microsoft_entra_id_scopes',
156
- ]);
193
+ private static readonly PROVIDER_SCOPE_SLUGS: Record<string, string> = {
194
+ google: 'oauth-google-scopes',
195
+ facebook: 'oauth-facebook-scopes',
196
+ github: 'oauth-github-scopes',
197
+ microsoft: 'oauth-microsoft-scopes',
198
+ 'microsoft-entra-id': 'oauth-microsoft-entra-id-scopes',
199
+ microsoft_entra_id: 'oauth-microsoft-entra-id-scopes',
200
+ };
201
+
202
+ private static readonly PROVIDER_ENABLED_SLUGS: Record<string, string> = {
203
+ google: 'oauth-google-enabled',
204
+ facebook: 'oauth-facebook-enabled',
205
+ github: 'oauth-github-enabled',
206
+ microsoft: 'oauth-microsoft-enabled',
207
+ 'microsoft-entra-id': 'oauth-microsoft-entra-id-enabled',
208
+ microsoft_entra_id: 'oauth-microsoft-entra-id-enabled',
209
+ };
210
+
211
+ async isProviderEnabled(provider: user_account_provider_52222e2ecb_enum): Promise<boolean> {
212
+ const slug = OAuthService.PROVIDER_ENABLED_SLUGS[String(provider).toLowerCase()];
213
+ if (!slug) return false;
214
+ const settings = await this.setting.getSettingValues([slug]);
215
+ return settings[slug] === true || settings[slug] === 'true';
216
+ }
157
217
 
158
- return settings[settingKey].join(',') || '';
218
+ private async getProviderScopes(provider: user_account_provider_52222e2ecb_enum) {
219
+ const settingKey = OAuthService.PROVIDER_SCOPE_SLUGS[String(provider).toLowerCase()];
220
+ if (!settingKey) return '';
221
+ const settings = await this.setting.getSettingValues([settingKey]);
222
+ const scopes = settings[settingKey];
223
+ return Array.isArray(scopes) ? scopes.join(',') : String(scopes ?? '');
159
224
  }
160
225
 
161
226
  private buildCallbackKey(
@@ -176,6 +241,86 @@ export class OAuthService {
176
241
  : userAgent;
177
242
  }
178
243
 
244
+ private async issueMobileState(
245
+ provider: user_account_provider_52222e2ecb_enum,
246
+ redirectUri: string,
247
+ flow: 'login' | 'register' | 'connect',
248
+ ) {
249
+ const token = this.security.randomOpaque(24);
250
+ const signature = this.security.signHmacSha256(`oauth-mobile-state:${token}`);
251
+ const tokenHash = this.security.hashSha256(token);
252
+ const expiresAt = new Date(Date.now() + this.mobileOAuthStateTtlMs);
253
+
254
+ await this.prisma.$executeRaw(
255
+ Prisma.sql`
256
+ INSERT INTO oauth_mobile_state_token (
257
+ token_hash,
258
+ provider,
259
+ redirect_uri,
260
+ flow_type,
261
+ expires_at,
262
+ created_at
263
+ ) VALUES (
264
+ ${tokenHash},
265
+ ${String(provider).toLowerCase()},
266
+ ${redirectUri},
267
+ ${flow},
268
+ ${expiresAt},
269
+ NOW()
270
+ )
271
+ `,
272
+ );
273
+
274
+ return { token, signature };
275
+ }
276
+
277
+ private async consumeMobileState(
278
+ encodedState: string | undefined,
279
+ provider: user_account_provider_52222e2ecb_enum,
280
+ redirectUri: string,
281
+ ) {
282
+ if (!encodedState || !encodedState.startsWith('hhmob.')) {
283
+ return false;
284
+ }
285
+
286
+ const parts = encodedState.split('.');
287
+ const stateToken = parts[1];
288
+ const signature = parts[2];
289
+ const flow = parts[3] as 'login' | 'register' | 'connect' | undefined;
290
+
291
+ if (!stateToken || !signature || !flow) {
292
+ return false;
293
+ }
294
+
295
+ if (!['login', 'register', 'connect'].includes(flow)) {
296
+ return false;
297
+ }
298
+
299
+ const hasValidSignature = this.security.verifyHmacSha256(
300
+ `oauth-mobile-state:${stateToken}`,
301
+ signature,
302
+ );
303
+ if (!hasValidSignature) {
304
+ return false;
305
+ }
306
+
307
+ const tokenHash = this.security.hashSha256(stateToken);
308
+ const consumedRows = await this.prisma.$executeRaw(
309
+ Prisma.sql`
310
+ UPDATE oauth_mobile_state_token
311
+ SET consumed_at = NOW()
312
+ WHERE token_hash = ${tokenHash}
313
+ AND provider = ${String(provider).toLowerCase()}
314
+ AND redirect_uri = ${redirectUri}
315
+ AND flow_type = ${flow}
316
+ AND expires_at > NOW()
317
+ AND consumed_at IS NULL
318
+ `,
319
+ );
320
+
321
+ return Number(consumedRows) > 0;
322
+ }
323
+
179
324
  private async finalizeCallback({
180
325
  res,
181
326
  locale,
@@ -185,14 +330,15 @@ export class OAuthService {
185
330
  type,
186
331
  profile,
187
332
  userId,
188
- }: Omit<HandleCallbackProps, 'code' | 'email'> & { profile: any }) {
333
+ includeRefreshTokenInBody,
334
+ }: Omit<HandleCallbackProps, 'code' | 'email' | 'redirectUri' | 'state'> & { profile: any }) {
189
335
  switch (type) {
190
336
  case 'login':
191
- return this.handleLogin(res, locale, ipAddress, userAgent, provider, profile);
337
+ return this.handleLogin(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
192
338
  case 'register':
193
- return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile);
339
+ return this.handleRegister(res, locale, ipAddress, userAgent, provider, profile, includeRefreshTokenInBody);
194
340
  case 'connect':
195
- return this.handleConnect(res, locale, ipAddress, userAgent, provider, profile, userId);
341
+ return this.handleConnect(res, locale, ipAddress, userAgent, provider, profile, userId, includeRefreshTokenInBody);
196
342
  default:
197
343
  throw new BadRequestException(
198
344
  getLocaleText('oauth.callback.invalidType', locale, 'Unsupported OAuth callback type.'),
@@ -200,6 +346,18 @@ export class OAuthService {
200
346
  }
201
347
  }
202
348
 
349
+ private formatAuthResponse(
350
+ accessToken: string,
351
+ refreshToken: string,
352
+ includeRefreshTokenInBody?: boolean,
353
+ ) {
354
+ if (includeRefreshTokenInBody) {
355
+ return { accessToken, refreshToken };
356
+ }
357
+
358
+ return { accessToken };
359
+ }
360
+
203
361
  private resolveProfileScopes(provider: user_account_provider_52222e2ecb_enum, profile: any) {
204
362
  if (typeof profile?.oauth_scopes === 'string') {
205
363
  return profile.oauth_scopes;
@@ -274,7 +432,7 @@ export class OAuthService {
274
432
  );
275
433
  }
276
434
 
277
- private async handleLogin(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any) {
435
+ private async handleLogin(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean) {
278
436
  const userAccount = await this.prisma.user_account.findFirst({
279
437
  where: {
280
438
  provider: String(provider).toLowerCase().replace(/-/g, '_') as user_account_provider_52222e2ecb_enum,
@@ -324,11 +482,11 @@ export class OAuthService {
324
482
  );
325
483
 
326
484
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
327
- return { accessToken };
485
+ return this.formatAuthResponse(accessToken, refreshToken, includeRefreshTokenInBody);
328
486
  }
329
487
  }
330
488
 
331
- private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any) {
489
+ private async handleRegister(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, profile: any, includeRefreshTokenInBody?: boolean) {
332
490
  const existingUser = await this.prisma.user_identifier.findFirst({
333
491
  where: {
334
492
  type: 'email',
@@ -415,7 +573,7 @@ export class OAuthService {
415
573
  userAgent
416
574
  );
417
575
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
418
- return { accessToken };
576
+ return this.formatAuthResponse(accessToken, refreshToken, includeRefreshTokenInBody);
419
577
  }
420
578
 
421
579
  private async handleConnect(
@@ -426,6 +584,7 @@ export class OAuthService {
426
584
  provider: user_account_provider_52222e2ecb_enum,
427
585
  profile: any,
428
586
  userId: number,
587
+ includeRefreshTokenInBody?: boolean,
429
588
  ) {
430
589
  const existingAccount = await this.prisma.user_account.findFirst({
431
590
  where: {
@@ -482,7 +641,7 @@ export class OAuthService {
482
641
  await this.user.registerUserActivity(userId, 'oauth-connect');
483
642
  const { accessToken, refreshToken, session } = await this.auth.getAuthenticationPayload(locale, user.id, ipAddress, userAgent);
484
643
  await this.token.setRefreshTokenCookie(locale, res, refreshToken, session.expires_at);
485
- return { accessToken };
644
+ return this.formatAuthResponse(accessToken, refreshToken, includeRefreshTokenInBody);
486
645
  }
487
646
 
488
647
  private async handleDisconnect(res: Response, locale: string, ipAddress: string, userAgent: string, provider: user_account_provider_52222e2ecb_enum, email: string) {
@@ -7,7 +7,7 @@ export abstract class BaseOAuthProvider implements OAuthProvider {
7
7
  constructor(protected readonly http: HttpService) {}
8
8
 
9
9
  abstract getAuthUrl(callbackPath: string): Promise<string>;
10
- abstract getProfile(code: string, type?: string): Promise<any>;
10
+ abstract getProfile(code: string, type?: string, redirectUri?: string): Promise<any>;
11
11
  abstract getProviderType(): string;
12
12
 
13
13
  protected async fetchToken({
@@ -1,16 +1,18 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
1
2
  import { HttpService } from '@nestjs/axios';
2
- import { forwardRef, Inject, Injectable } from '@nestjs/common';
3
+ import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common';
4
+ import { buildOAuthConfigFromIntegration } from '../../integration-profile/integration-profile.utils';
3
5
  import { SettingService } from '../../setting/setting.service';
4
6
  import { BaseOAuthProvider } from './abstract.provider';
5
7
 
6
8
  @Injectable()
7
- export class FacebookProvider
8
- extends BaseOAuthProvider {
9
+ export class FacebookProvider extends BaseOAuthProvider {
9
10
 
10
11
  constructor(
11
12
  http: HttpService,
12
13
  @Inject(forwardRef(() => SettingService))
13
14
  private readonly setting: SettingService,
15
+ private readonly prisma: PrismaService,
14
16
  ) {
15
17
  super(http);
16
18
  }
@@ -19,42 +21,56 @@ export class FacebookProvider
19
21
  return 'FACEBOOK';
20
22
  }
21
23
 
22
- async getAuthUrl(callbackPath: string) {
24
+ private async loadConfig() {
23
25
  const settings = await this.setting.getSettingValues([
24
- 'facebook_client_id',
25
- 'facebook_client_secret',
26
- 'facebook_scopes',
26
+ 'oauth-facebook-profile-id',
27
27
  'url',
28
28
  ]);
29
+ const profileId = Number(settings['oauth-facebook-profile-id']);
30
+ if (!profileId) throw new BadRequestException('Facebook OAuth profile not configured.');
31
+
32
+ const profile = await this.prisma.integration_profile.findUnique({
33
+ where: { id: profileId },
34
+ include: { integration_provider: { select: { slug: true } } },
35
+ });
36
+ if (!profile) throw new BadRequestException('Facebook OAuth profile not found.');
37
+
38
+ const { clientId, clientSecret } = buildOAuthConfigFromIntegration(
39
+ profile.integration_provider.slug,
40
+ profile.config,
41
+ );
42
+ return { clientId, clientSecret, baseUrl: String(settings['url'] ?? '') };
43
+ }
29
44
 
30
- const redirectURI = new URL(callbackPath, settings['url']).toString();
45
+ async getAuthUrl(callbackPath: string) {
46
+ const { clientId, baseUrl } = await this.loadConfig();
47
+ const settings = await this.setting.getSettingValues(['oauth-facebook-scopes']);
48
+ const scopes: string[] = settings['oauth-facebook-scopes'] ?? ['email'];
49
+ const redirectURI = new URL(callbackPath, baseUrl).toString();
31
50
 
32
51
  const params = new URLSearchParams({
33
- client_id: settings['facebook_client_id'],
52
+ client_id: clientId,
34
53
  redirect_uri: redirectURI,
35
54
  response_type: 'code',
36
- scope: (settings['facebook_scopes'] ?? ['email']).join(','),
55
+ scope: scopes.join(','),
37
56
  auth_type: 'rerequest',
38
57
  });
39
58
 
40
59
  return `https://www.facebook.com/v17.0/dialog/oauth?${params}`;
41
60
  }
42
61
 
43
- async getProfile(code: string, type: string): Promise<any> {
62
+ async getProfile(code: string, type: string, redirectUri?: string): Promise<any> {
63
+ const { clientId, clientSecret, baseUrl } = await this.loadConfig();
44
64
 
45
- const settings = await this.setting.getSettingValues([
46
- 'facebook_client_id',
47
- 'facebook_client_secret',
48
- 'facebook_scopes',
49
- 'url',
50
- ]);
65
+ const resolvedRedirectUri =
66
+ redirectUri?.trim() || `${baseUrl}/oauth/facebook/callback/${type}`;
51
67
 
52
68
  const token = await this.fetchToken({
53
69
  code,
54
70
  url: 'https://graph.facebook.com/v17.0/oauth/access_token',
55
- clientId: settings['facebook_client_id'],
56
- clientSecret: settings['facebook_client_secret'],
57
- redirectUri: `${settings['url']}/oauth/facebook/callback/${type}`,
71
+ clientId,
72
+ clientSecret,
73
+ redirectUri: resolvedRedirectUri,
58
74
  });
59
75
  const profile = await this.fetchProfile(
60
76
  token.access_token,