insforge 0.3.2 → 1.2.10
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 -0
- package/.cursor/rules/cursor-rules.mdc +94 -0
- package/.dockerignore +3 -0
- package/.env.example +33 -4
- package/.github/ISSUE_TEMPLATE/bug_report.yml +13 -60
- package/.github/ISSUE_TEMPLATE/config.yml +2 -2
- package/.github/ISSUE_TEMPLATE/feature_request.yml +10 -63
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
- package/.github/workflows/build-image.yml +2 -1
- package/.github/workflows/e2e.yml +63 -0
- package/CHANGELOG.md +41 -0
- package/CLAUDE_PLUGIN.md +104 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +1 -1
- package/Dockerfile +4 -1
- package/README.md +66 -18
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/auth/index.html +13 -0
- package/auth/package.json +28 -0
- package/auth/public/favicon.ico +0 -0
- package/auth/src/App.tsx +33 -0
- package/auth/src/components/ErrorCard.tsx +37 -0
- package/auth/src/components/Layout.tsx +13 -0
- package/auth/src/index.css +19 -0
- package/auth/src/lib/broadcastService.ts +115 -0
- package/auth/src/lib/utils.ts +11 -0
- package/auth/src/main.tsx +22 -0
- package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
- package/auth/src/pages/ResetPasswordPage.tsx +11 -0
- package/auth/src/pages/SignInPage.tsx +57 -0
- package/auth/src/pages/SignUpPage.tsx +57 -0
- package/auth/src/pages/VerifyEmailPage.tsx +20 -0
- package/auth/src/vite-env.d.ts +10 -0
- package/auth/tsconfig.json +32 -0
- package/auth/tsconfig.node.json +11 -0
- package/auth/vite.config.ts +25 -0
- package/backend/package.json +9 -9
- package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
- package/backend/src/api/middlewares/rate-limiters.ts +127 -0
- package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +20 -24
- package/backend/src/api/routes/auth/index.routes.ts +570 -0
- package/backend/src/api/routes/auth/oauth.routes.ts +448 -0
- package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +107 -65
- package/backend/src/api/routes/database/index.routes.ts +13 -0
- package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +22 -8
- package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +20 -23
- package/backend/src/api/routes/docs/index.routes.ts +76 -0
- package/backend/src/api/routes/functions/index.routes.ts +188 -0
- package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
- package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +21 -31
- package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
- package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +34 -53
- package/backend/src/api/routes/usage/index.routes.ts +89 -0
- package/backend/src/infra/config/app.config.ts +51 -0
- package/backend/src/{core/database/manager.ts → infra/database/database.manager.ts} +76 -85
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
- package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
- package/backend/src/infra/security/token.manager.ts +125 -0
- package/backend/src/{core/socket/socket.ts → infra/socket/socket.manager.ts} +15 -15
- package/backend/src/providers/ai/openrouter.provider.ts +377 -0
- package/backend/src/providers/email/base.provider.ts +41 -0
- package/backend/src/providers/email/cloud.provider.ts +187 -0
- package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
- package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
- package/backend/src/providers/logs/local.provider.ts +185 -0
- package/backend/src/providers/oauth/base.provider.ts +29 -0
- package/backend/src/providers/oauth/discord.provider.ts +195 -0
- package/backend/src/providers/oauth/facebook.provider.ts +194 -0
- package/backend/src/providers/oauth/github.provider.ts +208 -0
- package/backend/src/providers/oauth/google.provider.ts +249 -0
- package/backend/src/providers/oauth/index.ts +7 -0
- package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
- package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
- package/backend/src/providers/oauth/x.provider.ts +202 -0
- package/backend/src/providers/storage/base.provider.ts +29 -0
- package/backend/src/providers/storage/local.provider.ts +103 -0
- package/backend/src/providers/storage/s3.provider.ts +313 -0
- package/backend/src/server.ts +70 -74
- package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
- package/backend/src/services/ai/ai-model.service.ts +60 -0
- package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
- package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
- package/backend/src/services/ai/helpers.ts +64 -0
- package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
- package/backend/src/services/ai/index.ts +13 -0
- package/backend/src/services/auth/auth-config.service.ts +250 -0
- package/backend/src/services/auth/auth-otp.service.ts +424 -0
- package/backend/src/services/auth/auth.service.ts +1136 -0
- package/backend/src/services/auth/index.ts +4 -0
- package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
- package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
- package/backend/src/services/database/database-table.service.ts +811 -0
- package/backend/src/services/email/email.service.ts +75 -0
- package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
- package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
- package/backend/src/services/logs/log.service.ts +73 -0
- package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
- package/backend/src/services/storage/storage.service.ts +617 -0
- package/backend/src/services/usage/usage.service.ts +149 -0
- package/backend/src/types/auth.ts +66 -2
- package/backend/src/types/email.ts +8 -0
- package/backend/src/types/error-constants.ts +4 -0
- package/backend/src/types/logs.ts +0 -29
- package/backend/src/{core/socket/types.ts → types/socket.ts} +5 -6
- package/backend/src/utils/environment.ts +9 -3
- package/backend/src/utils/logger.ts +20 -2
- package/backend/src/utils/seed.ts +150 -57
- package/backend/src/utils/sql-parser.ts +1 -1
- package/backend/src/utils/utils.ts +114 -0
- package/backend/src/utils/validations.ts +40 -4
- package/backend/tests/local/test-ai-config.sh +129 -0
- package/backend/tests/local/test-ai-usage.sh +80 -0
- package/backend/tests/local/test-auth-router.sh +1 -1
- package/backend/tests/local/test-e2e.sh +1 -1
- package/backend/tests/local/test-functions.sh +123 -0
- package/backend/tests/local/test-logs.sh +132 -0
- package/backend/tests/local/test-public-bucket.sh +3 -3
- package/backend/tests/local/test-secrets.sh +14 -12
- package/backend/tests/local/test-traditional-rest.sh +2 -2
- package/backend/tests/manual/test-rawsql-modes.sh +244 -0
- package/backend/tests/test-config.sh +37 -1
- package/backend/tests/unit/cloud-token.test.ts +48 -0
- package/backend/tests/unit/constant.test.ts +8 -0
- package/backend/tests/unit/email.test.ts +372 -0
- package/backend/tests/unit/environment.test.ts +59 -0
- package/backend/tests/unit/helpers.test.ts +63 -0
- package/backend/tests/unit/logger.test.ts +22 -0
- package/backend/tests/unit/rate-limit.test.ts +154 -0
- package/backend/tests/unit/response.test.ts +58 -0
- package/backend/tests/unit/sql-parser.test.ts +74 -0
- package/backend/tests/unit/uuid.test.ts +21 -0
- package/backend/tests/unit/validations.test.ts +80 -0
- package/backend/tsconfig.json +1 -1
- package/backend/vitest.config.ts +11 -0
- package/claude-plugin/.claude-plugin/plugin.json +24 -0
- package/claude-plugin/README.md +133 -0
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
- package/docker-compose.prod.yml +60 -4
- package/docker-compose.yml +65 -4
- package/docker-init/db/db-init.sql +6 -34
- package/docker-init/logs/vector.yml +236 -0
- package/docs/README.md +44 -0
- package/docs/changelog.mdx +67 -0
- package/docs/core-concepts/ai/architecture.mdx +373 -0
- package/docs/core-concepts/ai/sdk.mdx +213 -0
- package/docs/core-concepts/authentication/architecture.mdx +278 -0
- package/docs/core-concepts/authentication/sdk.mdx +414 -0
- package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
- package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
- package/docs/core-concepts/database/architecture.mdx +256 -0
- package/docs/core-concepts/database/sdk.mdx +382 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -0
- package/docs/core-concepts/functions/sdk.mdx +184 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -0
- package/docs/core-concepts/storage/sdk.mdx +253 -0
- package/docs/deployment/README.md +94 -0
- package/docs/deployment/deploy-to-aws-ec2.md +565 -0
- package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
- package/docs/deployment/deploy-to-render.md +441 -0
- package/docs/docs.json +210 -0
- package/docs/examples/framework-guides/nextjs.mdx +131 -0
- package/docs/examples/framework-guides/nuxt.mdx +165 -0
- package/docs/examples/framework-guides/react.mdx +165 -0
- package/docs/examples/framework-guides/svelte.mdx +153 -0
- package/docs/examples/framework-guides/vue.mdx +159 -0
- package/docs/examples/overview.mdx +67 -0
- package/docs/favicon.svg +19 -0
- package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
- package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
- package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
- package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
- package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
- package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
- package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
- package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
- package/docs/images/checks-passed.png +0 -0
- package/docs/images/dashboard-connect-expanded.png +0 -0
- package/docs/images/dashboard-connect.png +0 -0
- package/docs/images/hero-dark.png +0 -0
- package/docs/images/hero-light.png +0 -0
- package/docs/images/icons/ai.svg +4 -0
- package/docs/images/icons/auth.svg +1 -0
- package/docs/images/icons/database.svg +1 -0
- package/docs/images/icons/function.svg +1 -0
- package/docs/images/icons/storage.svg +1 -0
- package/docs/images/logos/nextjs.svg +4 -0
- package/docs/images/logos/nuxt.svg +4 -0
- package/docs/images/logos/react.svg +5 -0
- package/docs/images/logos/svelte.svg +4 -0
- package/docs/images/logos/vue.svg +5 -0
- package/docs/images/mcp-install.png +0 -0
- package/docs/images/onboarding-mcp.png +0 -0
- package/docs/insforge-instructions-sdk.md +55 -374
- package/docs/introduction.mdx +45 -0
- package/docs/logo/dark.svg +22 -0
- package/docs/logo/light.svg +20 -0
- package/docs/partnership.mdx +647 -0
- package/docs/quickstart.mdx +83 -0
- package/docs/showcase/2048-arena.png +0 -0
- package/docs/showcase/framegen-cloud.png +0 -0
- package/docs/showcase/line-connect-race.png +0 -0
- package/docs/showcase/moment-vibe.png +0 -0
- package/docs/showcase/national-flags.png +0 -0
- package/docs/showcase/pokemon-vibe.png +0 -0
- package/docs/showcase/pure-browse-buy.png +0 -0
- package/docs/showcase.mdx +52 -0
- package/docs/snippets/sdk-installation.mdx +22 -0
- package/docs/snippets/service-icons.mdx +27 -0
- package/eslint.config.js +10 -3
- package/frontend/package.json +10 -4
- package/frontend/src/App.tsx +13 -82
- package/frontend/src/assets/icons/connected.svg +3 -0
- package/frontend/src/assets/icons/loader.svg +9 -0
- package/frontend/src/assets/logos/apple.svg +4 -0
- package/frontend/src/assets/logos/discord.svg +1 -1
- package/frontend/src/assets/logos/facebook.svg +3 -0
- package/frontend/src/assets/logos/instagram.svg +2 -0
- package/frontend/src/assets/logos/linkedin.svg +3 -0
- package/frontend/src/assets/logos/microsoft.svg +1 -0
- package/frontend/src/assets/logos/spotify.svg +17 -0
- package/frontend/src/assets/logos/tiktok.svg +6 -0
- package/frontend/src/assets/logos/x.svg +3 -0
- package/frontend/src/components/Checkbox.tsx +27 -29
- package/frontend/src/components/CodeBlock.tsx +55 -2
- package/frontend/src/components/CodeEditor.tsx +92 -0
- package/frontend/src/components/ConfirmDialog.tsx +1 -1
- package/frontend/src/components/ConnectCTA.tsx +38 -0
- package/frontend/src/components/CopyButton.tsx +52 -15
- package/frontend/src/components/ErrorState.tsx +1 -2
- package/frontend/src/components/FeatureSidebar.tsx +6 -6
- package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
- package/frontend/src/components/JsonHighlight.tsx +21 -9
- package/frontend/src/components/ProjectInfoModal.tsx +128 -0
- package/frontend/src/components/PromptDialog.tsx +1 -4
- package/frontend/src/components/SearchInput.tsx +1 -2
- package/frontend/src/components/Stepper.tsx +53 -0
- package/frontend/src/components/ThemeToggle.tsx +3 -3
- package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
- package/frontend/src/components/datagrid/index.ts +23 -0
- package/frontend/src/components/index.ts +23 -30
- package/frontend/src/components/layout/AppHeader.tsx +133 -92
- package/frontend/src/components/layout/AppSidebar.tsx +80 -170
- package/frontend/src/components/layout/Layout.tsx +12 -23
- package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
- package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
- package/frontend/src/components/layout/index.ts +5 -0
- package/frontend/src/components/radix/Tooltip.tsx +24 -13
- package/frontend/src/components/radix/index.ts +22 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
- package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
- package/frontend/src/features/ai/components/index.ts +6 -0
- package/frontend/src/features/ai/helpers.ts +57 -71
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
- package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
- package/frontend/src/features/ai/page/AIPage.tsx +67 -79
- package/frontend/src/features/ai/services/ai.service.ts +5 -5
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +53 -30
- package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +44 -14
- package/frontend/src/features/auth/components/index.ts +5 -0
- package/frontend/src/features/auth/helpers.tsx +200 -0
- package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
- package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
- package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
- package/frontend/src/features/auth/index.ts +3 -2
- package/frontend/src/features/auth/page/AuthMethodsPage.tsx +275 -0
- package/frontend/src/features/auth/page/ConfigurationPage.tsx +395 -0
- package/frontend/src/features/auth/page/UsersPage.tsx +285 -0
- package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
- package/frontend/src/features/auth/services/config.service.ts +19 -0
- package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
- package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
- package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
- package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
- package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
- package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
- package/frontend/src/features/dashboard/components/index.ts +4 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +187 -169
- package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
- package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
- package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
- package/frontend/src/features/dashboard/prompts/index.ts +31 -0
- package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
- package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
- package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
- package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
- package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
- package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
- package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
- package/frontend/src/features/database/components/TableForm.tsx +28 -15
- package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
- package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
- package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
- package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
- package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
- package/frontend/src/features/database/components/index.ts +19 -0
- package/frontend/src/features/database/constants.ts +28 -2
- package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
- package/frontend/src/features/database/helpers.ts +2 -2
- package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
- package/frontend/src/features/database/hooks/useFullMetadata.ts +18 -0
- package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
- package/frontend/src/features/database/hooks/useRecords.ts +139 -0
- package/frontend/src/features/database/hooks/useTables.ts +131 -0
- package/frontend/src/features/database/index.ts +6 -1
- package/frontend/src/features/database/page/FunctionsPage.tsx +211 -0
- package/frontend/src/features/database/page/IndexesPage.tsx +240 -0
- package/frontend/src/features/database/page/PoliciesPage.tsx +248 -0
- package/frontend/src/features/database/page/SQLEditorPage.tsx +382 -0
- package/frontend/src/features/database/page/{DatabasePage.tsx → TablesPage.tsx} +186 -185
- package/frontend/src/features/database/page/TemplatesPage.tsx +39 -0
- package/frontend/src/features/database/page/TriggersPage.tsx +242 -0
- package/frontend/src/features/database/services/advance.service.ts +66 -0
- package/frontend/src/features/database/services/{database.service.ts → record.service.ts} +67 -64
- package/frontend/src/features/database/services/table.service.ts +64 -0
- package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
- package/frontend/src/features/database/templates/crm-system.ts +528 -0
- package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
- package/frontend/src/features/database/templates/index.ts +34 -0
- package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
- package/frontend/src/features/database/templates/notion-clone.ts +483 -0
- package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
- package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
- package/frontend/src/features/functions/components/index.ts +5 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
- package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
- package/frontend/src/features/functions/page/FunctionsPage.tsx +160 -17
- package/frontend/src/features/functions/{components/SecretsContent.tsx → page/SecretsPage.tsx} +8 -12
- package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
- package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
- package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +79 -54
- package/frontend/src/features/login/page/LoginPage.tsx +16 -23
- package/frontend/src/features/login/services/partnership.service.ts +65 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
- package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
- package/frontend/src/features/logs/components/index.ts +2 -0
- package/frontend/src/features/logs/helpers.ts +24 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
- package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
- package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +181 -0
- package/frontend/src/features/logs/index.ts +8 -2
- package/frontend/src/features/logs/page/AuditsPage.tsx +91 -38
- package/frontend/src/features/logs/page/LogsPage.tsx +152 -0
- package/frontend/src/features/logs/page/MCPLogsPage.tsx +84 -0
- package/frontend/src/features/logs/services/audit.service.ts +63 -0
- package/frontend/src/features/logs/services/log.service.ts +15 -110
- package/frontend/src/features/logs/services/usage.service.ts +31 -0
- package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
- package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
- package/frontend/src/features/onboard/components/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
- package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
- package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
- package/frontend/src/features/onboard/index.ts +13 -3
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
- package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
- package/frontend/src/features/storage/components/index.ts +12 -0
- package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +41 -115
- package/frontend/src/features/storage/services/storage.service.ts +22 -1
- package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
- package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
- package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +33 -29
- package/frontend/src/index.css +1 -0
- package/frontend/src/lib/analytics/posthog.tsx +27 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
- package/frontend/src/lib/contexts/SocketContext.tsx +5 -6
- package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
- package/frontend/src/lib/hooks/useToast.tsx +6 -2
- package/frontend/src/lib/routing/AppRoutes.tsx +84 -0
- package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
- package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
- package/frontend/src/lib/utils/menuItems.ts +183 -0
- package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
- package/frontend/src/lib/utils/utils.ts +19 -1
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/vite.config.ts +5 -3
- package/functions/server.ts +28 -3
- package/functions/worker-template.js +15 -4
- package/i18n/README.ar.md +130 -0
- package/i18n/README.de.md +130 -0
- package/i18n/README.es.md +154 -0
- package/i18n/README.fr.md +134 -0
- package/i18n/README.hi.md +129 -0
- package/i18n/README.ja.md +174 -0
- package/i18n/README.ko.md +137 -0
- package/i18n/README.pt-BR.md +131 -0
- package/i18n/README.ru.md +129 -0
- package/i18n/README.zh-CN.md +133 -0
- package/openapi/ai.yaml +31 -4
- package/openapi/auth.yaml +827 -146
- package/package.json +16 -7
- package/shared-schemas/package.json +1 -1
- package/shared-schemas/src/ai-api.schema.ts +34 -58
- package/shared-schemas/src/ai.schema.ts +5 -0
- package/shared-schemas/src/auth-api.schema.ts +154 -8
- package/shared-schemas/src/auth.schema.ts +42 -6
- package/shared-schemas/src/cloud-events.schema.ts +57 -0
- package/shared-schemas/src/database-api.schema.ts +3 -3
- package/shared-schemas/src/database.schema.ts +1 -1
- package/shared-schemas/src/index.ts +1 -0
- package/shared-schemas/src/logs-api.schema.ts +7 -1
- package/shared-schemas/src/logs.schema.ts +26 -0
- package/shared-schemas/src/metadata.schema.ts +9 -4
- package/test-gemini.sh +35 -0
- package/test-usage-admin.sh +57 -0
- package/test-usage.sh +50 -0
- package/zeabur/README.md +13 -0
- package/zeabur/template.yml +1032 -0
- package/.github/workflows/deploy-aws.yml +0 -130
- package/backend/src/api/routes/agent.ts +0 -29
- package/backend/src/api/routes/auth.oauth.ts +0 -482
- package/backend/src/api/routes/auth.ts +0 -386
- package/backend/src/api/routes/docs.ts +0 -66
- package/backend/src/api/routes/functions.ts +0 -183
- package/backend/src/api/routes/openapi.ts +0 -82
- package/backend/src/api/routes/usage.ts +0 -96
- package/backend/src/core/ai/client.ts +0 -242
- package/backend/src/core/ai/model.ts +0 -117
- package/backend/src/core/auth/auth.ts +0 -781
- package/backend/src/core/database/table.ts +0 -772
- package/backend/src/core/documentation/agent.ts +0 -689
- package/backend/src/core/documentation/openapi.ts +0 -856
- package/backend/src/core/logs/analytics.ts +0 -76
- package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
- package/backend/src/core/storage/storage.ts +0 -923
- package/backend/src/utils/cloud-token.ts +0 -39
- package/backend/src/utils/helpers.ts +0 -49
- package/backend/src/utils/uuid.ts +0 -9
- package/backend/tests/manual/test-better-auth.sh +0 -303
- package/docker-init/db/logs.sql +0 -9
- package/frontend/README.md +0 -112
- package/frontend/src/components/datagrid/index.tsx +0 -20
- package/frontend/src/components/layout/CloudLayout.tsx +0 -95
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
- package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
- package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
- package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
- package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
- package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
- package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
- package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
- package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
- package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
- package/frontend/src/features/onboard/types.ts +0 -8
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
- /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
- /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +0 -0
- /package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +0 -0
- /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import logger from '@/utils/logger.js';
|
|
3
|
+
import { getApiBaseUrl } from '@/utils/environment.js';
|
|
4
|
+
import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
|
|
5
|
+
import type { GitHubUserInfo, GitHubEmailInfo, OAuthUserData } from '@/types/auth.js';
|
|
6
|
+
import { OAuthProvider } from './base.provider.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* GitHub OAuth Service
|
|
10
|
+
* Handles all GitHub OAuth operations including URL generation, token exchange, and user info retrieval
|
|
11
|
+
*/
|
|
12
|
+
export class GitHubOAuthProvider implements OAuthProvider {
|
|
13
|
+
private static instance: GitHubOAuthProvider;
|
|
14
|
+
|
|
15
|
+
private constructor() {
|
|
16
|
+
// Initialize OAuth helpers if needed
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public static getInstance(): GitHubOAuthProvider {
|
|
20
|
+
if (!GitHubOAuthProvider.instance) {
|
|
21
|
+
GitHubOAuthProvider.instance = new GitHubOAuthProvider();
|
|
22
|
+
}
|
|
23
|
+
return GitHubOAuthProvider.instance;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate GitHub OAuth authorization URL
|
|
28
|
+
*/
|
|
29
|
+
async generateOAuthUrl(state?: string): Promise<string> {
|
|
30
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
31
|
+
const config = await oAuthConfigService.getConfigByProvider('github');
|
|
32
|
+
|
|
33
|
+
if (!config) {
|
|
34
|
+
throw new Error('GitHub OAuth not configured');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
38
|
+
|
|
39
|
+
if (config?.useSharedKey) {
|
|
40
|
+
if (!state) {
|
|
41
|
+
logger.warn('Shared GitHub OAuth called without state parameter');
|
|
42
|
+
throw new Error('State parameter is required for shared GitHub OAuth');
|
|
43
|
+
}
|
|
44
|
+
// Use shared keys if configured
|
|
45
|
+
const cloudBaseUrl = process.env.CLOUD_API_HOST || 'https://api.insforge.dev';
|
|
46
|
+
const redirectUri = `${selfBaseUrl}/api/auth/oauth/shared/callback/${state}`;
|
|
47
|
+
const response = await axios.get(
|
|
48
|
+
`${cloudBaseUrl}/auth/v1/shared/github?redirect_uri=${encodeURIComponent(redirectUri)}`,
|
|
49
|
+
{
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
return response.data.auth_url || response.data.url || '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logger.debug('GitHub OAuth Config (fresh from DB):', {
|
|
59
|
+
clientId: config.clientId ? 'SET' : 'NOT SET',
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const authUrl = new URL('https://github.com/login/oauth/authorize');
|
|
63
|
+
authUrl.searchParams.set('client_id', config.clientId ?? '');
|
|
64
|
+
authUrl.searchParams.set('redirect_uri', `${selfBaseUrl}/api/auth/oauth/github/callback`);
|
|
65
|
+
authUrl.searchParams.set('scope', config.scopes ? config.scopes.join(' ') : 'user:email');
|
|
66
|
+
if (state) {
|
|
67
|
+
authUrl.searchParams.set('state', state);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return authUrl.toString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Exchange GitHub code for access token
|
|
75
|
+
*/
|
|
76
|
+
async exchangeCodeToToken(code: string): Promise<string> {
|
|
77
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
78
|
+
const config = await oAuthConfigService.getConfigByProvider('github');
|
|
79
|
+
|
|
80
|
+
if (!config) {
|
|
81
|
+
throw new Error('GitHub OAuth not configured');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
logger.info('Exchanging GitHub code for token', {
|
|
86
|
+
hasCode: !!code,
|
|
87
|
+
clientId: config.clientId?.substring(0, 10) + '...',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const clientSecret = await oAuthConfigService.getClientSecretByProvider('github');
|
|
91
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
92
|
+
const response = await axios.post(
|
|
93
|
+
'https://github.com/login/oauth/access_token',
|
|
94
|
+
{
|
|
95
|
+
client_id: config.clientId,
|
|
96
|
+
client_secret: clientSecret,
|
|
97
|
+
code,
|
|
98
|
+
redirect_uri: `${selfBaseUrl}/api/auth/oauth/github/callback`,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
headers: {
|
|
102
|
+
Accept: 'application/json',
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!response.data.access_token) {
|
|
108
|
+
throw new Error('Failed to get access token from GitHub');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return response.data.access_token;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
114
|
+
logger.error('GitHub token exchange failed', {
|
|
115
|
+
status: error.response.status,
|
|
116
|
+
error: error.response.data,
|
|
117
|
+
});
|
|
118
|
+
throw new Error(`GitHub OAuth error: ${JSON.stringify(error.response.data)}`);
|
|
119
|
+
}
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get GitHub user info
|
|
126
|
+
*/
|
|
127
|
+
async getUserInfo(accessToken: string): Promise<GitHubUserInfo> {
|
|
128
|
+
try {
|
|
129
|
+
const userResponse = await axios.get('https://api.github.com/user', {
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `Bearer ${accessToken}`,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// GitHub doesn't always return email in user endpoint
|
|
136
|
+
let email = userResponse.data.email;
|
|
137
|
+
|
|
138
|
+
if (!email) {
|
|
139
|
+
const emailResponse = await axios.get('https://api.github.com/user/emails', {
|
|
140
|
+
headers: {
|
|
141
|
+
Authorization: `Bearer ${accessToken}`,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const primaryEmail = emailResponse.data.find((e: GitHubEmailInfo) => e.primary);
|
|
146
|
+
email = primaryEmail ? primaryEmail.email : emailResponse.data[0]?.email;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
id: userResponse.data.id,
|
|
151
|
+
login: userResponse.data.login,
|
|
152
|
+
name: userResponse.data.name,
|
|
153
|
+
email: email || `${userResponse.data.login}@users.noreply.github.com`,
|
|
154
|
+
avatar_url: userResponse.data.avatar_url,
|
|
155
|
+
};
|
|
156
|
+
} catch (error) {
|
|
157
|
+
logger.error('GitHub user info retrieval failed:', error);
|
|
158
|
+
throw new Error(`Failed to get GitHub user info: ${error}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Handle GitHub OAuth callback
|
|
164
|
+
*/
|
|
165
|
+
async handleCallback(payload: { code?: string; token?: string }): Promise<OAuthUserData> {
|
|
166
|
+
if (!payload.code) {
|
|
167
|
+
throw new Error('No authorization code provided');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const accessToken = await this.exchangeCodeToToken(payload.code);
|
|
171
|
+
const githubUserInfo = await this.getUserInfo(accessToken);
|
|
172
|
+
|
|
173
|
+
// Transform GitHub user info to generic format
|
|
174
|
+
const userName = githubUserInfo.name || githubUserInfo.login;
|
|
175
|
+
const email = githubUserInfo.email || `${githubUserInfo.login}@users.noreply.github.com`;
|
|
176
|
+
return {
|
|
177
|
+
provider: 'github',
|
|
178
|
+
providerId: githubUserInfo.id.toString(),
|
|
179
|
+
email,
|
|
180
|
+
userName,
|
|
181
|
+
avatarUrl: githubUserInfo.avatar_url || '',
|
|
182
|
+
identityData: githubUserInfo,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handle shared callback payload transformation
|
|
188
|
+
*/
|
|
189
|
+
handleSharedCallback(payloadData: Record<string, unknown>): OAuthUserData {
|
|
190
|
+
const providerId = String(payloadData.providerId ?? '');
|
|
191
|
+
const name = String(payloadData.name ?? '');
|
|
192
|
+
const login = String(payloadData.login ?? '');
|
|
193
|
+
const emailField = String(payloadData.email ?? '');
|
|
194
|
+
const avatar = String(payloadData.avatar ?? '');
|
|
195
|
+
|
|
196
|
+
const userName = name || login;
|
|
197
|
+
const email = emailField || `${login}@users.noreply.github.com`;
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
provider: 'github',
|
|
201
|
+
providerId,
|
|
202
|
+
email,
|
|
203
|
+
userName,
|
|
204
|
+
avatarUrl: avatar,
|
|
205
|
+
identityData: payloadData,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
3
|
+
import logger from '@/utils/logger.js';
|
|
4
|
+
import { getApiBaseUrl } from '@/utils/environment.js';
|
|
5
|
+
import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
|
|
6
|
+
import type { GoogleUserInfo, OAuthUserData } from '@/types/auth.js';
|
|
7
|
+
import { OAuthProvider } from './base.provider.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Google OAuth Service
|
|
11
|
+
* Handles all Google OAuth operations including URL generation, token exchange, and user info verification
|
|
12
|
+
*/
|
|
13
|
+
export class GoogleOAuthProvider implements OAuthProvider {
|
|
14
|
+
private static instance: GoogleOAuthProvider;
|
|
15
|
+
private processedCodes: Set<string>;
|
|
16
|
+
private tokenCache: Map<string, { access_token: string; id_token: string }>;
|
|
17
|
+
|
|
18
|
+
private constructor() {
|
|
19
|
+
// Initialize OAuth helpers
|
|
20
|
+
this.processedCodes = new Set();
|
|
21
|
+
this.tokenCache = new Map();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public static getInstance(): GoogleOAuthProvider {
|
|
25
|
+
if (!GoogleOAuthProvider.instance) {
|
|
26
|
+
GoogleOAuthProvider.instance = new GoogleOAuthProvider();
|
|
27
|
+
}
|
|
28
|
+
return GoogleOAuthProvider.instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate Google OAuth authorization URL
|
|
33
|
+
*/
|
|
34
|
+
async generateOAuthUrl(state?: string): Promise<string> {
|
|
35
|
+
const oauthConfigService = OAuthConfigService.getInstance();
|
|
36
|
+
const config = await oauthConfigService.getConfigByProvider('google');
|
|
37
|
+
|
|
38
|
+
if (!config) {
|
|
39
|
+
throw new Error('Google OAuth not configured');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
43
|
+
|
|
44
|
+
if (config?.useSharedKey) {
|
|
45
|
+
if (!state) {
|
|
46
|
+
logger.warn('Shared Google OAuth called without state parameter');
|
|
47
|
+
throw new Error('State parameter is required for shared Google OAuth');
|
|
48
|
+
}
|
|
49
|
+
// Use shared keys if configured
|
|
50
|
+
const cloudBaseUrl = process.env.CLOUD_API_HOST || 'https://api.insforge.dev';
|
|
51
|
+
const redirectUri = `${selfBaseUrl}/api/auth/oauth/shared/callback/${state}`;
|
|
52
|
+
const response = await axios.get(
|
|
53
|
+
`${cloudBaseUrl}/auth/v1/shared/google?redirect_uri=${encodeURIComponent(redirectUri)}`,
|
|
54
|
+
{
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
return response.data.auth_url || response.data.url || '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
logger.debug('Google OAuth Config (fresh from DB):', {
|
|
64
|
+
clientId: config.clientId ? 'SET' : 'NOT SET',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
|
|
68
|
+
authUrl.searchParams.set('client_id', config.clientId ?? '');
|
|
69
|
+
authUrl.searchParams.set('redirect_uri', `${selfBaseUrl}/api/auth/oauth/google/callback`);
|
|
70
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
71
|
+
authUrl.searchParams.set(
|
|
72
|
+
'scope',
|
|
73
|
+
config.scopes ? config.scopes.join(' ') : 'openid email profile'
|
|
74
|
+
);
|
|
75
|
+
authUrl.searchParams.set('access_type', 'offline');
|
|
76
|
+
if (state) {
|
|
77
|
+
authUrl.searchParams.set('state', state);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return authUrl.toString();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Exchange Google code for tokens
|
|
85
|
+
*/
|
|
86
|
+
async exchangeCodeToToken(code: string): Promise<{ access_token: string; id_token: string }> {
|
|
87
|
+
// Check cache first
|
|
88
|
+
if (this.processedCodes.has(code)) {
|
|
89
|
+
const cachedTokens = this.tokenCache.get(code);
|
|
90
|
+
if (cachedTokens) {
|
|
91
|
+
logger.debug('Returning cached tokens for already processed code.');
|
|
92
|
+
return cachedTokens;
|
|
93
|
+
}
|
|
94
|
+
throw new Error('Authorization code is currently being processed.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const oauthConfigService = OAuthConfigService.getInstance();
|
|
98
|
+
const config = await oauthConfigService.getConfigByProvider('google');
|
|
99
|
+
|
|
100
|
+
if (!config) {
|
|
101
|
+
throw new Error('Google OAuth not configured');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
this.processedCodes.add(code);
|
|
106
|
+
|
|
107
|
+
logger.info('Exchanging Google code for tokens', {
|
|
108
|
+
hasCode: !!code,
|
|
109
|
+
clientId: config.clientId?.substring(0, 10) + '...',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const clientSecret = await oauthConfigService.getClientSecretByProvider('google');
|
|
113
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
114
|
+
const response = await axios.post('https://oauth2.googleapis.com/token', {
|
|
115
|
+
code,
|
|
116
|
+
client_id: config.clientId,
|
|
117
|
+
client_secret: clientSecret,
|
|
118
|
+
redirect_uri: `${selfBaseUrl}/api/auth/oauth/google/callback`,
|
|
119
|
+
grant_type: 'authorization_code',
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!response.data.access_token || !response.data.id_token) {
|
|
123
|
+
throw new Error('Failed to get tokens from Google');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = {
|
|
127
|
+
access_token: response.data.access_token,
|
|
128
|
+
id_token: response.data.id_token,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Cache the successful token exchange
|
|
132
|
+
this.tokenCache.set(code, result);
|
|
133
|
+
|
|
134
|
+
// Set a timeout to clear the code and cache to prevent memory leaks
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
this.processedCodes.delete(code);
|
|
137
|
+
this.tokenCache.delete(code);
|
|
138
|
+
}, 60000); // 1 minute timeout
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// If the request fails, remove the code immediately to allow for a retry
|
|
143
|
+
this.processedCodes.delete(code);
|
|
144
|
+
|
|
145
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
146
|
+
logger.error('Google token exchange failed', {
|
|
147
|
+
status: error.response.status,
|
|
148
|
+
error: error.response.data,
|
|
149
|
+
});
|
|
150
|
+
throw new Error(`Google OAuth error: ${JSON.stringify(error.response.data)}`);
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Verify Google ID token and get user info
|
|
158
|
+
*/
|
|
159
|
+
async verifyToken(idToken: string): Promise<GoogleUserInfo> {
|
|
160
|
+
const oauthConfigService = OAuthConfigService.getInstance();
|
|
161
|
+
const config = await oauthConfigService.getConfigByProvider('google');
|
|
162
|
+
|
|
163
|
+
if (!config) {
|
|
164
|
+
throw new Error('Google OAuth not configured');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const clientSecret = await oauthConfigService.getClientSecretByProvider('google');
|
|
168
|
+
|
|
169
|
+
if (!clientSecret) {
|
|
170
|
+
throw new Error('Google Client Secret not configured.');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create OAuth2Client with fresh config
|
|
174
|
+
const googleClient = new OAuth2Client(config.clientId, clientSecret, config.redirectUri);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
// Properly verify the ID token with Google's servers
|
|
178
|
+
const ticket = await googleClient.verifyIdToken({
|
|
179
|
+
idToken,
|
|
180
|
+
audience: config.clientId,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const payload = ticket.getPayload();
|
|
184
|
+
if (!payload) {
|
|
185
|
+
throw new Error('Invalid Google token payload');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
sub: payload.sub,
|
|
190
|
+
email: payload.email || '',
|
|
191
|
+
email_verified: payload.email_verified || false,
|
|
192
|
+
name: payload.name || '',
|
|
193
|
+
picture: payload.picture || '',
|
|
194
|
+
given_name: payload.given_name || '',
|
|
195
|
+
family_name: payload.family_name || '',
|
|
196
|
+
locale: payload.locale || '',
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
logger.error('Google token verification failed:', error);
|
|
200
|
+
throw new Error(`Google token verification failed: ${error}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Handle Google OAuth callback
|
|
206
|
+
*/
|
|
207
|
+
async handleCallback(payload: { code?: string; token?: string }): Promise<OAuthUserData> {
|
|
208
|
+
let googleUserInfo: GoogleUserInfo;
|
|
209
|
+
|
|
210
|
+
if (payload.token) {
|
|
211
|
+
googleUserInfo = await this.verifyToken(payload.token);
|
|
212
|
+
} else if (payload.code) {
|
|
213
|
+
const tokens = await this.exchangeCodeToToken(payload.code);
|
|
214
|
+
googleUserInfo = await this.verifyToken(tokens.id_token);
|
|
215
|
+
} else {
|
|
216
|
+
throw new Error('No authorization code or token provided');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Transform Google user info to generic format
|
|
220
|
+
const userName = googleUserInfo.name || googleUserInfo.email.split('@')[0];
|
|
221
|
+
return {
|
|
222
|
+
provider: 'google',
|
|
223
|
+
providerId: googleUserInfo.sub,
|
|
224
|
+
email: googleUserInfo.email,
|
|
225
|
+
userName,
|
|
226
|
+
avatarUrl: googleUserInfo.picture || '',
|
|
227
|
+
identityData: googleUserInfo,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Handle shared callback payload transformation
|
|
233
|
+
*/
|
|
234
|
+
handleSharedCallback(payloadData: Record<string, unknown>): OAuthUserData {
|
|
235
|
+
const providerId = String(payloadData.providerId ?? '');
|
|
236
|
+
const email = String(payloadData.email ?? '');
|
|
237
|
+
const name = String(payloadData.name ?? '');
|
|
238
|
+
const avatar = String(payloadData.avatar ?? '');
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
provider: 'google',
|
|
242
|
+
providerId,
|
|
243
|
+
email,
|
|
244
|
+
userName: name || email.split('@')[0],
|
|
245
|
+
avatarUrl: avatar,
|
|
246
|
+
identityData: payloadData,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { GoogleOAuthProvider } from './google.provider.js';
|
|
2
|
+
export { GitHubOAuthProvider } from './github.provider.js';
|
|
3
|
+
export { DiscordOAuthProvider } from './discord.provider.js';
|
|
4
|
+
export { LinkedInOAuthProvider } from './linkedin.provider.js';
|
|
5
|
+
export { FacebookOAuthProvider } from './facebook.provider.js';
|
|
6
|
+
export { MicrosoftOAuthProvider } from './microsoft.provider.js';
|
|
7
|
+
export { XOAuthProvider } from './x.provider.js';
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import logger from '@/utils/logger.js';
|
|
3
|
+
import { getApiBaseUrl } from '@/utils/environment.js';
|
|
4
|
+
import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
|
|
5
|
+
import { OAuthProvider } from './base.provider.js';
|
|
6
|
+
import type { LinkedInUserInfo, OAuthUserData } from '@/types/auth.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* LinkedIn OAuth Service
|
|
10
|
+
* Handles all LinkedIn OAuth operations including URL generation, token exchange, and user info verification
|
|
11
|
+
*/
|
|
12
|
+
export class LinkedInOAuthProvider implements OAuthProvider {
|
|
13
|
+
private static instance: LinkedInOAuthProvider;
|
|
14
|
+
private processedCodes: Set<string>;
|
|
15
|
+
private tokenCache: Map<string, { access_token: string; id_token: string }>;
|
|
16
|
+
|
|
17
|
+
private constructor() {
|
|
18
|
+
// Initialize OAuth helpers
|
|
19
|
+
this.processedCodes = new Set();
|
|
20
|
+
this.tokenCache = new Map();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public static getInstance(): LinkedInOAuthProvider {
|
|
24
|
+
if (!LinkedInOAuthProvider.instance) {
|
|
25
|
+
LinkedInOAuthProvider.instance = new LinkedInOAuthProvider();
|
|
26
|
+
}
|
|
27
|
+
return LinkedInOAuthProvider.instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate LinkedIn OAuth authorization URL
|
|
32
|
+
*/
|
|
33
|
+
async generateOAuthUrl(state?: string): Promise<string> {
|
|
34
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
35
|
+
const config = await oAuthConfigService.getConfigByProvider('linkedin');
|
|
36
|
+
|
|
37
|
+
if (!config) {
|
|
38
|
+
throw new Error('LinkedIn OAuth not configured');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
42
|
+
|
|
43
|
+
if (config?.useSharedKey) {
|
|
44
|
+
if (!state) {
|
|
45
|
+
logger.warn('Shared LinkedIn OAuth called without state parameter');
|
|
46
|
+
throw new Error('State parameter is required for shared LinkedIn OAuth');
|
|
47
|
+
}
|
|
48
|
+
const cloudBaseUrl = process.env.CLOUD_API_HOST || 'https://api.insforge.dev';
|
|
49
|
+
const redirectUri = `${selfBaseUrl}/api/auth/oauth/shared/callback/${state}`;
|
|
50
|
+
const response = await axios.get(
|
|
51
|
+
`${cloudBaseUrl}/auth/v1/shared/linkedin?redirect_uri=${encodeURIComponent(redirectUri)}`,
|
|
52
|
+
{
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
return response.data.auth_url || response.data.url || '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logger.debug('LinkedIn OAuth Config (fresh from DB):', {
|
|
62
|
+
clientId: config.clientId ? 'SET' : 'NOT SET',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const authUrl = new URL('https://www.linkedin.com/oauth/v2/authorization');
|
|
66
|
+
authUrl.searchParams.set('client_id', config.clientId ?? '');
|
|
67
|
+
authUrl.searchParams.set('redirect_uri', `${selfBaseUrl}/api/auth/oauth/linkedin/callback`);
|
|
68
|
+
authUrl.searchParams.set('response_type', 'code');
|
|
69
|
+
authUrl.searchParams.set(
|
|
70
|
+
'scope',
|
|
71
|
+
config.scopes ? config.scopes.join(' ') : 'openid profile email'
|
|
72
|
+
);
|
|
73
|
+
if (state) {
|
|
74
|
+
authUrl.searchParams.set('state', state);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return authUrl.toString();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Exchange LinkedIn code for tokens
|
|
82
|
+
*/
|
|
83
|
+
async exchangeCodeToToken(code: string): Promise<{ access_token: string; id_token: string }> {
|
|
84
|
+
if (this.processedCodes.has(code)) {
|
|
85
|
+
const cachedTokens = this.tokenCache.get(code);
|
|
86
|
+
if (cachedTokens) {
|
|
87
|
+
logger.debug('Returning cached tokens for already processed code.');
|
|
88
|
+
return cachedTokens;
|
|
89
|
+
}
|
|
90
|
+
throw new Error('Authorization code is currently being processed.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
94
|
+
const config = await oAuthConfigService.getConfigByProvider('linkedin');
|
|
95
|
+
|
|
96
|
+
if (!config) {
|
|
97
|
+
throw new Error('LinkedIn OAuth not configured');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
this.processedCodes.add(code);
|
|
102
|
+
|
|
103
|
+
logger.info('Exchanging LinkedIn code for tokens', {
|
|
104
|
+
hasCode: !!code,
|
|
105
|
+
clientId: config.clientId?.substring(0, 10) + '...',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const clientSecret = await oAuthConfigService.getClientSecretByProvider('linkedin');
|
|
109
|
+
const selfBaseUrl = getApiBaseUrl();
|
|
110
|
+
const response = await axios.post(
|
|
111
|
+
'https://www.linkedin.com/oauth/v2/accessToken',
|
|
112
|
+
new URLSearchParams({
|
|
113
|
+
code,
|
|
114
|
+
client_id: config.clientId ?? '',
|
|
115
|
+
client_secret: clientSecret ?? '',
|
|
116
|
+
redirect_uri: `${selfBaseUrl}/api/auth/oauth/linkedin/callback`,
|
|
117
|
+
grant_type: 'authorization_code',
|
|
118
|
+
}),
|
|
119
|
+
{
|
|
120
|
+
headers: {
|
|
121
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
122
|
+
},
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (!response.data.access_token || !response.data.id_token) {
|
|
127
|
+
throw new Error('Failed to get tokens from LinkedIn');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = {
|
|
131
|
+
access_token: response.data.access_token,
|
|
132
|
+
id_token: response.data.id_token,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
this.tokenCache.set(code, result);
|
|
136
|
+
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
this.processedCodes.delete(code);
|
|
139
|
+
this.tokenCache.delete(code);
|
|
140
|
+
}, 60000);
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
this.processedCodes.delete(code);
|
|
145
|
+
|
|
146
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
147
|
+
logger.error('LinkedIn token exchange failed', {
|
|
148
|
+
status: error.response.status,
|
|
149
|
+
error: error.response.data,
|
|
150
|
+
});
|
|
151
|
+
throw new Error(`LinkedIn OAuth error: ${JSON.stringify(error.response.data)}`);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Verify LinkedIn ID token and get user info
|
|
159
|
+
*/
|
|
160
|
+
async verifyToken(idToken: string): Promise<LinkedInUserInfo> {
|
|
161
|
+
const oAuthConfigService = OAuthConfigService.getInstance();
|
|
162
|
+
const config = await oAuthConfigService.getConfigByProvider('linkedin');
|
|
163
|
+
|
|
164
|
+
if (!config) {
|
|
165
|
+
throw new Error('LinkedIn OAuth not configured');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const { createRemoteJWKSet, jwtVerify } = await import('jose');
|
|
170
|
+
const JWKS = createRemoteJWKSet(new URL('https://www.linkedin.com/oauth/openid/jwks'));
|
|
171
|
+
|
|
172
|
+
const { payload } = await jwtVerify(idToken, JWKS, {
|
|
173
|
+
issuer: 'https://www.linkedin.com/oauth',
|
|
174
|
+
audience: config.clientId,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
sub: String(payload.sub),
|
|
179
|
+
email: (payload.email as string) || '',
|
|
180
|
+
email_verified: Boolean(payload.email_verified),
|
|
181
|
+
name: (payload.name as string) || '',
|
|
182
|
+
picture: (payload.picture as string) || '',
|
|
183
|
+
given_name: (payload.given_name as string) || '',
|
|
184
|
+
family_name: (payload.family_name as string) || '',
|
|
185
|
+
locale: (payload.locale as string) || '',
|
|
186
|
+
};
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.error('LinkedIn token verification failed:', error);
|
|
189
|
+
throw new Error('LinkedIn token verification failed');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Handle LinkedIn OAuth callback
|
|
195
|
+
*/
|
|
196
|
+
async handleCallback(payload: { code?: string; token?: string }): Promise<OAuthUserData> {
|
|
197
|
+
let linkedinUserInfo: LinkedInUserInfo;
|
|
198
|
+
|
|
199
|
+
if (payload.token) {
|
|
200
|
+
linkedinUserInfo = await this.verifyToken(payload.token);
|
|
201
|
+
} else if (payload.code) {
|
|
202
|
+
const tokens = await this.exchangeCodeToToken(payload.code);
|
|
203
|
+
linkedinUserInfo = await this.verifyToken(tokens.id_token);
|
|
204
|
+
} else {
|
|
205
|
+
throw new Error('No authorization code or token provided');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Transform LinkedIn user info to generic format
|
|
209
|
+
const userName = linkedinUserInfo.name || linkedinUserInfo.email.split('@')[0];
|
|
210
|
+
return {
|
|
211
|
+
provider: 'linkedin',
|
|
212
|
+
providerId: linkedinUserInfo.sub,
|
|
213
|
+
email: linkedinUserInfo.email,
|
|
214
|
+
userName,
|
|
215
|
+
avatarUrl: linkedinUserInfo.picture || '',
|
|
216
|
+
identityData: linkedinUserInfo,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Handle shared callback payload transformation
|
|
222
|
+
*/
|
|
223
|
+
handleSharedCallback(payloadData: Record<string, unknown>): OAuthUserData {
|
|
224
|
+
const providerId = String(payloadData.providerId ?? '');
|
|
225
|
+
const email = String(payloadData.email ?? '');
|
|
226
|
+
const name = String(payloadData.name ?? '');
|
|
227
|
+
const avatar = String(payloadData.avatar ?? '');
|
|
228
|
+
|
|
229
|
+
const userName = name || email.split('@')[0];
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
provider: 'linkedin',
|
|
233
|
+
providerId,
|
|
234
|
+
email,
|
|
235
|
+
userName,
|
|
236
|
+
avatarUrl: avatar,
|
|
237
|
+
identityData: payloadData,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|