@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
|
@@ -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
|
-
|
|
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
|
|
23
|
-
|
|
28
|
+
private async loadConfig() {
|
|
24
29
|
const settings = await this.setting.getSettingValues([
|
|
25
|
-
'
|
|
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 =
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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:
|
|
60
|
-
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
|
|
22
|
-
|
|
24
|
+
private async loadConfig() {
|
|
23
25
|
const settings = await this.setting.getSettingValues([
|
|
24
|
-
'
|
|
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
|
-
|
|
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:
|
|
52
|
+
client_id: clientId,
|
|
34
53
|
redirect_uri: redirectURI,
|
|
35
54
|
response_type: 'code',
|
|
36
|
-
scope:
|
|
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
|
|
47
|
-
|
|
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
|
|
57
|
-
clientSecret
|
|
58
|
-
redirectUri:
|
|
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,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
1
2
|
import { HttpService } from '@nestjs/axios';
|
|
2
|
-
import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
|
|
3
|
+
import { BadRequestException, forwardRef, Inject, Injectable, Logger } 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
|
|
|
@@ -11,6 +13,7 @@ export class MicrosoftEntraIdProvider extends BaseOAuthProvider {
|
|
|
11
13
|
http: HttpService,
|
|
12
14
|
@Inject(forwardRef(() => SettingService))
|
|
13
15
|
private readonly setting: SettingService,
|
|
16
|
+
private readonly prisma: PrismaService,
|
|
14
17
|
) {
|
|
15
18
|
super(http);
|
|
16
19
|
}
|
|
@@ -32,39 +35,42 @@ export class MicrosoftEntraIdProvider extends BaseOAuthProvider {
|
|
|
32
35
|
return 'MICROSOFT-ENTRA-ID';
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
async
|
|
38
|
+
private async loadConfig() {
|
|
36
39
|
const settings = await this.setting.getSettingValues([
|
|
37
|
-
'
|
|
38
|
-
'microsoft_entra_id_client_secret',
|
|
39
|
-
'microsoft_entra_id_scopes',
|
|
40
|
-
'microsoft_entra_id_tenant_id',
|
|
40
|
+
'oauth-microsoft-entra-id-profile-id',
|
|
41
41
|
'url',
|
|
42
42
|
]);
|
|
43
|
+
const profileId = Number(settings['oauth-microsoft-entra-id-profile-id']);
|
|
44
|
+
if (!profileId) throw new BadRequestException('Microsoft Entra ID OAuth profile not configured.');
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
const profile = await this.prisma.integration_profile.findUnique({
|
|
47
|
+
where: { id: profileId },
|
|
48
|
+
include: { integration_provider: { select: { slug: true } } },
|
|
49
|
+
});
|
|
50
|
+
if (!profile) throw new BadRequestException('Microsoft Entra ID OAuth profile not found.');
|
|
51
|
+
|
|
52
|
+
const { clientId, clientSecret, tenantId } = buildOAuthConfigFromIntegration(
|
|
53
|
+
profile.integration_provider.slug,
|
|
54
|
+
profile.config,
|
|
55
|
+
);
|
|
56
|
+
if (!tenantId) {
|
|
57
|
+
throw new BadRequestException('Microsoft Entra ID OAuth profile is missing tenant_id.');
|
|
55
58
|
}
|
|
59
|
+
return { clientId, clientSecret, tenantId, baseUrl: String(settings['url'] ?? '') };
|
|
60
|
+
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
: [];
|
|
62
|
+
async getAuthUrl(callbackPath: string) {
|
|
63
|
+
const { clientId, tenantId, baseUrl } = await this.loadConfig();
|
|
64
|
+
const settings = await this.setting.getSettingValues(['oauth-microsoft-entra-id-scopes']);
|
|
65
|
+
const baseScopes: string[] = settings['oauth-microsoft-entra-id-scopes'] ?? ['openid', 'profile', 'email'];
|
|
62
66
|
const scopes = baseScopes.includes('offline_access')
|
|
63
67
|
? baseScopes
|
|
64
68
|
: [...baseScopes, 'offline_access'];
|
|
69
|
+
|
|
70
|
+
const redirectURI = this.buildRedirectUri(baseUrl, callbackPath);
|
|
65
71
|
const isConnectFlow = callbackPath.includes('/connect');
|
|
66
72
|
const params = new URLSearchParams({
|
|
67
|
-
client_id:
|
|
73
|
+
client_id: clientId,
|
|
68
74
|
redirect_uri: redirectURI,
|
|
69
75
|
response_type: 'code',
|
|
70
76
|
scope: scopes.join(' '),
|
|
@@ -75,43 +81,33 @@ export class MicrosoftEntraIdProvider extends BaseOAuthProvider {
|
|
|
75
81
|
`OAuth auth URL requested: provider=MICROSOFT-ENTRA-ID redirectUri=${redirectURI} hasOfflineAccess=${scopes.includes('offline_access') ? 'yes' : 'no'} connectFlow=${isConnectFlow ? 'yes' : 'no'} tenantConfigured=${tenantId ? 'yes' : 'no'}`,
|
|
76
82
|
);
|
|
77
83
|
|
|
78
|
-
// Force consent only for explicit account connect, not for routine login.
|
|
79
84
|
if (isConnectFlow) {
|
|
80
85
|
params.set('prompt', 'consent');
|
|
81
86
|
}
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
return authUrl;
|
|
88
|
+
return `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?${params.toString()}`;
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
async getProfile(code: string, type: string): Promise<any> {
|
|
88
|
-
let
|
|
91
|
+
async getProfile(code: string, type: string, redirectUri?: string): Promise<any> {
|
|
92
|
+
let resolvedRedirectUri = '';
|
|
89
93
|
|
|
90
94
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'url',
|
|
97
|
-
]);
|
|
98
|
-
const tenantId = settings['microsoft_entra_id_tenant_id'];
|
|
99
|
-
const scopes = settings['microsoft_entra_id_scopes'] ?? [];
|
|
100
|
-
redirectUri = this.buildRedirectUri(
|
|
101
|
-
settings['url'],
|
|
102
|
-
`/callback/microsoft-entra-id/${type}`,
|
|
103
|
-
);
|
|
95
|
+
const { clientId, clientSecret, tenantId, baseUrl } = await this.loadConfig();
|
|
96
|
+
const settings = await this.setting.getSettingValues(['oauth-microsoft-entra-id-scopes']);
|
|
97
|
+
const scopes: string[] = settings['oauth-microsoft-entra-id-scopes'] ?? [];
|
|
98
|
+
resolvedRedirectUri =
|
|
99
|
+
redirectUri?.trim() || this.buildRedirectUri(baseUrl, `/callback/microsoft-entra-id/${type}`);
|
|
104
100
|
|
|
105
101
|
this.logger.log(
|
|
106
|
-
`OAuth token exchange started: provider=MICROSOFT-ENTRA-ID type=${type} redirectUri=${
|
|
102
|
+
`OAuth token exchange started: provider=MICROSOFT-ENTRA-ID type=${type} redirectUri=${resolvedRedirectUri} hasCode=${code ? 'yes' : 'no'} codeLength=${code?.length ?? 0}`,
|
|
107
103
|
);
|
|
108
104
|
|
|
109
105
|
const token = await this.fetchToken({
|
|
110
106
|
code,
|
|
111
107
|
url: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
|
|
112
|
-
clientId
|
|
113
|
-
clientSecret
|
|
114
|
-
redirectUri,
|
|
108
|
+
clientId,
|
|
109
|
+
clientSecret,
|
|
110
|
+
redirectUri: resolvedRedirectUri,
|
|
115
111
|
});
|
|
116
112
|
|
|
117
113
|
const profile = await this.fetchProfile(
|
|
@@ -141,7 +137,7 @@ export class MicrosoftEntraIdProvider extends BaseOAuthProvider {
|
|
|
141
137
|
const mappedError = this.mapProviderError('Microsoft Entra ID', error);
|
|
142
138
|
|
|
143
139
|
this.logger.warn(
|
|
144
|
-
`OAuth callback failed: provider=MICROSOFT-ENTRA-ID status=${mappedError.status ?? 'unknown'} error=${mappedError.providerError ?? 'unknown'} providerCode=${mappedError.providerErrorCode ?? 'n/a'} reason=${mappedError.reason} redirectUri=${
|
|
140
|
+
`OAuth callback failed: provider=MICROSOFT-ENTRA-ID status=${mappedError.status ?? 'unknown'} error=${mappedError.providerError ?? 'unknown'} providerCode=${mappedError.providerErrorCode ?? 'n/a'} reason=${mappedError.reason} redirectUri=${resolvedRedirectUri || 'unresolved'}`,
|
|
145
141
|
);
|
|
146
142
|
|
|
147
143
|
throw mappedError;
|
|
@@ -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 MicrosoftProvider
|
|
8
|
-
extends BaseOAuthProvider {
|
|
9
|
+
export class MicrosoftProvider 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,19 +21,35 @@ export class MicrosoftProvider
|
|
|
19
21
|
return 'MICROSOFT';
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
async
|
|
23
|
-
|
|
24
|
+
private async loadConfig() {
|
|
24
25
|
const settings = await this.setting.getSettingValues([
|
|
25
|
-
'
|
|
26
|
-
'microsoft_client_secret',
|
|
27
|
-
'microsoft_scopes',
|
|
26
|
+
'oauth-microsoft-profile-id',
|
|
28
27
|
'url',
|
|
29
28
|
]);
|
|
29
|
+
const profileId = Number(settings['oauth-microsoft-profile-id']);
|
|
30
|
+
if (!profileId) throw new BadRequestException('Microsoft 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('Microsoft OAuth profile not found.');
|
|
37
|
+
|
|
38
|
+
const { clientId, clientSecret, tenantId } = buildOAuthConfigFromIntegration(
|
|
39
|
+
profile.integration_provider.slug,
|
|
40
|
+
profile.config,
|
|
41
|
+
);
|
|
42
|
+
return { clientId, clientSecret, tenantId, baseUrl: String(settings['url'] ?? '') };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async getAuthUrl(callbackPath: string) {
|
|
46
|
+
const { clientId, baseUrl } = await this.loadConfig();
|
|
47
|
+
const settings = await this.setting.getSettingValues(['oauth-microsoft-scopes']);
|
|
48
|
+
const scopes: string[] = settings['oauth-microsoft-scopes'] ?? ['openid', 'profile', 'email'];
|
|
49
|
+
const redirectURI = new URL(callbackPath, baseUrl).toString();
|
|
30
50
|
|
|
31
|
-
const redirectURI = new URL(callbackPath, settings['url']).toString();
|
|
32
|
-
const scopes = settings['microsoft_scopes']
|
|
33
51
|
const params = new URLSearchParams({
|
|
34
|
-
client_id:
|
|
52
|
+
client_id: clientId,
|
|
35
53
|
redirect_uri: redirectURI,
|
|
36
54
|
response_type: 'code',
|
|
37
55
|
scope: scopes.join(' '),
|
|
@@ -42,21 +60,18 @@ export class MicrosoftProvider
|
|
|
42
60
|
return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${params}`;
|
|
43
61
|
}
|
|
44
62
|
|
|
45
|
-
async getProfile(code: string, type: string): Promise<any> {
|
|
63
|
+
async getProfile(code: string, type: string, redirectUri?: string): Promise<any> {
|
|
64
|
+
const { clientId, clientSecret, baseUrl } = await this.loadConfig();
|
|
46
65
|
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
'microsoft_client_secret',
|
|
50
|
-
'microsoft_scopes',
|
|
51
|
-
'url',
|
|
52
|
-
]);
|
|
66
|
+
const resolvedRedirectUri =
|
|
67
|
+
redirectUri?.trim() || `${baseUrl}/callback/microsoft/${type}`;
|
|
53
68
|
|
|
54
69
|
const token = await this.fetchToken({
|
|
55
70
|
code,
|
|
56
71
|
url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
|
57
|
-
clientId
|
|
58
|
-
clientSecret
|
|
59
|
-
redirectUri:
|
|
72
|
+
clientId,
|
|
73
|
+
clientSecret,
|
|
74
|
+
redirectUri: resolvedRedirectUri,
|
|
60
75
|
});
|
|
61
76
|
|
|
62
77
|
const profile = await this.fetchProfile(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NoRole, User } from '@hed-hog/api';
|
|
2
2
|
import { Locale } from '@hed-hog/api-locale';
|
|
3
3
|
import { Body, Controller, Delete, Get, Headers, Ip, Param, ParseIntPipe, Post, Put, UploadedFile, UseInterceptors } from '@nestjs/common';
|
|
4
4
|
import { FileInterceptor } from '@nestjs/platform-express';
|
|
@@ -19,7 +19,7 @@ import { VerifyMfaTotpDto } from './dto/verify-mfa-totp.dto';
|
|
|
19
19
|
import { VerifyWebAuthnRegistrationDto } from './dto/verify-webauthn-registration.dto';
|
|
20
20
|
import { VerifyWebAuthnDto } from './dto/verify-webauthn.dto';
|
|
21
21
|
import { ProfileService } from './profile.service';
|
|
22
|
-
@
|
|
22
|
+
@NoRole()
|
|
23
23
|
@Controller('profile')
|
|
24
24
|
export class ProfileController {
|
|
25
25
|
|
|
@@ -2,7 +2,15 @@ import { getLocaleText } from '@hed-hog/api-locale';
|
|
|
2
2
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
|
3
3
|
import { ConfigService } from '@nestjs/config';
|
|
4
4
|
import * as argon2 from 'argon2';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
createCipheriv,
|
|
7
|
+
createDecipheriv,
|
|
8
|
+
createHash,
|
|
9
|
+
createHmac,
|
|
10
|
+
randomBytes,
|
|
11
|
+
scryptSync,
|
|
12
|
+
timingSafeEqual,
|
|
13
|
+
} from 'crypto';
|
|
6
14
|
|
|
7
15
|
@Injectable()
|
|
8
16
|
export class SecurityService {
|
|
@@ -31,6 +39,31 @@ export class SecurityService {
|
|
|
31
39
|
return createHash('sha256').update(value).digest('base64url');
|
|
32
40
|
}
|
|
33
41
|
|
|
42
|
+
signHmacSha256(value: string, secret?: string): string {
|
|
43
|
+
const signingSecret = secret || this.getJwtSecret();
|
|
44
|
+
return createHmac('sha256', signingSecret).update(value).digest('base64url');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
verifyHmacSha256(value: string, signature: string, secret?: string): boolean {
|
|
48
|
+
if (!signature) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const expected = this.signHmacSha256(value, secret);
|
|
54
|
+
const expectedBuffer = Buffer.from(expected);
|
|
55
|
+
const providedBuffer = Buffer.from(signature);
|
|
56
|
+
|
|
57
|
+
if (expectedBuffer.length !== providedBuffer.length) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return timingSafeEqual(expectedBuffer, providedBuffer);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
34
67
|
hashArgon2(value: string, pepper?: string): Promise<string> {
|
|
35
68
|
return argon2.hash(this.getValueWithPepper(value, pepper));
|
|
36
69
|
}
|