insforge 1.2.10 → 1.3.0
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/.claude-plugin/marketplace.json +20 -20
- package/.dockerignore +60 -60
- package/.env.example +83 -77
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +65 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -63
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +44 -44
- package/CLAUDE_PLUGIN.md +104 -104
- package/CODE_OF_CONDUCT.md +128 -128
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -30
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -182
- package/assets/Dark.svg +23 -23
- package/auth/package.json +28 -28
- package/auth/src/lib/broadcastService.ts +117 -115
- package/auth/src/pages/SignInPage.tsx +60 -57
- package/auth/src/pages/SignUpPage.tsx +60 -57
- package/auth/tsconfig.json +32 -32
- package/auth/tsconfig.node.json +11 -11
- package/backend/package.json +78 -75
- package/backend/src/api/routes/ai/index.routes.ts +3 -3
- package/backend/src/api/routes/auth/index.routes.ts +667 -570
- package/backend/src/api/routes/auth/oauth.routes.ts +473 -448
- package/backend/src/api/routes/database/advance.routes.ts +37 -16
- package/backend/src/api/routes/database/index.routes.ts +78 -1
- package/backend/src/api/routes/database/records.routes.ts +10 -10
- package/backend/src/api/routes/database/tables.routes.ts +0 -14
- package/backend/src/api/routes/docs/index.routes.ts +75 -76
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +18 -12
- package/backend/src/api/routes/metadata/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/storage/index.routes.ts +18 -12
- package/backend/src/api/routes/usage/index.routes.ts +6 -4
- package/backend/src/infra/database/database.manager.ts +14 -1
- package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
- package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
- package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
- package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
- package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
- package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
- package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
- package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
- package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
- package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
- package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
- package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/infra/security/token.manager.ts +219 -125
- package/backend/src/infra/socket/socket.manager.ts +198 -64
- package/backend/src/providers/ai/openrouter.provider.ts +12 -9
- package/backend/src/providers/email/base.provider.ts +4 -7
- package/backend/src/providers/email/cloud.provider.ts +84 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/index.ts +1 -0
- package/backend/src/server.ts +317 -284
- package/backend/src/services/ai/ai-model.service.ts +5 -5
- package/backend/src/services/ai/chat-completion.service.ts +4 -4
- package/backend/src/services/ai/image-generation.service.ts +3 -3
- package/backend/src/services/auth/auth.service.ts +14 -0
- package/backend/src/services/database/database-table.service.ts +0 -9
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/email/email.service.ts +5 -7
- package/backend/src/services/realtime/index.ts +3 -0
- package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
- package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
- package/backend/src/services/realtime/realtime-message.service.ts +260 -0
- package/backend/src/types/auth.ts +11 -0
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/types/socket.ts +7 -31
- package/backend/src/utils/cookies.ts +35 -0
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +301 -298
- package/backend/src/utils/sql-parser.ts +90 -0
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -129
- package/backend/tests/local/test-ai-usage.sh +80 -80
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -123
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -132
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-secrets.sh +249 -249
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -244
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -338
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tsconfig.json +22 -22
- package/claude-plugin/.claude-plugin/plugin.json +24 -24
- package/claude-plugin/README.md +133 -133
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -270
- package/docker-compose.prod.yml +204 -200
- package/docker-compose.yml +232 -228
- package/docker-init/db/db-init.sql +97 -97
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -236
- package/docs/README.md +44 -44
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +119 -67
- package/docs/core-concepts/ai/architecture.mdx +372 -372
- package/docs/core-concepts/ai/sdk.mdx +213 -213
- package/docs/core-concepts/authentication/architecture.mdx +278 -278
- package/docs/core-concepts/authentication/sdk.mdx +414 -414
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -529
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -221
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -184
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -129
- package/docs/core-concepts/database/architecture.mdx +255 -255
- package/docs/core-concepts/database/sdk.mdx +382 -382
- package/docs/core-concepts/email/architecture.mdx +101 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -105
- package/docs/core-concepts/functions/sdk.mdx +184 -184
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -243
- package/docs/core-concepts/storage/sdk.mdx +253 -253
- package/docs/deployment/README.md +94 -94
- package/docs/deployment/deploy-to-aws-ec2.md +564 -564
- package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
- package/docs/deployment/deploy-to-render.md +441 -441
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +232 -210
- package/docs/examples/framework-guides/nextjs.mdx +131 -131
- package/docs/examples/framework-guides/nuxt.mdx +165 -165
- package/docs/examples/framework-guides/react.mdx +165 -165
- package/docs/examples/framework-guides/svelte.mdx +153 -153
- package/docs/examples/framework-guides/vue.mdx +159 -159
- package/docs/examples/overview.mdx +67 -67
- package/docs/favicon.svg +19 -19
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/icons/ai.svg +4 -4
- package/docs/images/logos/nextjs.svg +4 -4
- package/docs/images/logos/nuxt.svg +4 -4
- package/docs/images/logos/react.svg +5 -5
- package/docs/images/logos/svelte.svg +4 -4
- package/docs/images/logos/vue.svg +5 -5
- package/docs/insforge-instructions-sdk.md +89 -88
- package/docs/introduction.mdx +45 -45
- package/docs/logo/dark.svg +22 -22
- package/docs/logo/light.svg +20 -20
- package/docs/partnership.mdx +651 -646
- package/docs/quickstart.mdx +82 -82
- package/docs/showcase.mdx +52 -52
- package/docs/snippets/sdk-installation.mdx +21 -21
- package/docs/snippets/service-icons.mdx +27 -27
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -69
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -3
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -9
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/apple.svg +3 -3
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -3
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +1 -1
- package/frontend/src/assets/logos/linkedin.svg +3 -3
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +16 -16
- package/frontend/src/assets/logos/tiktok.svg +5 -5
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -3
- package/frontend/src/components/layout/AppHeader.tsx +9 -10
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +6 -0
- package/frontend/src/features/auth/helpers.tsx +8 -0
- package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableForm.tsx +0 -4
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useTables.ts +32 -28
- package/frontend/src/features/database/index.ts +1 -0
- package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -37
- package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +35 -47
- package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +43 -54
- package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
- package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +35 -47
- package/frontend/src/features/database/services/advance.service.ts +0 -26
- package/frontend/src/features/database/services/database.service.ts +55 -0
- package/frontend/src/features/database/services/table.service.ts +0 -6
- package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
- package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +11 -9
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
- package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
- package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
- package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
- package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
- package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
- package/frontend/src/features/realtime/index.ts +11 -0
- package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
- package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
- package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
- package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +3 -3
- package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +1 -35
- package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
- package/frontend/src/lib/routing/AppRoutes.tsx +35 -20
- package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
- package/frontend/src/lib/utils/menuItems.ts +24 -0
- package/frontend/src/lib/utils/utils.ts +14 -1
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/functions/deno.json +24 -24
- package/functions/server.ts +315 -315
- package/i18n/README.ar.md +130 -130
- package/i18n/README.de.md +130 -130
- package/i18n/README.es.md +154 -154
- package/i18n/README.fr.md +134 -134
- package/i18n/README.hi.md +129 -129
- package/i18n/README.ja.md +174 -174
- package/i18n/README.ko.md +136 -136
- package/i18n/README.pt-BR.md +131 -131
- package/i18n/README.ru.md +129 -129
- package/i18n/README.zh-CN.md +133 -133
- package/openapi/ai.yaml +715 -715
- package/openapi/auth.yaml +1244 -1244
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +223 -223
- package/openapi/metadata.yaml +177 -177
- package/openapi/realtime.yaml +699 -0
- package/openapi/records.yaml +381 -381
- package/openapi/secrets.yaml +370 -370
- package/openapi/storage.yaml +875 -875
- package/openapi/tables.yaml +463 -463
- package/package.json +97 -97
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai.schema.ts +63 -59
- package/shared-schemas/src/auth-api.schema.ts +352 -339
- package/shared-schemas/src/auth.schema.ts +1 -1
- package/shared-schemas/src/database-api.schema.ts +32 -1
- package/shared-schemas/src/database.schema.ts +39 -0
- package/shared-schemas/src/docs.schema.ts +26 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/index.ts +4 -0
- package/shared-schemas/src/metadata.schema.ts +9 -0
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +13 -13
- package/zeabur/template.yml +1032 -1032
- package/.cursor/rules/cursor-rules.mdc +0 -94
- package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
- package/test-gemini.sh +0 -35
- package/test-usage-admin.sh +0 -57
- package/test-usage.sh +0 -50
- /package/frontend/src/features/ai/{page → pages}/AIPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +0 -0
- /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
- /package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
|
@@ -1,448 +1,473 @@
|
|
|
1
|
-
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
|
-
import { AuthService } from '@/services/auth/auth.service.js';
|
|
3
|
-
import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
|
|
4
|
-
import { AuditService } from '@/services/logs/audit.service.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
await
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
//
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
params.set('
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
1
|
+
import { Router, Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { AuthService } from '@/services/auth/auth.service.js';
|
|
3
|
+
import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
|
|
4
|
+
import { AuditService } from '@/services/logs/audit.service.js';
|
|
5
|
+
import { TokenManager } from '@/infra/security/token.manager.js';
|
|
6
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
7
|
+
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
8
|
+
import { successResponse } from '@/utils/response.js';
|
|
9
|
+
import { AuthRequest, verifyAdmin } from '@/api/middlewares/auth.js';
|
|
10
|
+
import { setAuthCookie, REFRESH_TOKEN_COOKIE_NAME } from '@/utils/cookies.js';
|
|
11
|
+
import logger from '@/utils/logger.js';
|
|
12
|
+
import jwt from 'jsonwebtoken';
|
|
13
|
+
import {
|
|
14
|
+
createOAuthConfigRequestSchema,
|
|
15
|
+
updateOAuthConfigRequestSchema,
|
|
16
|
+
type ListOAuthConfigsResponse,
|
|
17
|
+
oAuthProvidersSchema,
|
|
18
|
+
} from '@insforge/shared-schemas';
|
|
19
|
+
import { isOAuthSharedKeysAvailable } from '@/utils/environment.js';
|
|
20
|
+
|
|
21
|
+
const router = Router();
|
|
22
|
+
const authService = AuthService.getInstance();
|
|
23
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
24
|
+
const auditService = AuditService.getInstance();
|
|
25
|
+
|
|
26
|
+
// Helper function to validate JWT_SECRET
|
|
27
|
+
const validateJwtSecret = (): string => {
|
|
28
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
29
|
+
if (!jwtSecret || jwtSecret.trim() === '') {
|
|
30
|
+
throw new AppError(
|
|
31
|
+
'JWT_SECRET environment variable is not configured.',
|
|
32
|
+
500,
|
|
33
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return jwtSecret;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// OAuth Configuration Management Routes (must come before wildcard routes)
|
|
40
|
+
// GET /api/auth/oauth/configs - List all OAuth configurations (admin only)
|
|
41
|
+
router.get('/configs', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
42
|
+
try {
|
|
43
|
+
const configs = await oAuthConfigService.getAllConfigs();
|
|
44
|
+
const response: ListOAuthConfigsResponse = {
|
|
45
|
+
data: configs,
|
|
46
|
+
count: configs.length,
|
|
47
|
+
};
|
|
48
|
+
successResponse(res, response);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error('Failed to list OAuth configurations', { error });
|
|
51
|
+
next(error);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// GET /api/auth/oauth/:provider/config - Get specific OAuth configuration (admin only)
|
|
56
|
+
router.get(
|
|
57
|
+
'/:provider/config',
|
|
58
|
+
verifyAdmin,
|
|
59
|
+
async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
60
|
+
try {
|
|
61
|
+
const { provider } = req.params;
|
|
62
|
+
const config = await oAuthConfigService.getConfigByProvider(provider);
|
|
63
|
+
const clientSecret = await oAuthConfigService.getClientSecretByProvider(provider);
|
|
64
|
+
|
|
65
|
+
if (!config) {
|
|
66
|
+
throw new AppError(
|
|
67
|
+
`OAuth configuration for ${provider} not found`,
|
|
68
|
+
404,
|
|
69
|
+
ERROR_CODES.NOT_FOUND
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
successResponse(res, {
|
|
74
|
+
...config,
|
|
75
|
+
clientSecret: clientSecret || undefined,
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
logger.error('Failed to get OAuth config by provider', {
|
|
79
|
+
provider: req.params.provider,
|
|
80
|
+
error,
|
|
81
|
+
});
|
|
82
|
+
next(error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// POST /api/auth/oauth/configs - Create new OAuth configuration (admin only)
|
|
88
|
+
router.post(
|
|
89
|
+
'/configs',
|
|
90
|
+
verifyAdmin,
|
|
91
|
+
async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
92
|
+
try {
|
|
93
|
+
const validationResult = createOAuthConfigRequestSchema.safeParse(req.body);
|
|
94
|
+
if (!validationResult.success) {
|
|
95
|
+
throw new AppError(
|
|
96
|
+
validationResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
|
|
97
|
+
400,
|
|
98
|
+
ERROR_CODES.INVALID_INPUT
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const input = validationResult.data;
|
|
103
|
+
|
|
104
|
+
// Check if using shared keys when not allowed
|
|
105
|
+
if (input.useSharedKey && !isOAuthSharedKeysAvailable()) {
|
|
106
|
+
throw new AppError(
|
|
107
|
+
'Shared OAuth keys are not enabled in this environment',
|
|
108
|
+
400,
|
|
109
|
+
ERROR_CODES.AUTH_OAUTH_CONFIG_ERROR
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const config = await oAuthConfigService.createConfig(input);
|
|
114
|
+
|
|
115
|
+
await auditService.log({
|
|
116
|
+
actor: req.user?.email || 'api-key',
|
|
117
|
+
action: 'CREATE_OAUTH_CONFIG',
|
|
118
|
+
module: 'AUTH',
|
|
119
|
+
details: {
|
|
120
|
+
provider: input.provider,
|
|
121
|
+
useSharedKey: input.useSharedKey || false,
|
|
122
|
+
},
|
|
123
|
+
ip_address: req.ip,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
successResponse(res, config);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.error('Failed to create OAuth configuration', { error });
|
|
129
|
+
next(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// PUT /api/auth/oauth/:provider/config - Update OAuth configuration (admin only)
|
|
135
|
+
router.put(
|
|
136
|
+
'/:provider/config',
|
|
137
|
+
verifyAdmin,
|
|
138
|
+
async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
139
|
+
try {
|
|
140
|
+
const provider = req.params.provider;
|
|
141
|
+
if (!provider || provider.length === 0 || provider.length > 50) {
|
|
142
|
+
throw new AppError('Invalid provider name', 400, ERROR_CODES.INVALID_INPUT);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const validationResult = updateOAuthConfigRequestSchema.safeParse(req.body);
|
|
146
|
+
if (!validationResult.success) {
|
|
147
|
+
throw new AppError(
|
|
148
|
+
validationResult.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
|
|
149
|
+
400,
|
|
150
|
+
ERROR_CODES.INVALID_INPUT
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const input = validationResult.data;
|
|
155
|
+
|
|
156
|
+
// Check if using shared keys when not allowed
|
|
157
|
+
if (input.useSharedKey && !isOAuthSharedKeysAvailable()) {
|
|
158
|
+
throw new AppError(
|
|
159
|
+
'Shared OAuth keys are not enabled in this environment',
|
|
160
|
+
400,
|
|
161
|
+
ERROR_CODES.AUTH_OAUTH_CONFIG_ERROR
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const config = await oAuthConfigService.updateConfig(provider, input);
|
|
166
|
+
|
|
167
|
+
await auditService.log({
|
|
168
|
+
actor: req.user?.email || 'api-key',
|
|
169
|
+
action: 'UPDATE_OAUTH_CONFIG',
|
|
170
|
+
module: 'AUTH',
|
|
171
|
+
details: {
|
|
172
|
+
provider,
|
|
173
|
+
updatedFields: Object.keys(input),
|
|
174
|
+
},
|
|
175
|
+
ip_address: req.ip,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
successResponse(res, config);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
logger.error('Failed to update OAuth configuration', {
|
|
181
|
+
error,
|
|
182
|
+
provider: req.params.provider,
|
|
183
|
+
});
|
|
184
|
+
next(error);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
// DELETE /api/auth/oauth/:provider/config - Delete OAuth configuration (admin only)
|
|
190
|
+
router.delete(
|
|
191
|
+
'/:provider/config',
|
|
192
|
+
verifyAdmin,
|
|
193
|
+
async (req: AuthRequest, res: Response, next: NextFunction) => {
|
|
194
|
+
try {
|
|
195
|
+
const provider = req.params.provider;
|
|
196
|
+
if (!provider || provider.length === 0 || provider.length > 50) {
|
|
197
|
+
throw new AppError('Invalid provider name', 400, ERROR_CODES.INVALID_INPUT);
|
|
198
|
+
}
|
|
199
|
+
const deleted = await oAuthConfigService.deleteConfig(provider);
|
|
200
|
+
|
|
201
|
+
if (!deleted) {
|
|
202
|
+
throw new AppError(
|
|
203
|
+
`OAuth configuration for ${provider} not found`,
|
|
204
|
+
404,
|
|
205
|
+
ERROR_CODES.NOT_FOUND
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await auditService.log({
|
|
210
|
+
actor: req.user?.email || 'api-key',
|
|
211
|
+
action: 'DELETE_OAUTH_CONFIG',
|
|
212
|
+
module: 'AUTH',
|
|
213
|
+
details: { provider },
|
|
214
|
+
ip_address: req.ip,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
successResponse(res, {
|
|
218
|
+
success: true,
|
|
219
|
+
message: `OAuth configuration for ${provider} deleted successfully`,
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
logger.error('Failed to delete OAuth configuration', {
|
|
223
|
+
error,
|
|
224
|
+
provider: req.params.provider,
|
|
225
|
+
});
|
|
226
|
+
next(error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// OAuth Flow Routes
|
|
232
|
+
// GET /api/auth/oauth/:provider - Initialize OAuth flow for any supported provider
|
|
233
|
+
router.get('/:provider', async (req: Request, res: Response, next: NextFunction) => {
|
|
234
|
+
try {
|
|
235
|
+
const { provider } = req.params;
|
|
236
|
+
const { redirect_uri } = req.query;
|
|
237
|
+
|
|
238
|
+
// Validate provider using OAuthProvidersSchema
|
|
239
|
+
const providerValidation = oAuthProvidersSchema.safeParse(provider);
|
|
240
|
+
if (!providerValidation.success) {
|
|
241
|
+
throw new AppError(
|
|
242
|
+
`Unsupported OAuth provider: ${provider}. Supported providers: ${oAuthProvidersSchema.options.join(', ')}`,
|
|
243
|
+
400,
|
|
244
|
+
ERROR_CODES.INVALID_INPUT
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const validatedProvider = providerValidation.data;
|
|
249
|
+
|
|
250
|
+
if (!redirect_uri) {
|
|
251
|
+
throw new AppError('Redirect URI is required', 400, ERROR_CODES.INVALID_INPUT);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const jwtPayload = {
|
|
255
|
+
provider: validatedProvider,
|
|
256
|
+
redirectUri: redirect_uri ? (redirect_uri as string) : undefined,
|
|
257
|
+
createdAt: Date.now(),
|
|
258
|
+
};
|
|
259
|
+
const jwtSecret = validateJwtSecret();
|
|
260
|
+
const state = jwt.sign(jwtPayload, jwtSecret, {
|
|
261
|
+
algorithm: 'HS256',
|
|
262
|
+
expiresIn: '1h', // Set expiration time for the state token
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const authUrl = await authService.generateOAuthUrl(validatedProvider, state);
|
|
266
|
+
|
|
267
|
+
successResponse(res, { authUrl });
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.error(`${req.params.provider} OAuth error`, { error });
|
|
270
|
+
|
|
271
|
+
// If it's already an AppError, pass it through
|
|
272
|
+
if (error instanceof AppError) {
|
|
273
|
+
next(error);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// For other errors, return the generic OAuth configuration error
|
|
278
|
+
next(
|
|
279
|
+
new AppError(
|
|
280
|
+
`${req.params.provider} OAuth is not properly configured. Please check your oauth configurations.`,
|
|
281
|
+
500,
|
|
282
|
+
ERROR_CODES.AUTH_OAUTH_CONFIG_ERROR
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// GET /api/auth/oauth/shared/callback/:state - Shared callback for OAuth providers
|
|
289
|
+
router.get('/shared/callback/:state', async (req: Request, res: Response, next: NextFunction) => {
|
|
290
|
+
try {
|
|
291
|
+
const { state } = req.params;
|
|
292
|
+
const { success, error, payload } = req.query;
|
|
293
|
+
|
|
294
|
+
if (!state) {
|
|
295
|
+
logger.warn('Shared OAuth callback called without state parameter');
|
|
296
|
+
throw new AppError('State parameter is required', 400, ERROR_CODES.INVALID_INPUT);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let redirectUri: string;
|
|
300
|
+
let provider: string;
|
|
301
|
+
try {
|
|
302
|
+
const jwtSecret = validateJwtSecret();
|
|
303
|
+
const decodedState = jwt.verify(state, jwtSecret) as {
|
|
304
|
+
provider: string;
|
|
305
|
+
redirectUri: string;
|
|
306
|
+
};
|
|
307
|
+
redirectUri = decodedState.redirectUri || '';
|
|
308
|
+
provider = decodedState.provider || '';
|
|
309
|
+
} catch {
|
|
310
|
+
logger.warn('Invalid state parameter', { state });
|
|
311
|
+
throw new AppError('Invalid state parameter', 400, ERROR_CODES.INVALID_INPUT);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Validate provider using OAuthProvidersSchema
|
|
315
|
+
const providerValidation = oAuthProvidersSchema.safeParse(provider);
|
|
316
|
+
if (!providerValidation.success) {
|
|
317
|
+
logger.warn('Invalid provider in state', { provider });
|
|
318
|
+
throw new AppError(
|
|
319
|
+
`Invalid provider in state: ${provider}. Supported providers: ${oAuthProvidersSchema.options.join(', ')}`,
|
|
320
|
+
400,
|
|
321
|
+
ERROR_CODES.INVALID_INPUT
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
const validatedProvider = providerValidation.data;
|
|
325
|
+
if (!redirectUri) {
|
|
326
|
+
throw new AppError('redirectUri is required', 400, ERROR_CODES.INVALID_INPUT);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (success !== 'true') {
|
|
330
|
+
const errorMessage = error || 'OAuth Authentication Failed';
|
|
331
|
+
logger.warn('Shared OAuth callback failed', { error: errorMessage, provider });
|
|
332
|
+
return res.redirect(`${redirectUri}/?error=${encodeURIComponent(String(errorMessage))}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!payload) {
|
|
336
|
+
throw new AppError('No payload provided in callback', 400, ERROR_CODES.INVALID_INPUT);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const payloadData = JSON.parse(
|
|
340
|
+
Buffer.from(payload as string, 'base64').toString('utf8')
|
|
341
|
+
) as Record<string, unknown>;
|
|
342
|
+
|
|
343
|
+
// Handle shared callback - transforms payload and creates/finds user
|
|
344
|
+
const result = await authService.handleSharedCallback(validatedProvider, payloadData);
|
|
345
|
+
|
|
346
|
+
// Set refresh token in httpOnly cookie and generate CSRF token
|
|
347
|
+
const tokenManager = TokenManager.getInstance();
|
|
348
|
+
const refreshToken = tokenManager.generateRefreshToken(result.user.id);
|
|
349
|
+
setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
|
|
350
|
+
const csrfToken = tokenManager.generateCsrfToken(refreshToken);
|
|
351
|
+
|
|
352
|
+
const params = new URLSearchParams();
|
|
353
|
+
// TODO: Remove all the parameters, will use PKCE in future
|
|
354
|
+
params.set('access_token', result.accessToken);
|
|
355
|
+
params.set('user_id', result.user.id);
|
|
356
|
+
params.set('email', result.user.email);
|
|
357
|
+
params.set('name', result.user.name);
|
|
358
|
+
params.set('csrf_token', csrfToken);
|
|
359
|
+
|
|
360
|
+
res.redirect(`${redirectUri}?${params.toString()}`);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
logger.error('Shared OAuth callback error', { error });
|
|
363
|
+
next(error);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Handle OAuth provider callback (shared logic for GET and POST)
|
|
369
|
+
* Most providers use GET, but Apple uses POST with form data
|
|
370
|
+
*/
|
|
371
|
+
const handleOAuthCallback = async (req: Request, res: Response, next: NextFunction) => {
|
|
372
|
+
try {
|
|
373
|
+
const { provider } = req.params;
|
|
374
|
+
// Support both query params (GET) and body params (POST for Apple)
|
|
375
|
+
// Use method-based source selection to prevent parameter pollution attacks
|
|
376
|
+
const isPostRequest = req.method === 'POST';
|
|
377
|
+
const code = isPostRequest ? (req.body.code as string) : (req.query.code as string);
|
|
378
|
+
const state = isPostRequest ? (req.body.state as string) : (req.query.state as string);
|
|
379
|
+
const token = isPostRequest ? (req.body.id_token as string) : (req.query.token as string);
|
|
380
|
+
|
|
381
|
+
if (!state) {
|
|
382
|
+
logger.warn('OAuth callback called without state parameter');
|
|
383
|
+
throw new AppError('State parameter is required', 400, ERROR_CODES.INVALID_INPUT);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Decode redirectUri from state (needed for both success and error paths)
|
|
387
|
+
let redirectUri: string;
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
const jwtSecret = validateJwtSecret();
|
|
391
|
+
const stateData = jwt.verify(state, jwtSecret) as {
|
|
392
|
+
provider: string;
|
|
393
|
+
redirectUri: string;
|
|
394
|
+
};
|
|
395
|
+
redirectUri = stateData.redirectUri || '';
|
|
396
|
+
} catch {
|
|
397
|
+
// Invalid state
|
|
398
|
+
logger.warn('Invalid state in provider callback', { state });
|
|
399
|
+
throw new AppError('Invalid state parameter', 400, ERROR_CODES.INVALID_INPUT);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (!redirectUri) {
|
|
403
|
+
throw new AppError('redirectUri is required', 400, ERROR_CODES.INVALID_INPUT);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
// Validate provider using OAuthProvidersSchema
|
|
408
|
+
const providerValidation = oAuthProvidersSchema.safeParse(provider);
|
|
409
|
+
if (!providerValidation.success) {
|
|
410
|
+
throw new AppError(
|
|
411
|
+
`Unsupported OAuth provider: ${provider}. Supported providers: ${oAuthProvidersSchema.options.join(', ')}`,
|
|
412
|
+
400,
|
|
413
|
+
ERROR_CODES.INVALID_INPUT
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const validatedProvider = providerValidation.data;
|
|
418
|
+
|
|
419
|
+
const result = await authService.handleOAuthCallback(validatedProvider, {
|
|
420
|
+
code: code || undefined,
|
|
421
|
+
token: token || undefined,
|
|
422
|
+
state: state || undefined,
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Set refresh token in httpOnly cookie and generate CSRF token
|
|
426
|
+
const tokenManager = TokenManager.getInstance();
|
|
427
|
+
const refreshToken = tokenManager.generateRefreshToken(result.user.id);
|
|
428
|
+
setAuthCookie(res, REFRESH_TOKEN_COOKIE_NAME, refreshToken);
|
|
429
|
+
const csrfToken = tokenManager.generateCsrfToken(refreshToken);
|
|
430
|
+
|
|
431
|
+
// Construct redirect URL with query parameters
|
|
432
|
+
const params = new URLSearchParams();
|
|
433
|
+
// TODO: Remove all the parameters, will use PKCE in future
|
|
434
|
+
params.set('access_token', result.accessToken);
|
|
435
|
+
params.set('user_id', result.user.id);
|
|
436
|
+
params.set('email', result.user.email);
|
|
437
|
+
params.set('name', result.user.name);
|
|
438
|
+
params.set('csrf_token', csrfToken);
|
|
439
|
+
|
|
440
|
+
const finalRedirectUri = `${redirectUri}?${params.toString()}`;
|
|
441
|
+
|
|
442
|
+
return res.redirect(finalRedirectUri);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
logger.error('OAuth callback error', {
|
|
445
|
+
error: error instanceof Error ? error.message : error,
|
|
446
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
447
|
+
provider: req.params.provider,
|
|
448
|
+
hasCode: !!code,
|
|
449
|
+
hasState: !!state,
|
|
450
|
+
hasToken: !!token,
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const errorMessage = error instanceof Error ? error.message : 'OAuth Authentication Failed';
|
|
454
|
+
|
|
455
|
+
// Redirect with error in URL parameters
|
|
456
|
+
const params = new URLSearchParams();
|
|
457
|
+
params.set('error', errorMessage);
|
|
458
|
+
|
|
459
|
+
return res.redirect(`${redirectUri}?${params.toString()}`);
|
|
460
|
+
}
|
|
461
|
+
} catch (error) {
|
|
462
|
+
logger.error('OAuth callback error', { error });
|
|
463
|
+
next(error);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// GET /api/auth/oauth/:provider/callback - OAuth provider callback (most providers)
|
|
468
|
+
router.get('/:provider/callback', handleOAuthCallback);
|
|
469
|
+
|
|
470
|
+
// POST /api/auth/oauth/:provider/callback - OAuth provider callback (Apple uses POST with form_post)
|
|
471
|
+
router.post('/:provider/callback', handleOAuthCallback);
|
|
472
|
+
|
|
473
|
+
export default router;
|