@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.
- package/dist/auth/guards/auth.guard.d.ts +6 -1
- package/dist/auth/guards/auth.guard.d.ts.map +1 -1
- package/dist/auth/guards/auth.guard.js +33 -2
- package/dist/auth/guards/auth.guard.js.map +1 -1
- package/dist/file/file.controller.d.ts +4 -2
- package/dist/file/file.controller.d.ts.map +1 -1
- package/dist/file/file.controller.js +44 -38
- package/dist/file/file.controller.js.map +1 -1
- package/dist/file/file.service.d.ts +2 -2
- package/dist/file/file.service.d.ts.map +1 -1
- package/dist/file/file.service.js +25 -42
- package/dist/file/file.service.js.map +1 -1
- package/dist/file/provider/azure.provider.d.ts.map +1 -1
- package/dist/file/provider/azure.provider.js +10 -15
- package/dist/file/provider/azure.provider.js.map +1 -1
- package/dist/file/provider/gcs.provider.d.ts.map +1 -1
- package/dist/file/provider/gcs.provider.js +8 -9
- package/dist/file/provider/gcs.provider.js.map +1 -1
- package/dist/file/provider/local.provider.d.ts.map +1 -1
- package/dist/file/provider/local.provider.js +11 -10
- package/dist/file/provider/local.provider.js.map +1 -1
- package/dist/file/provider/provider.factory.d.ts +1 -1
- package/dist/file/provider/provider.factory.d.ts.map +1 -1
- package/dist/file/provider/provider.factory.js +16 -6
- package/dist/file/provider/provider.factory.js.map +1 -1
- package/dist/file/provider/s3.provider.d.ts.map +1 -1
- package/dist/file/provider/s3.provider.js +4 -20
- package/dist/file/provider/s3.provider.js.map +1 -1
- package/dist/file/provider/spaces.provider.d.ts.map +1 -1
- package/dist/file/provider/spaces.provider.js +4 -19
- package/dist/file/provider/spaces.provider.js.map +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts +1 -1
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.controller.js +4 -3
- package/dist/integration-profile/integration-profile.controller.js.map +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts +1 -1
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.service.js +7 -4
- package/dist/integration-profile/integration-profile.service.js.map +1 -1
- package/dist/integration-profile/integration-profile.utils.d.ts +6 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -1
- package/dist/integration-profile/integration-profile.utils.js +9 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -1
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +9 -4
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +2 -1
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +11 -7
- package/dist/mail/mail.service.js.map +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js +1 -1
- package/dist/mcp/guards/mcp-auth.guard.js.map +1 -1
- package/dist/mcp/mcp-api-key.controller.d.ts +2 -0
- package/dist/mcp/mcp-api-key.controller.d.ts.map +1 -1
- package/dist/mcp/mcp-api-key.controller.js +1 -1
- package/dist/mcp/mcp-api-key.controller.js.map +1 -1
- package/dist/mcp/mcp-api-key.service.d.ts +2 -1
- package/dist/mcp/mcp-api-key.service.d.ts.map +1 -1
- package/dist/mcp/mcp-api-key.service.js +6 -2
- package/dist/mcp/mcp-api-key.service.js.map +1 -1
- package/dist/mcp/mcp.module.d.ts.map +1 -1
- package/dist/mcp/mcp.module.js +1 -3
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts +1 -1
- package/dist/oauth/interfaces/OAuthProvider.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.d.ts +4 -1
- package/dist/oauth/oauth.controller.d.ts.map +1 -1
- package/dist/oauth/oauth.controller.js +31 -5
- package/dist/oauth/oauth.controller.js.map +1 -1
- package/dist/oauth/oauth.service.d.ts +12 -1
- package/dist/oauth/oauth.service.d.ts.map +1 -1
- package/dist/oauth/oauth.service.js +125 -23
- package/dist/oauth/oauth.service.js.map +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts +1 -1
- package/dist/oauth/providers/abstract.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.d.ts +5 -2
- package/dist/oauth/providers/facebook.provider.d.ts.map +1 -1
- package/dist/oauth/providers/facebook.provider.js +34 -19
- package/dist/oauth/providers/facebook.provider.js.map +1 -1
- package/dist/oauth/providers/github.provider.d.ts +6 -2
- package/dist/oauth/providers/github.provider.d.ts.map +1 -1
- package/dist/oauth/providers/github.provider.js +43 -20
- package/dist/oauth/providers/github.provider.js.map +1 -1
- package/dist/oauth/providers/google.provider.d.ts +5 -2
- package/dist/oauth/providers/google.provider.d.ts.map +1 -1
- package/dist/oauth/providers/google.provider.js +34 -19
- package/dist/oauth/providers/google.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts +5 -2
- package/dist/oauth/providers/microsoft-entra-id.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft-entra-id.provider.js +43 -40
- package/dist/oauth/providers/microsoft-entra-id.provider.js.map +1 -1
- package/dist/oauth/providers/microsoft.provider.d.ts +5 -2
- package/dist/oauth/providers/microsoft.provider.d.ts.map +1 -1
- package/dist/oauth/providers/microsoft.provider.js +34 -19
- package/dist/oauth/providers/microsoft.provider.js.map +1 -1
- package/dist/profile/profile.controller.js +1 -1
- package/dist/profile/profile.controller.js.map +1 -1
- package/dist/security/security.service.d.ts +2 -0
- package/dist/security/security.service.d.ts.map +1 -1
- package/dist/security/security.service.js +21 -0
- package/dist/security/security.service.js.map +1 -1
- package/dist/setting/setting.service.d.ts +26 -1
- package/dist/setting/setting.service.d.ts.map +1 -1
- package/dist/setting/setting.service.js +240 -5
- package/dist/setting/setting.service.js.map +1 -1
- package/dist/user/user.controller.d.ts +2 -1
- package/dist/user/user.controller.d.ts.map +1 -1
- package/dist/user/user.controller.js +5 -4
- package/dist/user/user.controller.js.map +1 -1
- package/dist/user/user.service.d.ts +4 -2
- package/dist/user/user.service.d.ts.map +1 -1
- package/dist/user/user.service.js +19 -4
- package/dist/user/user.service.js.map +1 -1
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts +17 -2
- package/dist/webhook-integration/dto/webhook-action.dto.d.ts.map +1 -1
- package/dist/webhook-integration/dto/webhook-action.dto.js +93 -2
- package/dist/webhook-integration/dto/webhook-action.dto.js.map +1 -1
- package/dist/webhook-integration/webhook-command.registry.d.ts +15 -0
- package/dist/webhook-integration/webhook-command.registry.d.ts.map +1 -0
- package/dist/webhook-integration/webhook-command.registry.js +36 -0
- package/dist/webhook-integration/webhook-command.registry.js.map +1 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts +19 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +38 -0
- package/dist/webhook-integration/webhook-integration.controller.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.module.js +5 -0
- package/dist/webhook-integration/webhook-integration.module.js.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts +25 -1
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +332 -23
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/integration_provider.yaml +16 -0
- package/hedhog/data/menu.yaml +0 -16
- package/hedhog/data/route.yaml +32 -383
- package/hedhog/data/setting_group.yaml +179 -174
- package/hedhog/data/setting_subgroup.yaml +16 -0
- package/hedhog/frontend/app/account/components/tokens.tsx.ejs +421 -0
- package/hedhog/frontend/app/account/layout.tsx.ejs +6 -3
- package/hedhog/frontend/app/account/mcp-tokens/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/account/tokens/page.tsx.ejs +5 -0
- package/hedhog/frontend/app/configurations/[slug]/components/setting-field.tsx.ejs +414 -76
- package/hedhog/frontend/app/configurations/layout.tsx.ejs +1 -1
- package/hedhog/frontend/app/dashboard/dashboard-home-tabs.tsx.ejs +3 -1
- package/hedhog/frontend/app/integration/profiles/page.tsx.ejs +186 -18
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +597 -21
- package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
- package/hedhog/frontend/messages/en.json +67 -3
- package/hedhog/frontend/messages/pt.json +67 -3
- package/hedhog/table/file.yaml +1 -1
- package/hedhog/table/mcp_api_key.yaml +4 -0
- package/hedhog/table/webhook_action.yaml +57 -1
- package/package.json +5 -4
- package/src/auth/guards/auth.guard.ts +32 -0
- package/src/file/file.controller.ts +51 -42
- package/src/file/file.service.ts +31 -52
- package/src/file/provider/azure.provider.ts +14 -28
- package/src/file/provider/gcs.provider.ts +9 -9
- package/src/file/provider/local.provider.ts +10 -13
- package/src/file/provider/provider.factory.ts +19 -7
- package/src/file/provider/s3.provider.ts +4 -20
- package/src/file/provider/spaces.provider.ts +4 -19
- package/src/integration-profile/integration-profile.controller.ts +2 -0
- package/src/integration-profile/integration-profile.service.ts +7 -2
- package/src/integration-profile/integration-profile.utils.ts +17 -0
- package/src/mail/mail.module.ts +14 -4
- package/src/mail/mail.service.ts +12 -7
- package/src/mcp/guards/mcp-auth.guard.ts +1 -1
- package/src/mcp/mcp-api-key.controller.ts +2 -2
- package/src/mcp/mcp-api-key.service.ts +6 -2
- package/src/mcp/mcp.module.ts +1 -3
- package/src/oauth/interfaces/OAuthProvider.ts +1 -1
- package/src/oauth/oauth.controller.ts +30 -2
- package/src/oauth/oauth.service.ts +194 -35
- package/src/oauth/providers/abstract.provider.ts +1 -1
- package/src/oauth/providers/facebook.provider.ts +36 -20
- package/src/oauth/providers/github.provider.ts +46 -23
- package/src/oauth/providers/google.provider.ts +36 -20
- package/src/oauth/providers/microsoft-entra-id.provider.ts +42 -46
- package/src/oauth/providers/microsoft.provider.ts +36 -21
- package/src/profile/profile.controller.ts +2 -2
- package/src/security/security.service.ts +34 -1
- package/src/setting/setting.service.ts +307 -7
- package/src/user/user.controller.ts +4 -1
- package/src/user/user.service.ts +20 -2
- package/src/webhook-integration/dto/webhook-action.dto.ts +85 -4
- package/src/webhook-integration/webhook-command.registry.ts +42 -0
- package/src/webhook-integration/webhook-integration.controller.ts +20 -0
- package/src/webhook-integration/webhook-integration.module.ts +50 -45
- package/src/webhook-integration/webhook-integration.service.ts +374 -11
- package/dist/mail-profile/dto/create.dto.d.ts +0 -23
- package/dist/mail-profile/dto/create.dto.d.ts.map +0 -1
- package/dist/mail-profile/dto/create.dto.js +0 -139
- package/dist/mail-profile/dto/create.dto.js.map +0 -1
- package/dist/mail-profile/dto/update.dto.d.ts +0 -6
- package/dist/mail-profile/dto/update.dto.d.ts.map +0 -1
- package/dist/mail-profile/dto/update.dto.js +0 -9
- package/dist/mail-profile/dto/update.dto.js.map +0 -1
- package/dist/mail-profile/mail-profile.controller.d.ts +0 -94
- package/dist/mail-profile/mail-profile.controller.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.controller.js +0 -95
- package/dist/mail-profile/mail-profile.controller.js.map +0 -1
- package/dist/mail-profile/mail-profile.module.d.ts +0 -3
- package/dist/mail-profile/mail-profile.module.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.module.js +0 -31
- package/dist/mail-profile/mail-profile.module.js.map +0 -1
- package/dist/mail-profile/mail-profile.service.d.ts +0 -109
- package/dist/mail-profile/mail-profile.service.d.ts.map +0 -1
- package/dist/mail-profile/mail-profile.service.js +0 -278
- package/dist/mail-profile/mail-profile.service.js.map +0 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts +0 -20
- package/dist/mcp/tools/core-mail-profile.mcp-tools.d.ts.map +0 -1
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js +0 -96
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
- package/dist/whatsapp-profile/dto/create.dto.d.ts +0 -11
- package/dist/whatsapp-profile/dto/create.dto.d.ts.map +0 -1
- package/dist/whatsapp-profile/dto/create.dto.js +0 -55
- package/dist/whatsapp-profile/dto/create.dto.js.map +0 -1
- package/dist/whatsapp-profile/dto/update.dto.d.ts +0 -6
- package/dist/whatsapp-profile/dto/update.dto.d.ts.map +0 -1
- package/dist/whatsapp-profile/dto/update.dto.js +0 -9
- package/dist/whatsapp-profile/dto/update.dto.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts +0 -54
- package/dist/whatsapp-profile/whatsapp-profile.controller.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.controller.js +0 -85
- package/dist/whatsapp-profile/whatsapp-profile.controller.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts +0 -3
- package/dist/whatsapp-profile/whatsapp-profile.module.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.module.js +0 -26
- package/dist/whatsapp-profile/whatsapp-profile.module.js.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts +0 -63
- package/dist/whatsapp-profile/whatsapp-profile.service.d.ts.map +0 -1
- package/dist/whatsapp-profile/whatsapp-profile.service.js +0 -138
- package/dist/whatsapp-profile/whatsapp-profile.service.js.map +0 -1
- package/hedhog/data/mail_profile.yaml +0 -18
- package/hedhog/data/whatsapp_profile.yaml +0 -6
- package/hedhog/table/mail_profile.yaml +0 -75
- package/hedhog/table/whatsapp_profile.yaml +0 -26
- package/src/mail-profile/dto/create.dto.ts +0 -145
- package/src/mail-profile/dto/update.dto.ts +0 -4
- package/src/mail-profile/mail-profile.controller.ts +0 -56
- package/src/mail-profile/mail-profile.module.ts +0 -18
- package/src/mail-profile/mail-profile.service.ts +0 -308
- package/src/mcp/tools/core-mail-profile.mcp-tools.ts +0 -69
- package/src/whatsapp-profile/dto/create.dto.ts +0 -36
- package/src/whatsapp-profile/dto/update.dto.ts +0 -4
- package/src/whatsapp-profile/whatsapp-profile.controller.ts +0 -51
- package/src/whatsapp-profile/whatsapp-profile.module.ts +0 -13
- package/src/whatsapp-profile/whatsapp-profile.service.ts +0 -160
package/src/mail/mail.service.ts
CHANGED
|
@@ -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 ===
|
|
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([
|
|
76
|
-
|
|
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
|
-
|
|
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([
|
|
113
|
-
|
|
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
|
|
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
|
},
|
package/src/mcp/mcp.module.ts
CHANGED
|
@@ -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 {}
|
|
@@ -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({
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
24
|
+
private async loadConfig() {
|
|
23
25
|
const settings = await this.setting.getSettingValues([
|
|
24
|
-
'
|
|
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
|
-
|
|
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:
|
|
52
|
+
client_id: clientId,
|
|
34
53
|
redirect_uri: redirectURI,
|
|
35
54
|
response_type: 'code',
|
|
36
|
-
scope:
|
|
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
|
|
46
|
-
|
|
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
|
|
56
|
-
clientSecret
|
|
57
|
-
redirectUri:
|
|
71
|
+
clientId,
|
|
72
|
+
clientSecret,
|
|
73
|
+
redirectUri: resolvedRedirectUri,
|
|
58
74
|
});
|
|
59
75
|
const profile = await this.fetchProfile(
|
|
60
76
|
token.access_token,
|