@hed-hog/core 0.0.355 → 0.0.358

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 (287) hide show
  1. package/dist/ai/ai.service.d.ts +3 -12
  2. package/dist/ai/ai.service.d.ts.map +1 -1
  3. package/dist/ai/ai.service.js +34 -125
  4. package/dist/ai/ai.service.js.map +1 -1
  5. package/dist/auth/guards/auth.guard.d.ts +6 -1
  6. package/dist/auth/guards/auth.guard.d.ts.map +1 -1
  7. package/dist/auth/guards/auth.guard.js +33 -2
  8. package/dist/auth/guards/auth.guard.js.map +1 -1
  9. package/dist/core.module.d.ts.map +1 -1
  10. package/dist/core.module.js +3 -3
  11. package/dist/core.module.js.map +1 -1
  12. package/dist/file/file.controller.d.ts +4 -2
  13. package/dist/file/file.controller.d.ts.map +1 -1
  14. package/dist/file/file.controller.js +44 -38
  15. package/dist/file/file.controller.js.map +1 -1
  16. package/dist/file/file.service.d.ts +2 -2
  17. package/dist/file/file.service.d.ts.map +1 -1
  18. package/dist/file/file.service.js +58 -89
  19. package/dist/file/file.service.js.map +1 -1
  20. package/dist/file/provider/azure.provider.d.ts.map +1 -1
  21. package/dist/file/provider/azure.provider.js +10 -15
  22. package/dist/file/provider/azure.provider.js.map +1 -1
  23. package/dist/file/provider/gcs.provider.d.ts.map +1 -1
  24. package/dist/file/provider/gcs.provider.js +8 -9
  25. package/dist/file/provider/gcs.provider.js.map +1 -1
  26. package/dist/file/provider/local.provider.d.ts.map +1 -1
  27. package/dist/file/provider/local.provider.js +11 -10
  28. package/dist/file/provider/local.provider.js.map +1 -1
  29. package/dist/file/provider/provider.enum.d.ts +2 -1
  30. package/dist/file/provider/provider.enum.d.ts.map +1 -1
  31. package/dist/file/provider/provider.enum.js +1 -0
  32. package/dist/file/provider/provider.enum.js.map +1 -1
  33. package/dist/file/provider/provider.factory.d.ts +1 -1
  34. package/dist/file/provider/provider.factory.d.ts.map +1 -1
  35. package/dist/file/provider/provider.factory.js +18 -5
  36. package/dist/file/provider/provider.factory.js.map +1 -1
  37. package/dist/file/provider/s3.provider.d.ts.map +1 -1
  38. package/dist/file/provider/s3.provider.js +4 -20
  39. package/dist/file/provider/s3.provider.js.map +1 -1
  40. package/dist/file/provider/spaces.provider.d.ts +27 -0
  41. package/dist/file/provider/spaces.provider.d.ts.map +1 -0
  42. package/dist/file/provider/spaces.provider.js +203 -0
  43. package/dist/file/provider/spaces.provider.js.map +1 -0
  44. package/dist/index.d.ts +4 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +4 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/integration-profile/dto/create.dto.d.ts +9 -0
  49. package/dist/integration-profile/dto/create.dto.d.ts.map +1 -0
  50. package/dist/integration-profile/dto/create.dto.js +54 -0
  51. package/dist/integration-profile/dto/create.dto.js.map +1 -0
  52. package/dist/integration-profile/dto/export.dto.d.ts +5 -0
  53. package/dist/integration-profile/dto/export.dto.d.ts.map +1 -0
  54. package/dist/integration-profile/dto/export.dto.js +29 -0
  55. package/dist/integration-profile/dto/export.dto.js.map +1 -0
  56. package/dist/integration-profile/dto/import-confirm.dto.d.ts +17 -0
  57. package/dist/integration-profile/dto/import-confirm.dto.d.ts.map +1 -0
  58. package/dist/integration-profile/dto/import-confirm.dto.js +77 -0
  59. package/dist/integration-profile/dto/import-confirm.dto.js.map +1 -0
  60. package/dist/integration-profile/dto/update.dto.d.ts.map +1 -0
  61. package/dist/integration-profile/dto/update.dto.js.map +1 -0
  62. package/dist/integration-profile/integration-profile.controller.d.ts +142 -0
  63. package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -0
  64. package/dist/integration-profile/integration-profile.controller.js +166 -0
  65. package/dist/integration-profile/integration-profile.controller.js.map +1 -0
  66. package/dist/integration-profile/integration-profile.module.d.ts +3 -0
  67. package/dist/integration-profile/integration-profile.module.d.ts.map +1 -0
  68. package/dist/{mail-profile/mail-profile.module.js → integration-profile/integration-profile.module.js} +11 -13
  69. package/dist/integration-profile/integration-profile.module.js.map +1 -0
  70. package/dist/integration-profile/integration-profile.service.d.ts +160 -0
  71. package/dist/integration-profile/integration-profile.service.d.ts.map +1 -0
  72. package/dist/integration-profile/integration-profile.service.js +609 -0
  73. package/dist/integration-profile/integration-profile.service.js.map +1 -0
  74. package/dist/integration-profile/integration-profile.utils.d.ts +30 -0
  75. package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -0
  76. package/dist/integration-profile/integration-profile.utils.js +156 -0
  77. package/dist/integration-profile/integration-profile.utils.js.map +1 -0
  78. package/dist/mail/mail.module.d.ts.map +1 -1
  79. package/dist/mail/mail.module.js +36 -23
  80. package/dist/mail/mail.module.js.map +1 -1
  81. package/dist/mail/mail.service.d.ts +4 -2
  82. package/dist/mail/mail.service.d.ts.map +1 -1
  83. package/dist/mail/mail.service.js +33 -51
  84. package/dist/mail/mail.service.js.map +1 -1
  85. package/dist/mcp/guards/mcp-auth.guard.js +1 -1
  86. package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
  87. package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
  88. package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
  89. package/dist/mcp/mcp-api-key.controller.js +1 -1
  90. package/dist/mcp/mcp-api-key.controller.js.map +1 -1
  91. package/dist/mcp/mcp-api-key.service.d.ts +2 -1
  92. package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
  93. package/dist/mcp/mcp-api-key.service.js +6 -2
  94. package/dist/mcp/mcp-api-key.service.js.map +1 -1
  95. package/dist/mcp/mcp.module.d.ts.map +1 -1
  96. package/dist/mcp/mcp.module.js +3 -5
  97. package/dist/mcp/mcp.module.js.map +1 -1
  98. package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
  99. package/dist/mcp-chat/mcp-chat.service.js +17 -16
  100. package/dist/mcp-chat/mcp-chat.service.js.map +1 -1
  101. package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
  102. package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
  103. package/dist/oauth/oauth.controller.d.ts +4 -1
  104. package/dist/oauth/oauth.controller.d.ts.map +1 -1
  105. package/dist/oauth/oauth.controller.js +31 -5
  106. package/dist/oauth/oauth.controller.js.map +1 -1
  107. package/dist/oauth/oauth.service.d.ts +12 -1
  108. package/dist/oauth/oauth.service.d.ts.map +1 -1
  109. package/dist/oauth/oauth.service.js +125 -23
  110. package/dist/oauth/oauth.service.js.map +1 -1
  111. package/dist/oauth/providers/abstract.provider.d.ts +1 -1
  112. package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
  113. package/dist/oauth/providers/facebook.provider.d.ts +5 -2
  114. package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
  115. package/dist/oauth/providers/facebook.provider.js +34 -19
  116. package/dist/oauth/providers/facebook.provider.js.map +1 -1
  117. package/dist/oauth/providers/github.provider.d.ts +6 -2
  118. package/dist/oauth/providers/github.provider.d.ts.map +1 -1
  119. package/dist/oauth/providers/github.provider.js +43 -20
  120. package/dist/oauth/providers/github.provider.js.map +1 -1
  121. package/dist/oauth/providers/google.provider.d.ts +5 -2
  122. package/dist/oauth/providers/google.provider.d.ts.map +1 -1
  123. package/dist/oauth/providers/google.provider.js +34 -19
  124. package/dist/oauth/providers/google.provider.js.map +1 -1
  125. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
  126. package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
  127. package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
  128. package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
  129. package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
  130. package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
  131. package/dist/oauth/providers/microsoft.provider.js +34 -19
  132. package/dist/oauth/providers/microsoft.provider.js.map +1 -1
  133. package/dist/profile/profile.controller.js +1 -1
  134. package/dist/profile/profile.controller.js.map +1 -1
  135. package/dist/security/security.service.d.ts +2 -0
  136. package/dist/security/security.service.d.ts.map +1 -1
  137. package/dist/security/security.service.js +21 -0
  138. package/dist/security/security.service.js.map +1 -1
  139. package/dist/setting/setting.service.d.ts +26 -1
  140. package/dist/setting/setting.service.d.ts.map +1 -1
  141. package/dist/setting/setting.service.js +240 -7
  142. package/dist/setting/setting.service.js.map +1 -1
  143. package/dist/user/user.controller.d.ts +2 -1
  144. package/dist/user/user.controller.d.ts.map +1 -1
  145. package/dist/user/user.controller.js +5 -4
  146. package/dist/user/user.controller.js.map +1 -1
  147. package/dist/user/user.service.d.ts +4 -2
  148. package/dist/user/user.service.d.ts.map +1 -1
  149. package/dist/user/user.service.js +19 -4
  150. package/dist/user/user.service.js.map +1 -1
  151. package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
  152. package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
  153. package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
  154. package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
  155. package/dist/webhook-integration/event-webhook.controller.d.ts +1 -0
  156. package/dist/webhook-integration/event-webhook.controller.d.ts.map +1 -1
  157. package/dist/webhook-integration/event-webhook.controller.js +10 -0
  158. package/dist/webhook-integration/event-webhook.controller.js.map +1 -1
  159. package/dist/webhook-integration/event-webhook.service.d.ts +2 -0
  160. package/dist/webhook-integration/event-webhook.service.d.ts.map +1 -1
  161. package/dist/webhook-integration/event-webhook.service.js +30 -0
  162. package/dist/webhook-integration/event-webhook.service.js.map +1 -1
  163. package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
  164. package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
  165. package/dist/webhook-integration/webhook-command.registry.js +36 -0
  166. package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
  167. package/dist/webhook-integration/webhook-integration.controller.d.ts +20 -0
  168. package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
  169. package/dist/webhook-integration/webhook-integration.controller.js +48 -0
  170. package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
  171. package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
  172. package/dist/webhook-integration/webhook-integration.module.js +5 -0
  173. package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
  174. package/dist/webhook-integration/webhook-integration.service.d.ts +27 -2
  175. package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
  176. package/dist/webhook-integration/webhook-integration.service.js +377 -50
  177. package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
  178. package/hedhog/data/file_provider.yaml +9 -1
  179. package/hedhog/data/integration_provider.yaml +148 -0
  180. package/hedhog/data/integration_type.yaml +29 -0
  181. package/hedhog/data/menu.yaml +6 -21
  182. package/hedhog/data/role.yaml +8 -0
  183. package/hedhog/data/route.yaml +73 -373
  184. package/hedhog/data/setting_group.yaml +209 -365
  185. package/hedhog/data/setting_subgroup.yaml +21 -13
  186. package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +25 -20
  187. package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
  188. package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
  189. package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
  190. package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
  191. package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +938 -225
  192. package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
  193. package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
  194. package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +1830 -0
  195. package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +829 -128
  196. package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +1 -820
  197. package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
  198. package/hedhog/frontend/app/users/permissions.tsx.ejs +79 -2
  199. package/hedhog/frontend/app/users/user-sheet.tsx.ejs +7 -2
  200. package/hedhog/frontend/messages/en.json +239 -3
  201. package/hedhog/frontend/messages/pt.json +239 -3
  202. package/hedhog/table/file.yaml +1 -1
  203. package/hedhog/table/integration_profile.yaml +34 -0
  204. package/hedhog/table/integration_provider.yaml +23 -0
  205. package/hedhog/table/integration_type.yaml +19 -0
  206. package/hedhog/table/mcp_api_key.yaml +4 -0
  207. package/hedhog/table/webhook_action.yaml +141 -85
  208. package/package.json +4 -3
  209. package/src/ai/ai.service.ts +35 -153
  210. package/src/auth/guards/auth.guard.ts +32 -0
  211. package/src/core.module.ts +3 -3
  212. package/src/file/file.controller.ts +51 -42
  213. package/src/file/file.service.ts +67 -104
  214. package/src/file/provider/azure.provider.ts +14 -28
  215. package/src/file/provider/gcs.provider.ts +9 -9
  216. package/src/file/provider/local.provider.ts +10 -13
  217. package/src/file/provider/provider.enum.ts +1 -0
  218. package/src/file/provider/provider.factory.ts +21 -6
  219. package/src/file/provider/s3.provider.ts +4 -20
  220. package/src/file/provider/spaces.provider.ts +246 -0
  221. package/src/index.ts +6 -3
  222. package/src/integration-profile/dto/create.dto.ts +44 -0
  223. package/src/integration-profile/dto/export.dto.ts +15 -0
  224. package/src/integration-profile/dto/import-confirm.dto.ts +61 -0
  225. package/src/integration-profile/integration-profile.controller.ts +131 -0
  226. package/src/integration-profile/integration-profile.module.ts +16 -0
  227. package/src/integration-profile/integration-profile.service.ts +793 -0
  228. package/src/integration-profile/integration-profile.utils.ts +173 -0
  229. package/src/mail/mail.module.ts +41 -22
  230. package/src/mail/mail.service.ts +40 -64
  231. package/src/mcp/guards/mcp-auth.guard.ts +1 -1
  232. package/src/mcp/mcp-api-key.controller.ts +2 -2
  233. package/src/mcp/mcp-api-key.service.ts +6 -2
  234. package/src/mcp/mcp.module.ts +3 -5
  235. package/src/mcp-chat/mcp-chat.service.ts +23 -20
  236. package/src/oauth/interfaces/OAuthProvider.ts +1 -1
  237. package/src/oauth/oauth.controller.ts +30 -2
  238. package/src/oauth/oauth.service.ts +194 -35
  239. package/src/oauth/providers/abstract.provider.ts +1 -1
  240. package/src/oauth/providers/facebook.provider.ts +36 -20
  241. package/src/oauth/providers/github.provider.ts +46 -23
  242. package/src/oauth/providers/google.provider.ts +36 -20
  243. package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
  244. package/src/oauth/providers/microsoft.provider.ts +36 -21
  245. package/src/profile/profile.controller.ts +2 -2
  246. package/src/security/security.service.ts +34 -1
  247. package/src/setting/setting.service.ts +307 -9
  248. package/src/user/user.controller.ts +4 -1
  249. package/src/user/user.service.ts +20 -2
  250. package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
  251. package/src/webhook-integration/event-webhook.controller.ts +55 -50
  252. package/src/webhook-integration/event-webhook.service.ts +422 -384
  253. package/src/webhook-integration/webhook-command.registry.ts +42 -0
  254. package/src/webhook-integration/webhook-integration.controller.ts +25 -0
  255. package/src/webhook-integration/webhook-integration.module.ts +50 -45
  256. package/src/webhook-integration/webhook-integration.service.ts +444 -53
  257. package/dist/mail-profile/dto/create.dto.d.ts +0 -23
  258. package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
  259. package/dist/mail-profile/dto/create.dto.js +0 -139
  260. package/dist/mail-profile/dto/create.dto.js.map +0 -1
  261. package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
  262. package/dist/mail-profile/dto/update.dto.js.map +0 -1
  263. package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
  264. package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
  265. package/dist/mail-profile/mail-profile.controller.js +0 -95
  266. package/dist/mail-profile/mail-profile.controller.js.map +0 -1
  267. package/dist/mail-profile/mail-profile.module.d.ts +0 -3
  268. package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
  269. package/dist/mail-profile/mail-profile.module.js.map +0 -1
  270. package/dist/mail-profile/mail-profile.service.d.ts +0 -109
  271. package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
  272. package/dist/mail-profile/mail-profile.service.js +0 -278
  273. package/dist/mail-profile/mail-profile.service.js.map +0 -1
  274. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -32
  275. package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
  276. package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -178
  277. package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
  278. package/hedhog/data/mail_profile.yaml +0 -18
  279. package/hedhog/table/mail_profile.yaml +0 -75
  280. package/src/mail-profile/dto/create.dto.ts +0 -145
  281. package/src/mail-profile/mail-profile.controller.ts +0 -56
  282. package/src/mail-profile/mail-profile.module.ts +0 -18
  283. package/src/mail-profile/mail-profile.service.ts +0 -308
  284. package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -128
  285. /package/dist/{mail-profile → integration-profile}/dto/update.dto.d.ts +0 -0
  286. /package/dist/{mail-profile → integration-profile}/dto/update.dto.js +0 -0
  287. /package/src/{mail-profile → integration-profile}/dto/update.dto.ts +0 -0
