@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.
- package/dist/ai/ai.service.d.ts +3 -12
- package/dist/ai/ai.service.d.ts.map +1 -1
- package/dist/ai/ai.service.js +34 -125
- package/dist/ai/ai.service.js.map +1 -1
- 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/core.module.d.ts.map +1 -1
- package/dist/core.module.js +3 -3
- package/dist/core.module.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 +58 -89
- 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.enum.d.ts +2 -1
- package/dist/file/provider/provider.enum.d.ts.map +1 -1
- package/dist/file/provider/provider.enum.js +1 -0
- package/dist/file/provider/provider.enum.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 +18 -5
- 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 +27 -0
- package/dist/file/provider/spaces.provider.d.ts.map +1 -0
- package/dist/file/provider/spaces.provider.js +203 -0
- package/dist/file/provider/spaces.provider.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/integration-profile/dto/create.dto.d.ts +9 -0
- package/dist/integration-profile/dto/create.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/create.dto.js +54 -0
- package/dist/integration-profile/dto/create.dto.js.map +1 -0
- package/dist/integration-profile/dto/export.dto.d.ts +5 -0
- package/dist/integration-profile/dto/export.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/export.dto.js +29 -0
- package/dist/integration-profile/dto/export.dto.js.map +1 -0
- package/dist/integration-profile/dto/import-confirm.dto.d.ts +17 -0
- package/dist/integration-profile/dto/import-confirm.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/import-confirm.dto.js +77 -0
- package/dist/integration-profile/dto/import-confirm.dto.js.map +1 -0
- package/dist/integration-profile/dto/update.dto.d.ts.map +1 -0
- package/dist/integration-profile/dto/update.dto.js.map +1 -0
- package/dist/integration-profile/integration-profile.controller.d.ts +142 -0
- package/dist/integration-profile/integration-profile.controller.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.controller.js +166 -0
- package/dist/integration-profile/integration-profile.controller.js.map +1 -0
- package/dist/integration-profile/integration-profile.module.d.ts +3 -0
- package/dist/integration-profile/integration-profile.module.d.ts.map +1 -0
- package/dist/{mail-profile/mail-profile.module.js → integration-profile/integration-profile.module.js} +11 -13
- package/dist/integration-profile/integration-profile.module.js.map +1 -0
- package/dist/integration-profile/integration-profile.service.d.ts +160 -0
- package/dist/integration-profile/integration-profile.service.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.service.js +609 -0
- package/dist/integration-profile/integration-profile.service.js.map +1 -0
- package/dist/integration-profile/integration-profile.utils.d.ts +30 -0
- package/dist/integration-profile/integration-profile.utils.d.ts.map +1 -0
- package/dist/integration-profile/integration-profile.utils.js +156 -0
- package/dist/integration-profile/integration-profile.utils.js.map +1 -0
- package/dist/mail/mail.module.d.ts.map +1 -1
- package/dist/mail/mail.module.js +36 -23
- package/dist/mail/mail.module.js.map +1 -1
- package/dist/mail/mail.service.d.ts +4 -2
- package/dist/mail/mail.service.d.ts.map +1 -1
- package/dist/mail/mail.service.js +33 -51
- 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 +3 -5
- package/dist/mcp/mcp.module.js.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.d.ts.map +1 -1
- package/dist/mcp-chat/mcp-chat.service.js +17 -16
- package/dist/mcp-chat/mcp-chat.service.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 -7
- 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/event-webhook.controller.d.ts +1 -0
- package/dist/webhook-integration/event-webhook.controller.d.ts.map +1 -1
- package/dist/webhook-integration/event-webhook.controller.js +10 -0
- package/dist/webhook-integration/event-webhook.controller.js.map +1 -1
- package/dist/webhook-integration/event-webhook.service.d.ts +2 -0
- package/dist/webhook-integration/event-webhook.service.d.ts.map +1 -1
- package/dist/webhook-integration/event-webhook.service.js +30 -0
- package/dist/webhook-integration/event-webhook.service.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 +20 -0
- package/dist/webhook-integration/webhook-integration.controller.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.controller.js +48 -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 +27 -2
- package/dist/webhook-integration/webhook-integration.service.d.ts.map +1 -1
- package/dist/webhook-integration/webhook-integration.service.js +377 -50
- package/dist/webhook-integration/webhook-integration.service.js.map +1 -1
- package/hedhog/data/file_provider.yaml +9 -1
- package/hedhog/data/integration_provider.yaml +148 -0
- package/hedhog/data/integration_type.yaml +29 -0
- package/hedhog/data/menu.yaml +6 -21
- package/hedhog/data/role.yaml +8 -0
- package/hedhog/data/route.yaml +73 -373
- package/hedhog/data/setting_group.yaml +209 -365
- package/hedhog/data/setting_subgroup.yaml +21 -13
- package/hedhog/frontend/app/account/components/connected-accounts.tsx.ejs +25 -20
- 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 +938 -225
- 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 +1830 -0
- package/hedhog/frontend/app/integrations-webhooks/page.tsx.ejs +829 -128
- package/hedhog/frontend/app/mail/profiles/page.tsx.ejs +1 -820
- package/hedhog/frontend/app/setting-list/page.tsx.ejs +229 -395
- package/hedhog/frontend/app/users/permissions.tsx.ejs +79 -2
- package/hedhog/frontend/app/users/user-sheet.tsx.ejs +7 -2
- package/hedhog/frontend/messages/en.json +239 -3
- package/hedhog/frontend/messages/pt.json +239 -3
- package/hedhog/table/file.yaml +1 -1
- package/hedhog/table/integration_profile.yaml +34 -0
- package/hedhog/table/integration_provider.yaml +23 -0
- package/hedhog/table/integration_type.yaml +19 -0
- package/hedhog/table/mcp_api_key.yaml +4 -0
- package/hedhog/table/webhook_action.yaml +141 -85
- package/package.json +4 -3
- package/src/ai/ai.service.ts +35 -153
- package/src/auth/guards/auth.guard.ts +32 -0
- package/src/core.module.ts +3 -3
- package/src/file/file.controller.ts +51 -42
- package/src/file/file.service.ts +67 -104
- 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.enum.ts +1 -0
- package/src/file/provider/provider.factory.ts +21 -6
- package/src/file/provider/s3.provider.ts +4 -20
- package/src/file/provider/spaces.provider.ts +246 -0
- package/src/index.ts +6 -3
- package/src/integration-profile/dto/create.dto.ts +44 -0
- package/src/integration-profile/dto/export.dto.ts +15 -0
- package/src/integration-profile/dto/import-confirm.dto.ts +61 -0
- package/src/integration-profile/integration-profile.controller.ts +131 -0
- package/src/integration-profile/integration-profile.module.ts +16 -0
- package/src/integration-profile/integration-profile.service.ts +793 -0
- package/src/integration-profile/integration-profile.utils.ts +173 -0
- package/src/mail/mail.module.ts +41 -22
- package/src/mail/mail.service.ts +40 -64
- 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 +3 -5
- package/src/mcp-chat/mcp-chat.service.ts +23 -20
- 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 -9
- 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/event-webhook.controller.ts +55 -50
- package/src/webhook-integration/event-webhook.service.ts +422 -384
- package/src/webhook-integration/webhook-command.registry.ts +42 -0
- package/src/webhook-integration/webhook-integration.controller.ts +25 -0
- package/src/webhook-integration/webhook-integration.module.ts +50 -45
- package/src/webhook-integration/webhook-integration.service.ts +444 -53
- 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.map +0 -1
- 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.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 -32
- 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 -178
- package/dist/mcp/tools/core-mail-profile.mcp-tools.js.map +0 -1
- package/hedhog/data/mail_profile.yaml +0 -18
- package/hedhog/table/mail_profile.yaml +0 -75
- package/src/mail-profile/dto/create.dto.ts +0 -145
- 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 -128
- /package/dist/{mail-profile → integration-profile}/dto/update.dto.d.ts +0 -0
- /package/dist/{mail-profile → integration-profile}/dto/update.dto.js +0 -0
- /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
|
-
|
|
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,
|
|
@@ -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,
|