insforge 1.2.10 → 1.4.8

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