@@ -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,
@@ -1,16 +1,22 @@
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 GithubProvider
8
- extends BaseOAuthProvider {
9
+ export class GithubProvider extends BaseOAuthProvider {
10
+
11
+ private isAbsoluteCallbackPath(value: string) {
12
+ return /^[a-z][a-z\d+.-]*:/i.test(String(value || '').trim());
13
+ }
9
14
 
10
15
  constructor(
11
16
  http: HttpService,
12
17
  @Inject(forwardRef(() => SettingService))
13
18
  private readonly setting: SettingService,
19
+ private readonly prisma: PrismaService,
14
20
  ) {
15
21
  super(http);
16
22
  }
@@ -19,21 +25,41 @@ export class GithubProvider
19
25
  return 'GITHUB';
20
26
  }
21
27
 
22
- async getAuthUrl(callbackPath: string) {
23
-
28
+ private async loadConfig() {
24
29
  const settings = await this.setting.getSettingValues([
25
- 'github_client_id',
26
- 'github_client_secret',
27
- 'github_scopes',
30
+ 'oauth-github-profile-id',
28
31
  'api-url',
29
32
  ]);
33
+ const profileId = Number(settings['oauth-github-profile-id']);
34
+ if (!profileId) throw new BadRequestException('GitHub OAuth profile not configured.');
35
+
36
+ const profile = await this.prisma.integration_profile.findUnique({
37
+ where: { id: profileId },
38
+ include: { integration_provider: { select: { slug: true } } },
39
+ });
40
+ if (!profile) throw new BadRequestException('GitHub OAuth profile not found.');
41
+
42
+ const { clientId, clientSecret } = buildOAuthConfigFromIntegration(
43
+ profile.integration_provider.slug,
44
+ profile.config,
45
+ );
46
+ return { clientId, clientSecret, apiUrl: String(settings['api-url'] ?? '') };
47
+ }
48
+
49
+ async getAuthUrl(callbackPath: string) {
50
+ const { clientId, apiUrl } = await this.loadConfig();
51
+ const settings = await this.setting.getSettingValues(['oauth-github-scopes']);
52
+ const scopes: string[] = settings['oauth-github-scopes'] ?? ['read:user', 'user:email'];
30
53
 
31
54
  const fixedCallbackPath = '/oauth/github/callback';
32
- const redirectURI = new URL(fixedCallbackPath, settings['api-url']).toString();
33
- const scopes = settings['github_scopes'];
34
- const type = callbackPath.split('/').pop() || 'login';
55
+ const redirectURI = this.isAbsoluteCallbackPath(callbackPath)
56
+ ? callbackPath
57
+ : new URL(fixedCallbackPath, apiUrl).toString();
58
+ const type = this.isAbsoluteCallbackPath(callbackPath)
59
+ ? 'login'
60
+ : callbackPath.split('/').pop() || 'login';
35
61
  const params = new URLSearchParams({
36
- client_id: settings['github_client_id'],
62
+ client_id: clientId,
37
63
  redirect_uri: redirectURI,
38
64
  scope: scopes.join(' '),
39
65
  allow_signup: 'true',
@@ -43,21 +69,18 @@ export class GithubProvider
43
69
  return `https://github.com/login/oauth/authorize?${params.toString()}`;
44
70
  }
45
71
 
46
- async getProfile(code: string) {
47
- const settings = await this.setting.getSettingValues([
48
- 'github_client_id',
49
- 'github_client_secret',
50
- 'github_scopes',
51
- 'api-url',
52
- ]);
72
+ async getProfile(code: string, _type?: string, redirectUri?: string) {
73
+ const { clientId, clientSecret, apiUrl } = await this.loadConfig();
53
74
 
54
75
  const fixedCallbackPath = '/oauth/github/callback';
55
- const redirectURI = new URL(fixedCallbackPath, settings['api-url'].replace(":3200", ":3100")).toString();
76
+ const redirectURI = redirectUri?.trim()
77
+ ? redirectUri.trim()
78
+ : new URL(fixedCallbackPath, apiUrl.replace(':3200', ':3100')).toString();
56
79
  const tokenResponse = await this.http.axiosRef.post(
57
80
  'https://github.com/login/oauth/access_token',
58
81
  {
59
- client_id: settings['github_client_id'],
60
- client_secret: settings['github_client_secret'],
82
+ client_id: clientId,
83
+ client_secret: clientSecret,
61
84
  code,
62
85
  redirect_uri: redirectURI,
63
86
  },
@@ -114,4 +137,4 @@ export class GithubProvider
114
137
  },
115
138
  };
116
139
  }
117
- }
140
+ }
@@ -1,5 +1,7 @@
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
 
@@ -10,6 +12,7 @@ export class GoogleProvider extends BaseOAuthProvider {
10
12
  http: HttpService,
11
13
  @Inject(forwardRef(() => SettingService))
12
14
  private readonly setting: SettingService,
15
+ private readonly prisma: PrismaService,
13
16
  ) {
14
17
  super(http);
15
18
  }
@@ -18,22 +21,38 @@ export class GoogleProvider extends BaseOAuthProvider {
18
21
  return 'GOOGLE';
19
22
  }
20
23
 
21
- async getAuthUrl(callbackPath: string) {
22
-
24
+ private async loadConfig() {
23
25
  const settings = await this.setting.getSettingValues([
24
- 'google_client_id',
25
- 'google_client_secret',
26
- 'google_scopes',
26
+ 'oauth-google-profile-id',
27
27
  'url',
28
28
  ]);
29
+ const profileId = Number(settings['oauth-google-profile-id']);
30
+ if (!profileId) throw new BadRequestException('Google 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('Google 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-google-scopes']);
48
+ const scopes: string[] = settings['oauth-google-scopes'] ?? ['email', 'profile'];
49
+ const redirectURI = new URL(callbackPath, baseUrl).toString();
31
50
 
32
51
  const params = new URLSearchParams({
33
- client_id: settings['google_client_id'],
52
+ client_id: clientId,
34
53
  redirect_uri: redirectURI,
35
54
  response_type: 'code',
36
- scope: (settings['google_scopes'] ?? []).join(' '),
55
+ scope: scopes.join(' '),
37
56
  access_type: 'offline',
38
57
  prompt: 'consent',
39
58
  });
@@ -41,21 +60,18 @@ export class GoogleProvider extends BaseOAuthProvider {
41
60
  return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
42
61
  }
43
62
 
44
- async getProfile(code: string, type: string) {
63
+ async getProfile(code: string, type: string, redirectUri?: string) {
64
+ const { clientId, clientSecret, baseUrl } = await this.loadConfig();
45
65
 
46
- const settings = await this.setting.getSettingValues([
47
- 'google_client_id',
48
- 'google_client_secret',
49
- 'google_scopes',
50
- 'url',
51
- ]);
66
+ const resolvedRedirectUri =
67
+ redirectUri?.trim() || `${baseUrl}/callback/google/${type}`;
52
68
 
53
69
  const token = await this.fetchToken({
54
70
  code,
55
71
  url: 'https://oauth2.googleapis.com/token',
56
- clientId: settings['google_client_id'],
57
- clientSecret: settings['google_client_secret'],
58
- redirectUri: `${settings['url']}/callback/google/${type}`,
72
+ clientId,
73
+ clientSecret,
74
+ redirectUri: resolvedRedirectUri,
59
75
  });
60
76
 
61
77
  const basicProfile = await this.fetchProfile(
@@ -66,7 +82,7 @@ export class GoogleProvider extends BaseOAuthProvider {
66
82
  const peopleProfile = await this.getGoogleProfileFromAccessToken(
67
83
  token.access_token,
68
84
  );
69
-
85
+
70
86
  return {
71
87
  ...basicProfile,
72
88
  gender: peopleProfile.gender,