insforge 0.3.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (635) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.dockerignore +60 -57
  3. package/.env.example +84 -49
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -83
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -79
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +66 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -0
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +44 -3
  15. package/CLAUDE_PLUGIN.md +104 -0
  16. package/CODE_OF_CONDUCT.md +128 -0
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -27
  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 -134
  23. package/assets/Dark.svg +23 -23
  24. package/assets/mcpInstallv2.png +0 -0
  25. package/assets/sampleResponse.png +0 -0
  26. package/auth/index.html +13 -0
  27. package/auth/package.json +28 -0
  28. package/auth/public/favicon.ico +0 -0
  29. package/auth/src/App.tsx +33 -0
  30. package/auth/src/components/ErrorCard.tsx +37 -0
  31. package/auth/src/components/Layout.tsx +13 -0
  32. package/auth/src/index.css +19 -0
  33. package/auth/src/lib/broadcastService.ts +117 -0
  34. package/auth/src/lib/utils.ts +11 -0
  35. package/auth/src/main.tsx +22 -0
  36. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  37. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  38. package/auth/src/pages/SignInPage.tsx +60 -0
  39. package/auth/src/pages/SignUpPage.tsx +60 -0
  40. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  41. package/auth/src/vite-env.d.ts +10 -0
  42. package/auth/tsconfig.json +32 -0
  43. package/auth/tsconfig.node.json +11 -0
  44. package/auth/vite.config.ts +25 -0
  45. package/backend/package.json +78 -75
  46. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  47. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  48. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +22 -26
  49. package/backend/src/api/routes/auth/index.routes.ts +667 -0
  50. package/backend/src/api/routes/auth/oauth.routes.ts +473 -0
  51. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +128 -65
  52. package/backend/src/api/routes/database/index.routes.ts +90 -0
  53. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +26 -12
  54. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +6 -23
  55. package/backend/src/api/routes/docs/index.routes.ts +75 -0
  56. package/backend/src/api/routes/email/index.routes.ts +35 -0
  57. package/backend/src/api/routes/functions/index.routes.ts +194 -0
  58. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  59. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +33 -31
  60. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  61. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  62. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  63. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  64. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  65. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +48 -61
  66. package/backend/src/api/routes/usage/index.routes.ts +91 -0
  67. package/backend/src/infra/config/app.config.ts +51 -0
  68. package/backend/src/infra/database/database.manager.ts +182 -0
  69. package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +141 -141
  70. package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +40 -40
  71. package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +29 -29
  72. package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +55 -55
  73. package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +23 -23
  74. package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +29 -29
  75. package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +24 -24
  76. package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +1 -1
  77. package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +76 -76
  78. package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +23 -23
  79. package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +93 -93
  80. package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +15 -15
  81. package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +7 -7
  82. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  83. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  84. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  85. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  86. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  87. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  88. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  89. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  90. package/backend/src/infra/security/token.manager.ts +219 -0
  91. package/backend/src/infra/socket/socket.manager.ts +522 -0
  92. package/backend/src/providers/ai/openrouter.provider.ts +380 -0
  93. package/backend/src/providers/email/base.provider.ts +38 -0
  94. package/backend/src/providers/email/cloud.provider.ts +271 -0
  95. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  96. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  97. package/backend/src/providers/logs/local.provider.ts +185 -0
  98. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  99. package/backend/src/providers/oauth/base.provider.ts +29 -0
  100. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  101. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  102. package/backend/src/providers/oauth/github.provider.ts +208 -0
  103. package/backend/src/providers/oauth/google.provider.ts +249 -0
  104. package/backend/src/providers/oauth/index.ts +8 -0
  105. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  106. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  107. package/backend/src/providers/oauth/x.provider.ts +202 -0
  108. package/backend/src/providers/storage/base.provider.ts +29 -0
  109. package/backend/src/providers/storage/local.provider.ts +103 -0
  110. package/backend/src/providers/storage/s3.provider.ts +313 -0
  111. package/backend/src/server.ts +317 -288
  112. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  113. package/backend/src/services/ai/ai-model.service.ts +60 -0
  114. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  115. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  116. package/backend/src/services/ai/helpers.ts +64 -0
  117. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  118. package/backend/src/services/ai/index.ts +13 -0
  119. package/backend/src/services/auth/auth-config.service.ts +250 -0
  120. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  121. package/backend/src/services/auth/auth.service.ts +1150 -0
  122. package/backend/src/services/auth/index.ts +4 -0
  123. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  124. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  125. package/backend/src/services/database/database-table.service.ts +802 -0
  126. package/backend/src/services/database/database.service.ts +127 -0
  127. package/backend/src/services/email/email.service.ts +73 -0
  128. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  129. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  130. package/backend/src/services/logs/log.service.ts +73 -0
  131. package/backend/src/services/realtime/index.ts +3 -0
  132. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  133. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  134. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  135. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  136. package/backend/src/services/storage/storage.service.ts +617 -0
  137. package/backend/src/services/usage/usage.service.ts +149 -0
  138. package/backend/src/types/auth.ts +77 -2
  139. package/backend/src/types/email.ts +8 -0
  140. package/backend/src/types/error-constants.ts +4 -0
  141. package/backend/src/types/logs.ts +0 -29
  142. package/backend/src/types/realtime.ts +18 -0
  143. package/backend/src/{core/socket/types.ts → types/socket.ts} +11 -36
  144. package/backend/src/utils/cookies.ts +35 -0
  145. package/backend/src/utils/environment.ts +9 -3
  146. package/backend/src/utils/logger.ts +20 -2
  147. package/backend/src/utils/s3-config-loader.ts +64 -0
  148. package/backend/src/utils/seed.ts +301 -205
  149. package/backend/src/utils/sql-parser.ts +91 -1
  150. package/backend/src/utils/utils.ts +114 -0
  151. package/backend/src/utils/validations.ts +40 -4
  152. package/backend/tests/README.md +133 -133
  153. package/backend/tests/cleanup-all-test-data.sh +230 -230
  154. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  155. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  156. package/backend/tests/local/test-ai-config.sh +129 -0
  157. package/backend/tests/local/test-ai-usage.sh +80 -0
  158. package/backend/tests/local/test-auth-router.sh +143 -143
  159. package/backend/tests/local/test-database-router.sh +222 -222
  160. package/backend/tests/local/test-e2e.sh +240 -240
  161. package/backend/tests/local/test-fk-errors.sh +96 -96
  162. package/backend/tests/local/test-functions.sh +123 -0
  163. package/backend/tests/local/test-id-field.sh +200 -200
  164. package/backend/tests/local/test-logs.sh +132 -0
  165. package/backend/tests/local/test-public-bucket.sh +264 -264
  166. package/backend/tests/local/test-secrets.sh +249 -247
  167. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  168. package/backend/tests/local/test-traditional-rest.sh +208 -208
  169. package/backend/tests/manual/README.md +50 -50
  170. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  171. package/backend/tests/manual/seed-large-table.sql +100 -100
  172. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  173. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  174. package/backend/tests/manual/test-database-advance.sh +296 -296
  175. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  176. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  177. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  178. package/backend/tests/manual/test-universal-storage.sh +263 -263
  179. package/backend/tests/manual/test-users.sql +17 -17
  180. package/backend/tests/run-all-tests.sh +139 -139
  181. package/backend/tests/setup.ts +0 -0
  182. package/backend/tests/test-config.sh +338 -302
  183. package/backend/tests/unit/analyze-query.test.ts +697 -0
  184. package/backend/tests/unit/cloud-token.test.ts +48 -0
  185. package/backend/tests/unit/constant.test.ts +8 -0
  186. package/backend/tests/unit/email.test.ts +372 -0
  187. package/backend/tests/unit/environment.test.ts +59 -0
  188. package/backend/tests/unit/helpers.test.ts +63 -0
  189. package/backend/tests/unit/logger.test.ts +22 -0
  190. package/backend/tests/unit/rate-limit.test.ts +154 -0
  191. package/backend/tests/unit/response.test.ts +58 -0
  192. package/backend/tests/unit/sql-parser.test.ts +74 -0
  193. package/backend/tests/unit/uuid.test.ts +21 -0
  194. package/backend/tests/unit/validations.test.ts +80 -0
  195. package/backend/tsconfig.json +22 -22
  196. package/backend/vitest.config.ts +11 -0
  197. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  198. package/claude-plugin/README.md +133 -0
  199. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  200. package/docker-compose.prod.yml +204 -144
  201. package/docker-compose.yml +232 -167
  202. package/docker-init/db/db-init.sql +97 -125
  203. package/docker-init/db/jwt.sql +5 -5
  204. package/docker-init/db/postgresql.conf +16 -16
  205. package/docker-init/logs/vector.yml +236 -0
  206. package/docs/README.md +44 -0
  207. package/docs/agent-docs/real-time.md +269 -0
  208. package/docs/changelog.mdx +119 -0
  209. package/docs/core-concepts/ai/architecture.mdx +373 -0
  210. package/docs/core-concepts/ai/sdk.mdx +213 -0
  211. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  212. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  213. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  214. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  215. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  216. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  217. package/docs/core-concepts/database/architecture.mdx +256 -0
  218. package/docs/core-concepts/database/sdk.mdx +382 -0
  219. package/docs/core-concepts/email/architecture.mdx +101 -0
  220. package/docs/core-concepts/email/sdk.mdx +53 -0
  221. package/docs/core-concepts/functions/architecture.mdx +105 -0
  222. package/docs/core-concepts/functions/sdk.mdx +184 -0
  223. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  224. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  225. package/docs/core-concepts/storage/architecture.mdx +243 -0
  226. package/docs/core-concepts/storage/sdk.mdx +253 -0
  227. package/docs/deployment/README.md +94 -0
  228. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  229. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  230. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  231. package/docs/deployment/deploy-to-render.md +441 -0
  232. package/docs/deprecated/insforge-auth-api.md +214 -214
  233. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  234. package/docs/deprecated/insforge-db-api.md +358 -358
  235. package/docs/deprecated/insforge-db-sdk.md +139 -139
  236. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  237. package/docs/deprecated/insforge-debug.md +64 -64
  238. package/docs/deprecated/insforge-instructions.md +123 -123
  239. package/docs/deprecated/insforge-project.md +117 -117
  240. package/docs/deprecated/insforge-storage-api.md +278 -278
  241. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  242. package/docs/docs.json +232 -0
  243. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  244. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  245. package/docs/examples/framework-guides/react.mdx +165 -0
  246. package/docs/examples/framework-guides/svelte.mdx +153 -0
  247. package/docs/examples/framework-guides/vue.mdx +159 -0
  248. package/docs/examples/overview.mdx +67 -0
  249. package/docs/favicon.svg +19 -0
  250. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  251. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  252. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  253. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  254. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  255. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  256. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  257. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  258. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  259. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  260. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  261. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  262. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  263. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  264. package/docs/images/checks-passed.png +0 -0
  265. package/docs/images/dashboard-connect-expanded.png +0 -0
  266. package/docs/images/dashboard-connect.png +0 -0
  267. package/docs/images/hero-dark.png +0 -0
  268. package/docs/images/hero-light.png +0 -0
  269. package/docs/images/icons/ai.svg +4 -0
  270. package/docs/images/icons/auth.svg +1 -0
  271. package/docs/images/icons/database.svg +1 -0
  272. package/docs/images/icons/function.svg +1 -0
  273. package/docs/images/icons/storage.svg +1 -0
  274. package/docs/images/logos/nextjs.svg +4 -0
  275. package/docs/images/logos/nuxt.svg +4 -0
  276. package/docs/images/logos/react.svg +5 -0
  277. package/docs/images/logos/svelte.svg +4 -0
  278. package/docs/images/logos/vue.svg +5 -0
  279. package/docs/images/mcp-install.png +0 -0
  280. package/docs/images/onboarding-mcp.png +0 -0
  281. package/docs/insforge-instructions-sdk.md +89 -407
  282. package/docs/introduction.mdx +45 -0
  283. package/docs/logo/dark.svg +22 -0
  284. package/docs/logo/light.svg +20 -0
  285. package/docs/partnership.mdx +652 -0
  286. package/docs/quickstart.mdx +83 -0
  287. package/docs/showcase/2048-arena.png +0 -0
  288. package/docs/showcase/framegen-cloud.png +0 -0
  289. package/docs/showcase/line-connect-race.png +0 -0
  290. package/docs/showcase/moment-vibe.png +0 -0
  291. package/docs/showcase/national-flags.png +0 -0
  292. package/docs/showcase/pokemon-vibe.png +0 -0
  293. package/docs/showcase/pure-browse-buy.png +0 -0
  294. package/docs/showcase.mdx +52 -0
  295. package/docs/snippets/sdk-installation.mdx +22 -0
  296. package/docs/snippets/service-icons.mdx +27 -0
  297. package/eslint.config.js +10 -3
  298. package/examples/oauth/frontend-oauth-example.html +250 -250
  299. package/examples/response-examples.md +443 -443
  300. package/frontend/components.json +17 -17
  301. package/frontend/package.json +69 -63
  302. package/frontend/src/App.tsx +13 -82
  303. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  304. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  305. package/frontend/src/assets/icons/checked.svg +3 -3
  306. package/frontend/src/assets/icons/connected.svg +3 -0
  307. package/frontend/src/assets/icons/error.svg +3 -3
  308. package/frontend/src/assets/icons/loader.svg +9 -0
  309. package/frontend/src/assets/icons/pencil.svg +4 -4
  310. package/frontend/src/assets/icons/refresh.svg +4 -4
  311. package/frontend/src/assets/icons/step_active.svg +3 -3
  312. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  313. package/frontend/src/assets/icons/warning.svg +3 -3
  314. package/frontend/src/assets/logos/apple.svg +4 -0
  315. package/frontend/src/assets/logos/claude_code.svg +3 -3
  316. package/frontend/src/assets/logos/cline.svg +6 -6
  317. package/frontend/src/assets/logos/cursor.svg +20 -20
  318. package/frontend/src/assets/logos/discord.svg +8 -8
  319. package/frontend/src/assets/logos/facebook.svg +3 -0
  320. package/frontend/src/assets/logos/gemini.svg +19 -19
  321. package/frontend/src/assets/logos/github.svg +5 -5
  322. package/frontend/src/assets/logos/google.svg +13 -13
  323. package/frontend/src/assets/logos/grok.svg +10 -10
  324. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  325. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  326. package/frontend/src/assets/logos/instagram.svg +2 -0
  327. package/frontend/src/assets/logos/linkedin.svg +3 -0
  328. package/frontend/src/assets/logos/microsoft.svg +1 -0
  329. package/frontend/src/assets/logos/openai.svg +10 -10
  330. package/frontend/src/assets/logos/roo_code.svg +9 -9
  331. package/frontend/src/assets/logos/spotify.svg +17 -0
  332. package/frontend/src/assets/logos/tiktok.svg +6 -0
  333. package/frontend/src/assets/logos/trae.svg +3 -3
  334. package/frontend/src/assets/logos/windsurf.svg +10 -10
  335. package/frontend/src/assets/logos/x.svg +3 -0
  336. package/frontend/src/components/Checkbox.tsx +27 -29
  337. package/frontend/src/components/CodeBlock.tsx +55 -2
  338. package/frontend/src/components/CodeEditor.tsx +92 -0
  339. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  340. package/frontend/src/components/ConnectCTA.tsx +38 -0
  341. package/frontend/src/components/CopyButton.tsx +52 -15
  342. package/frontend/src/components/ErrorState.tsx +1 -2
  343. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  344. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  345. package/frontend/src/components/JsonHighlight.tsx +21 -9
  346. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  347. package/frontend/src/components/PromptDialog.tsx +1 -4
  348. package/frontend/src/components/SearchInput.tsx +1 -2
  349. package/frontend/src/components/Stepper.tsx +53 -0
  350. package/frontend/src/components/ThemeToggle.tsx +3 -3
  351. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  352. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  353. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  354. package/frontend/src/components/datagrid/index.ts +23 -0
  355. package/frontend/src/components/index.ts +23 -30
  356. package/frontend/src/components/layout/AppHeader.tsx +131 -91
  357. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  358. package/frontend/src/components/layout/Layout.tsx +12 -23
  359. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  360. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  361. package/frontend/src/components/layout/index.ts +5 -0
  362. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  363. package/frontend/src/components/radix/index.ts +22 -0
  364. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  365. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  366. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  367. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  368. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  369. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  370. package/frontend/src/features/ai/components/index.ts +6 -0
  371. package/frontend/src/features/ai/helpers.ts +57 -71
  372. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  373. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  374. package/frontend/src/features/ai/pages/AIPage.tsx +166 -0
  375. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  376. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  377. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +54 -30
  378. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  379. package/frontend/src/features/auth/components/UsersDataGrid.tsx +50 -14
  380. package/frontend/src/features/auth/components/index.ts +5 -0
  381. package/frontend/src/features/auth/helpers.tsx +208 -0
  382. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  383. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  384. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  385. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  386. package/frontend/src/features/auth/index.ts +3 -2
  387. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -0
  388. package/frontend/src/features/auth/pages/ConfigurationPage.tsx +395 -0
  389. package/frontend/src/features/auth/pages/UsersPage.tsx +257 -0
  390. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  391. package/frontend/src/features/auth/services/config.service.ts +19 -0
  392. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  393. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  394. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  395. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  396. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  397. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  398. package/frontend/src/features/dashboard/components/index.ts +4 -0
  399. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +212 -0
  400. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  401. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  402. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  403. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  404. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  405. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  406. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  407. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  408. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  409. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  410. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  411. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  412. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  413. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  414. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  415. package/frontend/src/features/database/components/TableForm.tsx +28 -19
  416. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  417. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  418. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  419. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  420. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  421. package/frontend/src/features/database/components/index.ts +19 -0
  422. package/frontend/src/features/database/constants.ts +28 -2
  423. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  424. package/frontend/src/features/database/helpers.ts +2 -2
  425. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  426. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  427. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  428. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  429. package/frontend/src/features/database/hooks/useTables.ts +135 -0
  430. package/frontend/src/features/database/index.ts +7 -1
  431. package/frontend/src/features/database/pages/FunctionsPage.tsx +203 -0
  432. package/frontend/src/features/database/pages/IndexesPage.tsx +228 -0
  433. package/frontend/src/features/database/pages/PoliciesPage.tsx +237 -0
  434. package/frontend/src/features/database/pages/SQLEditorPage.tsx +382 -0
  435. package/frontend/src/features/database/{page/DatabasePage.tsx → pages/TablesPage.tsx} +168 -209
  436. package/frontend/src/features/database/pages/TemplatesPage.tsx +39 -0
  437. package/frontend/src/features/database/pages/TriggersPage.tsx +230 -0
  438. package/frontend/src/features/database/services/advance.service.ts +40 -0
  439. package/frontend/src/features/database/services/database.service.ts +33 -194
  440. package/frontend/src/features/database/services/record.service.ts +219 -0
  441. package/frontend/src/features/database/services/table.service.ts +58 -0
  442. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  443. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  444. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  445. package/frontend/src/features/database/templates/index.ts +34 -0
  446. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  447. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  448. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  449. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  450. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  451. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  452. package/frontend/src/features/functions/components/index.ts +5 -0
  453. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  454. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  455. package/frontend/src/features/functions/pages/FunctionsPage.tsx +148 -0
  456. package/frontend/src/features/functions/{components/SecretsContent.tsx → pages/SecretsPage.tsx} +19 -21
  457. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  458. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  459. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  460. package/frontend/src/features/login/pages/CloudLoginPage.tsx +118 -0
  461. package/frontend/src/features/login/{page → pages}/LoginPage.tsx +16 -23
  462. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  463. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  464. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  465. package/frontend/src/features/logs/components/index.ts +2 -0
  466. package/frontend/src/features/logs/helpers.ts +24 -0
  467. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  468. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  469. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  470. package/frontend/src/features/logs/hooks/useMcpUsage.ts +128 -0
  471. package/frontend/src/features/logs/index.ts +8 -2
  472. package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +91 -38
  473. package/frontend/src/features/logs/pages/LogsPage.tsx +152 -0
  474. package/frontend/src/features/logs/pages/MCPLogsPage.tsx +84 -0
  475. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  476. package/frontend/src/features/logs/services/log.service.ts +15 -110
  477. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  478. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  479. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  480. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  481. package/frontend/src/features/onboard/components/index.ts +4 -0
  482. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  483. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  484. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  485. package/frontend/src/features/onboard/index.ts +13 -3
  486. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  487. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  488. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  489. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  490. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  491. package/frontend/src/features/realtime/index.ts +11 -0
  492. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  493. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  494. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  495. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  496. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  497. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  498. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  499. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  500. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  501. package/frontend/src/features/storage/components/index.ts +12 -0
  502. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  503. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +41 -143
  504. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  505. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  506. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  507. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  508. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  509. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  510. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +97 -0
  511. package/frontend/src/index.css +1 -0
  512. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  513. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  514. package/frontend/src/lib/contexts/SocketContext.tsx +123 -80
  515. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  516. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  517. package/frontend/src/lib/routing/AppRoutes.tsx +99 -0
  518. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  519. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  520. package/frontend/src/lib/utils/menuItems.ts +207 -0
  521. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  522. package/frontend/src/lib/utils/utils.ts +32 -1
  523. package/frontend/src/vite-env.d.ts +1 -0
  524. package/frontend/tsconfig.json +25 -25
  525. package/frontend/tsconfig.node.json +9 -9
  526. package/frontend/vite.config.ts +5 -3
  527. package/functions/deno.json +24 -24
  528. package/functions/server.ts +315 -290
  529. package/functions/worker-template.js +15 -4
  530. package/i18n/README.ar.md +130 -0
  531. package/i18n/README.de.md +130 -0
  532. package/i18n/README.es.md +154 -0
  533. package/i18n/README.fr.md +134 -0
  534. package/i18n/README.hi.md +129 -0
  535. package/i18n/README.ja.md +174 -0
  536. package/i18n/README.ko.md +137 -0
  537. package/i18n/README.pt-BR.md +131 -0
  538. package/i18n/README.ru.md +129 -0
  539. package/i18n/README.zh-CN.md +133 -0
  540. package/openapi/ai.yaml +715 -688
  541. package/openapi/auth.yaml +1244 -563
  542. package/openapi/email.yaml +158 -0
  543. package/openapi/functions.yaml +475 -475
  544. package/openapi/health.yaml +29 -29
  545. package/openapi/logs.yaml +223 -223
  546. package/openapi/metadata.yaml +177 -177
  547. package/openapi/realtime.yaml +699 -0
  548. package/openapi/records.yaml +381 -381
  549. package/openapi/secrets.yaml +370 -370
  550. package/openapi/storage.yaml +875 -875
  551. package/openapi/tables.yaml +463 -463
  552. package/package.json +97 -88
  553. package/shared-schemas/package.json +31 -31
  554. package/shared-schemas/src/ai-api.schema.ts +34 -58
  555. package/shared-schemas/src/ai.schema.ts +63 -54
  556. package/shared-schemas/src/auth-api.schema.ts +352 -193
  557. package/shared-schemas/src/auth.schema.ts +43 -7
  558. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  559. package/shared-schemas/src/database-api.schema.ts +35 -4
  560. package/shared-schemas/src/database.schema.ts +40 -1
  561. package/shared-schemas/src/docs.schema.ts +26 -0
  562. package/shared-schemas/src/email-api.schema.ts +30 -0
  563. package/shared-schemas/src/index.ts +5 -0
  564. package/shared-schemas/src/logs-api.schema.ts +7 -1
  565. package/shared-schemas/src/logs.schema.ts +26 -0
  566. package/shared-schemas/src/metadata.schema.ts +18 -4
  567. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  568. package/shared-schemas/src/realtime.schema.ts +143 -0
  569. package/shared-schemas/tsconfig.json +21 -21
  570. package/tsconfig.json +7 -7
  571. package/zeabur/README.md +13 -0
  572. package/zeabur/template.yml +1032 -0
  573. package/.github/workflows/deploy-aws.yml +0 -130
  574. package/backend/src/api/routes/agent.ts +0 -29
  575. package/backend/src/api/routes/auth.oauth.ts +0 -482
  576. package/backend/src/api/routes/auth.ts +0 -386
  577. package/backend/src/api/routes/docs.ts +0 -66
  578. package/backend/src/api/routes/functions.ts +0 -183
  579. package/backend/src/api/routes/openapi.ts +0 -82
  580. package/backend/src/api/routes/usage.ts +0 -96
  581. package/backend/src/core/ai/client.ts +0 -242
  582. package/backend/src/core/ai/model.ts +0 -117
  583. package/backend/src/core/auth/auth.ts +0 -780
  584. package/backend/src/core/database/manager.ts +0 -178
  585. package/backend/src/core/database/table.ts +0 -772
  586. package/backend/src/core/documentation/agent.ts +0 -689
  587. package/backend/src/core/documentation/openapi.ts +0 -856
  588. package/backend/src/core/logs/analytics.ts +0 -76
  589. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  590. package/backend/src/core/socket/socket.ts +0 -388
  591. package/backend/src/core/storage/storage.ts +0 -923
  592. package/backend/src/utils/cloud-token.ts +0 -39
  593. package/backend/src/utils/helpers.ts +0 -49
  594. package/backend/src/utils/uuid.ts +0 -9
  595. package/backend/tests/manual/test-better-auth.sh +0 -303
  596. package/docker-init/db/logs.sql +0 -9
  597. package/frontend/README.md +0 -112
  598. package/frontend/src/components/datagrid/index.tsx +0 -20
  599. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  600. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  601. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  602. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  603. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  604. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  605. package/frontend/src/features/ai/page/AIPage.tsx +0 -178
  606. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  607. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  608. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  609. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  610. package/frontend/src/features/dashboard/page/DashboardPage.tsx +0 -194
  611. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  612. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  613. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  614. package/frontend/src/features/functions/page/FunctionsPage.tsx +0 -28
  615. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  616. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  617. package/frontend/src/features/login/page/CloudLoginPage.tsx +0 -93
  618. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  619. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  620. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  621. package/frontend/src/features/metadata/index.ts +0 -0
  622. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  623. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  624. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  625. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  626. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  627. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  628. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  629. package/frontend/src/features/onboard/types.ts +0 -8
  630. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +0 -127
  631. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  632. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  633. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  634. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  635. /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
@@ -0,0 +1,1150 @@
1
+ import bcrypt from 'bcryptjs';
2
+ import crypto from 'crypto';
3
+ import { Pool } from 'pg';
4
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
5
+ import { TokenManager } from '@/infra/security/token.manager.js';
6
+ import logger from '@/utils/logger.js';
7
+ import type {
8
+ UserSchema,
9
+ CreateUserResponse,
10
+ CreateSessionResponse,
11
+ VerifyEmailResponse,
12
+ ResetPasswordResponse,
13
+ CreateAdminSessionResponse,
14
+ AuthMetadataSchema,
15
+ OAuthProvidersSchema,
16
+ } from '@insforge/shared-schemas';
17
+ import { OAuthConfigService } from '@/services/auth/oauth-config.service.js';
18
+ import { AuthConfigService } from './auth-config.service.js';
19
+ import { AuthOTPService, OTPPurpose, OTPType } from './auth-otp.service.js';
20
+ import { GoogleOAuthProvider } from '@/providers/oauth/google.provider.js';
21
+ import { GitHubOAuthProvider } from '@/providers/oauth/github.provider.js';
22
+ import { DiscordOAuthProvider } from '@/providers/oauth/discord.provider.js';
23
+ import { LinkedInOAuthProvider } from '@/providers/oauth/linkedin.provider.js';
24
+ import { FacebookOAuthProvider } from '@/providers/oauth/facebook.provider.js';
25
+ import { MicrosoftOAuthProvider } from '@/providers/oauth/microsoft.provider.js';
26
+ import { validatePassword } from '@/utils/validations.js';
27
+ import { getPasswordRequirementsMessage } from '@/utils/utils.js';
28
+ import {
29
+ FacebookUserInfo,
30
+ GitHubUserInfo,
31
+ GoogleUserInfo,
32
+ MicrosoftUserInfo,
33
+ LinkedInUserInfo,
34
+ DiscordUserInfo,
35
+ XUserInfo,
36
+ AppleUserInfo,
37
+ UserRecord,
38
+ OAuthUserData,
39
+ } from '@/types/auth.js';
40
+ import { ADMIN_ID } from '@/utils/constants.js';
41
+ import { getApiBaseUrl } from '@/utils/environment.js';
42
+ import { AppError } from '@/api/middlewares/error.js';
43
+ import { ERROR_CODES } from '@/types/error-constants.js';
44
+ import { EmailService } from '@/services/email/email.service.js';
45
+ import { XOAuthProvider } from '@/providers/oauth/x.provider.js';
46
+ import { AppleOAuthProvider } from '@/providers/oauth/apple.provider.js';
47
+
48
+ /**
49
+ * Simplified JWT-based auth service
50
+ * Handles all authentication operations including OAuth
51
+ */
52
+ export class AuthService {
53
+ private static instance: AuthService;
54
+ private adminEmail: string;
55
+ private adminPassword: string;
56
+ private pool: Pool | null = null;
57
+ private tokenManager: TokenManager;
58
+
59
+ // OAuth provider instances (cached singletons)
60
+ private googleOAuthProvider: GoogleOAuthProvider;
61
+ private githubOAuthProvider: GitHubOAuthProvider;
62
+ private discordOAuthProvider: DiscordOAuthProvider;
63
+ private linkedinOAuthProvider: LinkedInOAuthProvider;
64
+ private facebookOAuthProvider: FacebookOAuthProvider;
65
+ private microsoftOAuthProvider: MicrosoftOAuthProvider;
66
+ private xOAuthProvider: XOAuthProvider;
67
+ private appleOAuthProvider: AppleOAuthProvider;
68
+
69
+ private constructor() {
70
+ this.adminEmail = process.env.ADMIN_EMAIL ?? '';
71
+ this.adminPassword = process.env.ADMIN_PASSWORD ?? '';
72
+
73
+ if (!this.adminEmail || !this.adminPassword) {
74
+ throw new Error('ADMIN_EMAIL and ADMIN_PASSWORD environment variables are required');
75
+ }
76
+
77
+ // Initialize token manager
78
+ this.tokenManager = TokenManager.getInstance();
79
+
80
+ // Initialize OAuth providers (cached singletons)
81
+ this.googleOAuthProvider = GoogleOAuthProvider.getInstance();
82
+ this.githubOAuthProvider = GitHubOAuthProvider.getInstance();
83
+ this.discordOAuthProvider = DiscordOAuthProvider.getInstance();
84
+ this.linkedinOAuthProvider = LinkedInOAuthProvider.getInstance();
85
+ this.facebookOAuthProvider = FacebookOAuthProvider.getInstance();
86
+ this.microsoftOAuthProvider = MicrosoftOAuthProvider.getInstance();
87
+ this.xOAuthProvider = XOAuthProvider.getInstance();
88
+ this.appleOAuthProvider = AppleOAuthProvider.getInstance();
89
+
90
+ logger.info('AuthService initialized');
91
+ }
92
+
93
+ public static getInstance(): AuthService {
94
+ if (!AuthService.instance) {
95
+ AuthService.instance = new AuthService();
96
+ }
97
+ return AuthService.instance;
98
+ }
99
+
100
+ private getPool(): Pool {
101
+ if (!this.pool) {
102
+ const dbManager = DatabaseManager.getInstance();
103
+ this.pool = dbManager.getPool();
104
+ }
105
+ return this.pool;
106
+ }
107
+
108
+ /**
109
+ * User registration
110
+ * Otherwise, returns user with access token for immediate login
111
+ */
112
+ async register(email: string, password: string, name?: string): Promise<CreateUserResponse> {
113
+ // Get email auth configuration and validate password
114
+ const authConfigService = AuthConfigService.getInstance();
115
+ const emailAuthConfig = await authConfigService.getAuthConfig();
116
+
117
+ if (!validatePassword(password, emailAuthConfig)) {
118
+ throw new AppError(
119
+ getPasswordRequirementsMessage(emailAuthConfig),
120
+ 400,
121
+ ERROR_CODES.INVALID_INPUT
122
+ );
123
+ }
124
+
125
+ const hashedPassword = await bcrypt.hash(password, 10);
126
+ const userId = crypto.randomUUID();
127
+
128
+ const pool = this.getPool();
129
+ const client = await pool.connect();
130
+ try {
131
+ await client.query('BEGIN');
132
+
133
+ await client.query(
134
+ `INSERT INTO _accounts (id, email, password, name, email_verified, created_at, updated_at)
135
+ VALUES ($1, $2, $3, $4, $5, NOW(), NOW())`,
136
+ [userId, email, hashedPassword, name || null, false]
137
+ );
138
+
139
+ await client.query(
140
+ `INSERT INTO users (id, nickname, created_at, updated_at)
141
+ VALUES ($1, $2, NOW(), NOW())`,
142
+ [userId, name || null]
143
+ );
144
+
145
+ await client.query('COMMIT');
146
+ } catch (e) {
147
+ await client.query('ROLLBACK');
148
+ // Postgres unique_violation
149
+ if (e && typeof e === 'object' && 'code' in e && e.code === '23505') {
150
+ throw new AppError('User already exists', 409, ERROR_CODES.ALREADY_EXISTS);
151
+ }
152
+ throw e;
153
+ } finally {
154
+ client.release();
155
+ }
156
+
157
+ const dbUser = await this.getUserById(userId);
158
+ if (!dbUser) {
159
+ throw new Error('User not found after registration');
160
+ }
161
+ const user = this.transformUserRecordToSchema(dbUser);
162
+
163
+ if (emailAuthConfig.requireEmailVerification) {
164
+ try {
165
+ if (emailAuthConfig.verifyEmailMethod === 'link') {
166
+ await this.sendVerificationEmailWithLink(email);
167
+ } else {
168
+ await this.sendVerificationEmailWithCode(email);
169
+ }
170
+ } catch (error) {
171
+ logger.warn('Verification email send failed during register', { error });
172
+ }
173
+ return {
174
+ accessToken: null,
175
+ requireEmailVerification: true,
176
+ };
177
+ }
178
+
179
+ // Email verification not required, provide access token for immediate login
180
+ const accessToken = this.tokenManager.generateToken({
181
+ sub: userId,
182
+ email,
183
+ role: 'authenticated',
184
+ });
185
+
186
+ return {
187
+ user,
188
+ accessToken,
189
+ requireEmailVerification: false,
190
+ redirectTo: emailAuthConfig.signInRedirectTo || undefined,
191
+ };
192
+ }
193
+
194
+ /**
195
+ * User login
196
+ */
197
+ async login(email: string, password: string): Promise<CreateSessionResponse> {
198
+ const dbUser = await this.getUserByEmail(email);
199
+
200
+ if (!dbUser || !dbUser.password) {
201
+ throw new AppError('Invalid credentials', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
202
+ }
203
+
204
+ const validPassword = await bcrypt.compare(password, dbUser.password);
205
+ if (!validPassword) {
206
+ throw new AppError('Invalid credentials', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
207
+ }
208
+
209
+ // Check if email verification is required
210
+ const authConfigService = AuthConfigService.getInstance();
211
+ const emailAuthConfig = await authConfigService.getAuthConfig();
212
+
213
+ if (emailAuthConfig.requireEmailVerification && !dbUser.email_verified) {
214
+ throw new AppError(
215
+ 'Email verification required',
216
+ 403,
217
+ ERROR_CODES.FORBIDDEN,
218
+ 'Please verify your email address before logging in'
219
+ );
220
+ }
221
+
222
+ const user = this.transformUserRecordToSchema(dbUser);
223
+ const accessToken = this.tokenManager.generateToken({
224
+ sub: dbUser.id,
225
+ email: dbUser.email,
226
+ role: 'authenticated',
227
+ });
228
+
229
+ // Include redirect URL if configured
230
+ const response: CreateSessionResponse = {
231
+ user,
232
+ accessToken,
233
+ redirectTo: emailAuthConfig.signInRedirectTo || undefined,
234
+ };
235
+
236
+ return response;
237
+ }
238
+
239
+ /**
240
+ * Send verification email with numeric OTP code
241
+ * Creates a 6-digit OTP and sends it via email for manual entry
242
+ */
243
+ async sendVerificationEmailWithCode(email: string): Promise<void> {
244
+ // Check if user exists
245
+ const pool = this.getPool();
246
+ const result = await pool.query('SELECT * FROM _accounts WHERE email = $1', [email]);
247
+ const dbUser = result.rows[0];
248
+ if (!dbUser) {
249
+ // Silently succeed to prevent user enumeration
250
+ logger.info('Verification email requested for non-existent user', { email });
251
+ return;
252
+ }
253
+
254
+ // Create numeric OTP code using the OTP service
255
+ const otpService = AuthOTPService.getInstance();
256
+ const { otp: code } = await otpService.createEmailOTP(
257
+ email,
258
+ OTPPurpose.VERIFY_EMAIL,
259
+ OTPType.NUMERIC_CODE
260
+ );
261
+
262
+ // Send email with verification code
263
+ const emailService = EmailService.getInstance();
264
+ await emailService.sendWithTemplate(email, dbUser.name || 'User', 'email-verification-code', {
265
+ token: code,
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Send verification email with clickable link
271
+ * Creates a long cryptographic token and sends it via email as a clickable link
272
+ * The link contains only the token (no email) for better privacy and security
273
+ */
274
+ async sendVerificationEmailWithLink(email: string): Promise<void> {
275
+ // Check if user exists
276
+ const pool = this.getPool();
277
+ const result = await pool.query('SELECT * FROM _accounts WHERE email = $1', [email]);
278
+ const dbUser = result.rows[0];
279
+ if (!dbUser) {
280
+ // Silently succeed to prevent user enumeration
281
+ logger.info('Verification email requested for non-existent user', { email });
282
+ return;
283
+ }
284
+
285
+ // Create long cryptographic token for clickable verification link
286
+ const otpService = AuthOTPService.getInstance();
287
+ const { otp: token } = await otpService.createEmailOTP(
288
+ email,
289
+ OTPPurpose.VERIFY_EMAIL,
290
+ OTPType.HASH_TOKEN
291
+ );
292
+
293
+ // Build verification link URL using backend API endpoint
294
+ const linkUrl = `${getApiBaseUrl()}/auth/verify-email?token=${token}`;
295
+
296
+ // Send email with verification link
297
+ const emailService = EmailService.getInstance();
298
+ await emailService.sendWithTemplate(email, dbUser.name || 'User', 'email-verification-link', {
299
+ link: linkUrl,
300
+ });
301
+ }
302
+
303
+ /**
304
+ * Verify email with numeric code
305
+ * Verifies the email OTP code and updates the account in a single transaction
306
+ */
307
+ async verifyEmailWithCode(email: string, verificationCode: string): Promise<VerifyEmailResponse> {
308
+ const dbManager = DatabaseManager.getInstance();
309
+ const pool = dbManager.getPool();
310
+ const client = await pool.connect();
311
+
312
+ try {
313
+ await client.query('BEGIN');
314
+
315
+ // Verify OTP using the OTP service (within the same transaction)
316
+ const otpService = AuthOTPService.getInstance();
317
+ await otpService.verifyEmailOTPWithCode(
318
+ email,
319
+ OTPPurpose.VERIFY_EMAIL,
320
+ verificationCode,
321
+ client
322
+ );
323
+
324
+ // Update account email verification status
325
+ const result = await client.query(
326
+ `UPDATE _accounts
327
+ SET email_verified = true, updated_at = NOW()
328
+ WHERE email = $1
329
+ RETURNING id`,
330
+ [email]
331
+ );
332
+
333
+ if (result.rows.length === 0) {
334
+ throw new Error('User not found');
335
+ }
336
+
337
+ await client.query('COMMIT');
338
+
339
+ // Fetch full user record with provider data
340
+ const userId = result.rows[0].id;
341
+ const dbUser = await this.getUserById(userId);
342
+ if (!dbUser) {
343
+ throw new Error('User not found after verification');
344
+ }
345
+ const user = this.transformUserRecordToSchema(dbUser);
346
+ const accessToken = this.tokenManager.generateToken({
347
+ sub: dbUser.id,
348
+ email: dbUser.email,
349
+ role: 'authenticated',
350
+ });
351
+
352
+ // Get redirect URL from auth config if configured
353
+ const authConfigService = AuthConfigService.getInstance();
354
+ const emailAuthConfig = await authConfigService.getAuthConfig();
355
+
356
+ return {
357
+ user,
358
+ accessToken,
359
+ redirectTo: emailAuthConfig.signInRedirectTo || undefined,
360
+ };
361
+ } catch (error) {
362
+ await client.query('ROLLBACK');
363
+ throw error;
364
+ } finally {
365
+ client.release();
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Verify email with hash token from clickable link
371
+ * Verifies the token (without needing email), looks up the email, and updates the account
372
+ * This is more secure as the email is not exposed in the URL
373
+ */
374
+ async verifyEmailWithToken(token: string): Promise<VerifyEmailResponse> {
375
+ const dbManager = DatabaseManager.getInstance();
376
+ const pool = dbManager.getPool();
377
+ const client = await pool.connect();
378
+
379
+ try {
380
+ await client.query('BEGIN');
381
+
382
+ // Verify token and get the associated email
383
+ const otpService = AuthOTPService.getInstance();
384
+ const { email } = await otpService.verifyEmailOTPWithToken(
385
+ OTPPurpose.VERIFY_EMAIL,
386
+ token,
387
+ client
388
+ );
389
+
390
+ // Update account email verification status
391
+ const result = await client.query(
392
+ `UPDATE _accounts
393
+ SET email_verified = true, updated_at = NOW()
394
+ WHERE email = $1
395
+ RETURNING id`,
396
+ [email]
397
+ );
398
+
399
+ if (result.rows.length === 0) {
400
+ throw new Error('User not found');
401
+ }
402
+
403
+ await client.query('COMMIT');
404
+
405
+ // Fetch full user record with provider data
406
+ const userId = result.rows[0].id;
407
+ const dbUser = await this.getUserById(userId);
408
+ if (!dbUser) {
409
+ throw new Error('User not found after verification');
410
+ }
411
+ const user = this.transformUserRecordToSchema(dbUser);
412
+ const accessToken = this.tokenManager.generateToken({
413
+ sub: dbUser.id,
414
+ email: dbUser.email,
415
+ role: 'authenticated',
416
+ });
417
+
418
+ // Get redirect URL from auth config if configured
419
+ const authConfigService = AuthConfigService.getInstance();
420
+ const emailAuthConfig = await authConfigService.getAuthConfig();
421
+
422
+ return {
423
+ user,
424
+ accessToken,
425
+ redirectTo: emailAuthConfig.signInRedirectTo || undefined,
426
+ };
427
+ } catch (error) {
428
+ await client.query('ROLLBACK');
429
+ throw error;
430
+ } finally {
431
+ client.release();
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Send reset password email with numeric OTP code
437
+ * Creates a 6-digit OTP and sends it via email for manual entry
438
+ */
439
+ async sendResetPasswordEmailWithCode(email: string): Promise<void> {
440
+ // Check if user exists
441
+ const pool = this.getPool();
442
+ const result = await pool.query('SELECT * FROM _accounts WHERE email = $1', [email]);
443
+ const dbUser = result.rows[0];
444
+ if (!dbUser) {
445
+ // Silently succeed to prevent user enumeration
446
+ logger.info('Password reset requested for non-existent user', { email });
447
+ return;
448
+ }
449
+
450
+ // Create numeric OTP code using the OTP service
451
+ const otpService = AuthOTPService.getInstance();
452
+ const { otp: code } = await otpService.createEmailOTP(
453
+ email,
454
+ OTPPurpose.RESET_PASSWORD,
455
+ OTPType.NUMERIC_CODE
456
+ );
457
+
458
+ // Send email with reset password code
459
+ const emailService = EmailService.getInstance();
460
+ await emailService.sendWithTemplate(email, dbUser.name || 'User', 'reset-password-code', {
461
+ token: code,
462
+ });
463
+ }
464
+
465
+ /**
466
+ * Send reset password email with clickable link
467
+ * Creates a long cryptographic token and sends it via email as a clickable link
468
+ * The link contains only the token (no email) for better privacy and security
469
+ */
470
+ async sendResetPasswordEmailWithLink(email: string): Promise<void> {
471
+ // Check if user exists
472
+ const pool = this.getPool();
473
+ const result = await pool.query('SELECT * FROM _accounts WHERE email = $1', [email]);
474
+ const dbUser = result.rows[0];
475
+ if (!dbUser) {
476
+ // Silently succeed to prevent user enumeration
477
+ logger.info('Password reset requested for non-existent user', { email });
478
+ return;
479
+ }
480
+
481
+ // Create long cryptographic token for clickable reset link
482
+ const otpService = AuthOTPService.getInstance();
483
+ const { otp: token } = await otpService.createEmailOTP(
484
+ email,
485
+ OTPPurpose.RESET_PASSWORD,
486
+ OTPType.HASH_TOKEN
487
+ );
488
+
489
+ // Build password reset link URL using backend API endpoint
490
+ const linkUrl = `${getApiBaseUrl()}/auth/reset-password?token=${token}`;
491
+
492
+ // Send email with password reset link
493
+ const emailService = EmailService.getInstance();
494
+ await emailService.sendWithTemplate(email, dbUser.name || 'User', 'reset-password-link', {
495
+ link: linkUrl,
496
+ });
497
+ }
498
+
499
+ /**
500
+ * Exchange reset password code for a temporary reset token
501
+ * This separates code verification from password reset for better security
502
+ * The reset token can be used later to reset the password without needing email
503
+ */
504
+ async exchangeResetPasswordToken(
505
+ email: string,
506
+ verificationCode: string
507
+ ): Promise<{ token: string; expiresAt: Date }> {
508
+ const otpService = AuthOTPService.getInstance();
509
+
510
+ // Exchange the numeric verification code for a long-lived reset token
511
+ // All OTP logic (verification, consumption, token generation) is handled by AuthOTPService
512
+ const result = await otpService.exchangeCodeForToken(
513
+ email,
514
+ OTPPurpose.RESET_PASSWORD,
515
+ verificationCode
516
+ );
517
+
518
+ return {
519
+ token: result.token,
520
+ expiresAt: result.expiresAt,
521
+ };
522
+ }
523
+
524
+ /**
525
+ * Reset password with token
526
+ * Verifies the token (without needing email), looks up the email, and updates the password
527
+ * Both clickable link tokens and code-verified reset tokens use RESET_PASSWORD purpose
528
+ * Note: Does not return access token - user must login again with new password
529
+ */
530
+ async resetPasswordWithToken(newPassword: string, token: string): Promise<ResetPasswordResponse> {
531
+ // Validate password first before verifying token
532
+ // This allows the user to retry with the same token if password is invalid
533
+ const authConfigService = AuthConfigService.getInstance();
534
+ const emailAuthConfig = await authConfigService.getAuthConfig();
535
+
536
+ if (!validatePassword(newPassword, emailAuthConfig)) {
537
+ throw new AppError(
538
+ getPasswordRequirementsMessage(emailAuthConfig),
539
+ 400,
540
+ ERROR_CODES.INVALID_INPUT
541
+ );
542
+ }
543
+
544
+ const dbManager = DatabaseManager.getInstance();
545
+ const pool = dbManager.getPool();
546
+ const client = await pool.connect();
547
+
548
+ try {
549
+ await client.query('BEGIN');
550
+
551
+ // Verify token and get the associated email
552
+ // Both clickable link tokens and code-verified reset tokens use RESET_PASSWORD purpose
553
+ const otpService = AuthOTPService.getInstance();
554
+ const { email } = await otpService.verifyEmailOTPWithToken(
555
+ OTPPurpose.RESET_PASSWORD,
556
+ token,
557
+ client
558
+ );
559
+
560
+ // Hash the new password
561
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
562
+
563
+ // Update password in the database
564
+ const result = await client.query(
565
+ `UPDATE _accounts
566
+ SET password = $1, updated_at = NOW()
567
+ WHERE email = $2
568
+ RETURNING id`,
569
+ [hashedPassword, email]
570
+ );
571
+
572
+ if (result.rows.length === 0) {
573
+ throw new Error('User not found');
574
+ }
575
+
576
+ const userId = result.rows[0].id;
577
+
578
+ await client.query('COMMIT');
579
+
580
+ logger.info('Password reset successfully with token', { userId });
581
+
582
+ return {
583
+ message: 'Password reset successfully. Please login with your new password.',
584
+ };
585
+ } catch (error) {
586
+ await client.query('ROLLBACK');
587
+ throw error;
588
+ } finally {
589
+ client.release();
590
+ }
591
+ }
592
+
593
+ /**
594
+ * Admin login (validates against env variables only)
595
+ */
596
+ adminLogin(email: string, password: string): CreateAdminSessionResponse {
597
+ // Simply validate against environment variables
598
+ if (email !== this.adminEmail || password !== this.adminPassword) {
599
+ throw new AppError('Invalid admin credentials', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
600
+ }
601
+
602
+ // Use a fixed admin ID for the system administrator
603
+
604
+ // Return admin user with JWT token - no database interaction
605
+ const accessToken = this.tokenManager.generateToken({
606
+ sub: ADMIN_ID,
607
+ email,
608
+ role: 'project_admin',
609
+ });
610
+
611
+ return {
612
+ user: {
613
+ id: ADMIN_ID,
614
+ email: email,
615
+ name: 'Administrator',
616
+ emailVerified: true,
617
+ createdAt: new Date().toISOString(),
618
+ updatedAt: new Date().toISOString(),
619
+ },
620
+ accessToken,
621
+ };
622
+ }
623
+
624
+ /**
625
+ * Admin login with authorization token (validates JWT from external issuer)
626
+ */
627
+ async adminLoginWithAuthorizationCode(code: string): Promise<CreateAdminSessionResponse> {
628
+ try {
629
+ // Use TokenManager to verify cloud token
630
+ const { payload } = await this.tokenManager.verifyCloudToken(code);
631
+
632
+ // If verification succeeds, extract user info and generate internal token
633
+ const email = payload['email'] || payload['sub'] || 'admin@insforge.local';
634
+
635
+ // Generate internal access token
636
+ const accessToken = this.tokenManager.generateToken({
637
+ sub: ADMIN_ID,
638
+ email: email as string,
639
+ role: 'project_admin',
640
+ });
641
+
642
+ return {
643
+ user: {
644
+ id: ADMIN_ID,
645
+ email: email as string,
646
+ name: 'Administrator',
647
+ emailVerified: true,
648
+ createdAt: new Date().toISOString(),
649
+ updatedAt: new Date().toISOString(),
650
+ },
651
+ accessToken,
652
+ };
653
+ } catch (error) {
654
+ logger.error('Admin token verification failed:', error);
655
+ throw new AppError('Invalid admin credentials', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
656
+ }
657
+ }
658
+
659
+ /**
660
+ * Find or create third-party user (main OAuth user handler)
661
+ * Adapted from 3-table to 2-table structure
662
+ */
663
+ async findOrCreateThirdPartyUser(
664
+ provider: string,
665
+ providerId: string,
666
+ email: string,
667
+ userName: string,
668
+ avatarUrl: string,
669
+ identityData:
670
+ | GoogleUserInfo
671
+ | GitHubUserInfo
672
+ | DiscordUserInfo
673
+ | LinkedInUserInfo
674
+ | MicrosoftUserInfo
675
+ | FacebookUserInfo
676
+ | XUserInfo
677
+ | AppleUserInfo
678
+ | Record<string, unknown>
679
+ ): Promise<CreateSessionResponse> {
680
+ const pool = this.getPool();
681
+
682
+ // First, try to find existing user by provider ID in _account_providers table
683
+ const accountResult = await pool.query(
684
+ 'SELECT * FROM _account_providers WHERE provider = $1 AND provider_account_id = $2',
685
+ [provider, providerId]
686
+ );
687
+ const account = accountResult.rows[0];
688
+
689
+ if (account) {
690
+ // Found existing OAuth user, update last login time
691
+ await pool.query(
692
+ 'UPDATE _account_providers SET updated_at = CURRENT_TIMESTAMP WHERE provider = $1 AND provider_account_id = $2',
693
+ [provider, providerId]
694
+ );
695
+
696
+ // Update email_verified to true if not already verified (OAuth login means email is trusted)
697
+ await pool.query(
698
+ 'UPDATE _accounts SET email_verified = true WHERE id = $1 AND email_verified = false',
699
+ [account.user_id]
700
+ );
701
+
702
+ const dbUser = await this.getUserById(account.user_id);
703
+ if (!dbUser) {
704
+ throw new Error('User not found after OAuth login');
705
+ }
706
+
707
+ const user = this.transformUserRecordToSchema(dbUser);
708
+ const accessToken = this.tokenManager.generateToken({
709
+ sub: user.id,
710
+ email: user.email,
711
+ role: 'authenticated',
712
+ });
713
+
714
+ return { user, accessToken };
715
+ }
716
+
717
+ // If not found by provider_id, try to find by email in _user table
718
+ const existingUserResult = await pool.query('SELECT * FROM _accounts WHERE email = $1', [
719
+ email,
720
+ ]);
721
+ const existingUser = existingUserResult.rows[0];
722
+
723
+ if (existingUser) {
724
+ // Found existing user by email, create _account_providers record to link OAuth
725
+ await pool.query(
726
+ `
727
+ INSERT INTO _account_providers (
728
+ user_id, provider, provider_account_id,
729
+ provider_data, created_at, updated_at
730
+ )
731
+ VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
732
+ `,
733
+ [existingUser.id, provider, providerId, JSON.stringify(identityData)]
734
+ );
735
+
736
+ // Update email_verified to true (OAuth login means email is trusted)
737
+ await pool.query(
738
+ 'UPDATE _accounts SET email_verified = true WHERE id = $1 AND email_verified = false',
739
+ [existingUser.id]
740
+ );
741
+
742
+ // Fetch updated user data with provider information
743
+ const dbUser = await this.getUserById(existingUser.id);
744
+ if (!dbUser) {
745
+ throw new Error('User not found after linking OAuth provider');
746
+ }
747
+
748
+ const user = this.transformUserRecordToSchema(dbUser);
749
+ const accessToken = this.tokenManager.generateToken({
750
+ sub: existingUser.id,
751
+ email: existingUser.email,
752
+ role: 'authenticated',
753
+ });
754
+
755
+ return { user, accessToken };
756
+ }
757
+
758
+ // Create new user with OAuth data
759
+ return this.createThirdPartyUser(
760
+ provider,
761
+ userName,
762
+ email,
763
+ providerId,
764
+ identityData,
765
+ avatarUrl
766
+ );
767
+ }
768
+
769
+ /**
770
+ * Create new third-party user
771
+ */
772
+ private async createThirdPartyUser(
773
+ provider: string,
774
+ userName: string,
775
+ email: string,
776
+ providerId: string,
777
+ identityData:
778
+ | GoogleUserInfo
779
+ | GitHubUserInfo
780
+ | DiscordUserInfo
781
+ | LinkedInUserInfo
782
+ | MicrosoftUserInfo
783
+ | FacebookUserInfo
784
+ | XUserInfo
785
+ | AppleUserInfo
786
+ | Record<string, unknown>,
787
+ avatarUrl: string
788
+ ): Promise<CreateSessionResponse> {
789
+ const userId = crypto.randomUUID();
790
+
791
+ const pool = this.getPool();
792
+ const client = await pool.connect();
793
+
794
+ try {
795
+ await client.query('BEGIN');
796
+
797
+ // Create user record (without password for OAuth users)
798
+ await client.query(
799
+ `
800
+ INSERT INTO _accounts (id, email, name, email_verified, created_at, updated_at)
801
+ VALUES ($1, $2, $3, true, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
802
+ `,
803
+ [userId, email, userName]
804
+ );
805
+
806
+ await client.query(
807
+ `
808
+ INSERT INTO users (id, nickname, avatar_url, created_at, updated_at)
809
+ VALUES ($1, $2, $3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
810
+ `,
811
+ [userId, userName, avatarUrl]
812
+ );
813
+
814
+ // Create _account_providers record
815
+ await client.query(
816
+ `
817
+ INSERT INTO _account_providers (
818
+ user_id, provider, provider_account_id,
819
+ provider_data, created_at, updated_at
820
+ )
821
+ VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
822
+ `,
823
+ [userId, provider, providerId, JSON.stringify({ ...identityData, avatar_url: avatarUrl })]
824
+ );
825
+
826
+ await client.query('COMMIT');
827
+
828
+ const user: UserSchema = {
829
+ id: userId,
830
+ email,
831
+ name: userName,
832
+ emailVerified: true,
833
+ createdAt: new Date().toISOString(),
834
+ updatedAt: new Date().toISOString(),
835
+ };
836
+
837
+ const accessToken = this.tokenManager.generateToken({
838
+ sub: userId,
839
+ email,
840
+ role: 'authenticated',
841
+ });
842
+
843
+ return { user, accessToken };
844
+ } catch (error) {
845
+ await client.query('ROLLBACK');
846
+ throw error;
847
+ } finally {
848
+ client.release();
849
+ }
850
+ }
851
+
852
+ async getMetadata(): Promise<AuthMetadataSchema> {
853
+ const oAuthConfigService = OAuthConfigService.getInstance();
854
+ const oAuthConfigs = await oAuthConfigService.getAllConfigs();
855
+ return {
856
+ oauths: oAuthConfigs,
857
+ };
858
+ }
859
+
860
+ /**
861
+ * Generate OAuth authorization URL for any supported provider
862
+ */
863
+ async generateOAuthUrl(provider: OAuthProvidersSchema, state?: string): Promise<string> {
864
+ switch (provider) {
865
+ case 'google':
866
+ return this.googleOAuthProvider.generateOAuthUrl(state);
867
+ case 'github':
868
+ return this.githubOAuthProvider.generateOAuthUrl(state);
869
+ case 'discord':
870
+ return this.discordOAuthProvider.generateOAuthUrl(state);
871
+ case 'linkedin':
872
+ return this.linkedinOAuthProvider.generateOAuthUrl(state);
873
+ case 'facebook':
874
+ return this.facebookOAuthProvider.generateOAuthUrl(state);
875
+ case 'microsoft':
876
+ return this.microsoftOAuthProvider.generateOAuthUrl(state);
877
+ case 'x':
878
+ return this.xOAuthProvider.generateOAuthUrl(state);
879
+ case 'apple':
880
+ return this.appleOAuthProvider.generateOAuthUrl(state);
881
+ default:
882
+ throw new Error(`OAuth provider ${provider} is not implemented yet.`);
883
+ }
884
+ }
885
+
886
+ /**
887
+ * Handle OAuth callback for any supported provider
888
+ */
889
+ async handleOAuthCallback(
890
+ provider: OAuthProvidersSchema,
891
+ payload: { code?: string; token?: string; state?: string }
892
+ ): Promise<CreateSessionResponse> {
893
+ let userData: OAuthUserData;
894
+
895
+ switch (provider) {
896
+ case 'google':
897
+ userData = await this.googleOAuthProvider.handleCallback(payload);
898
+ break;
899
+ case 'github':
900
+ userData = await this.githubOAuthProvider.handleCallback(payload);
901
+ break;
902
+ case 'discord':
903
+ userData = await this.discordOAuthProvider.handleCallback(payload);
904
+ break;
905
+ case 'linkedin':
906
+ userData = await this.linkedinOAuthProvider.handleCallback(payload);
907
+ break;
908
+ case 'facebook':
909
+ userData = await this.facebookOAuthProvider.handleCallback(payload);
910
+ break;
911
+ case 'microsoft':
912
+ userData = await this.microsoftOAuthProvider.handleCallback(payload);
913
+ break;
914
+ case 'x':
915
+ userData = await this.xOAuthProvider.handleCallback(payload);
916
+ break;
917
+ case 'apple':
918
+ userData = await this.appleOAuthProvider.handleCallback(payload);
919
+ break;
920
+ default:
921
+ throw new Error(`OAuth provider ${provider} is not implemented yet.`);
922
+ }
923
+
924
+ return this.findOrCreateThirdPartyUser(
925
+ userData.provider,
926
+ userData.providerId,
927
+ userData.email,
928
+ userData.userName,
929
+ userData.avatarUrl,
930
+ userData.identityData
931
+ );
932
+ }
933
+
934
+ /**
935
+ * Handle shared callback for any supported provider
936
+ * Transforms payload and creates/finds user
937
+ */
938
+ async handleSharedCallback(
939
+ provider: OAuthProvidersSchema,
940
+ payloadData: Record<string, unknown>
941
+ ): Promise<CreateSessionResponse> {
942
+ let userData: OAuthUserData;
943
+
944
+ switch (provider) {
945
+ case 'google':
946
+ userData = this.googleOAuthProvider.handleSharedCallback(payloadData);
947
+ break;
948
+ case 'github':
949
+ userData = this.githubOAuthProvider.handleSharedCallback(payloadData);
950
+ break;
951
+ case 'discord':
952
+ userData = this.discordOAuthProvider.handleSharedCallback(payloadData);
953
+ break;
954
+ case 'linkedin':
955
+ userData = this.linkedinOAuthProvider.handleSharedCallback(payloadData);
956
+ break;
957
+ case 'facebook':
958
+ userData = this.facebookOAuthProvider.handleSharedCallback(payloadData);
959
+ break;
960
+ case 'x':
961
+ userData = this.xOAuthProvider.handleSharedCallback(payloadData);
962
+ break;
963
+ case 'apple':
964
+ userData = this.appleOAuthProvider.handleSharedCallback(payloadData);
965
+ break;
966
+ case 'microsoft':
967
+ default:
968
+ throw new Error(`OAuth provider ${provider} is not supported for shared callback.`);
969
+ }
970
+
971
+ return this.findOrCreateThirdPartyUser(
972
+ userData.provider,
973
+ userData.providerId,
974
+ userData.email,
975
+ userData.userName,
976
+ userData.avatarUrl,
977
+ userData.identityData
978
+ );
979
+ }
980
+
981
+ /**
982
+ * Get user by email (helper method for internal use)
983
+ * @private
984
+ */
985
+ private async getUserByEmail(email: string): Promise<UserRecord | null> {
986
+ const pool = this.getPool();
987
+ const result = await pool.query(
988
+ `
989
+ SELECT
990
+ u.id,
991
+ u.email,
992
+ u.name,
993
+ u.email_verified,
994
+ u.created_at,
995
+ u.updated_at,
996
+ u.password,
997
+ STRING_AGG(a.provider, ',') as providers
998
+ FROM _accounts u
999
+ LEFT JOIN _account_providers a ON u.id = a.user_id
1000
+ WHERE u.email = $1
1001
+ GROUP BY u.id
1002
+ `,
1003
+ [email]
1004
+ );
1005
+
1006
+ return result.rows[0] || null;
1007
+ }
1008
+
1009
+ /**
1010
+ * Get user by ID (helper method for internal use)
1011
+ * @private
1012
+ */
1013
+ private async getUserById(userId: string): Promise<UserRecord | null> {
1014
+ const pool = this.getPool();
1015
+ const result = await pool.query(
1016
+ `
1017
+ SELECT
1018
+ u.id,
1019
+ u.email,
1020
+ u.name,
1021
+ u.email_verified,
1022
+ u.created_at,
1023
+ u.updated_at,
1024
+ u.password,
1025
+ STRING_AGG(a.provider, ',') as providers
1026
+ FROM _accounts u
1027
+ LEFT JOIN _account_providers a ON u.id = a.user_id
1028
+ WHERE u.id = $1
1029
+ GROUP BY u.id
1030
+ `,
1031
+ [userId]
1032
+ );
1033
+
1034
+ return result.rows[0] || null;
1035
+ }
1036
+
1037
+ /**
1038
+ * Transform database user record to API response format (snake_case to camelCase + provider logic)
1039
+ * @private
1040
+ */
1041
+ private transformUserRecordToSchema(dbUser: UserRecord): UserSchema {
1042
+ const identities = [];
1043
+ const providers: string[] = [];
1044
+
1045
+ // Add social providers if any
1046
+ if (dbUser.providers) {
1047
+ dbUser.providers.split(',').forEach((provider: string) => {
1048
+ identities.push({ provider });
1049
+ providers.push(provider);
1050
+ });
1051
+ }
1052
+
1053
+ // Add email provider if password exists
1054
+ if (dbUser.password) {
1055
+ identities.push({ provider: 'email' });
1056
+ providers.push('email');
1057
+ }
1058
+
1059
+ // Use first provider to determine type: 'email' or 'social'
1060
+ const firstProvider = providers[0];
1061
+ const providerType = firstProvider === 'email' ? 'email' : 'social';
1062
+
1063
+ return {
1064
+ id: dbUser.id,
1065
+ email: dbUser.email,
1066
+ name: dbUser.name,
1067
+ emailVerified: dbUser.email_verified,
1068
+ createdAt: dbUser.created_at,
1069
+ updatedAt: dbUser.updated_at,
1070
+ identities: identities,
1071
+ providerType: providerType,
1072
+ };
1073
+ }
1074
+
1075
+ /**
1076
+ * List users with pagination and search
1077
+ */
1078
+ async listUsers(
1079
+ limit: number,
1080
+ offset: number,
1081
+ search?: string
1082
+ ): Promise<{ users: UserSchema[]; total: number }> {
1083
+ const pool = this.getPool();
1084
+ let query = `
1085
+ SELECT
1086
+ u.id,
1087
+ u.email,
1088
+ u.name,
1089
+ u.email_verified,
1090
+ u.created_at,
1091
+ u.updated_at,
1092
+ u.password,
1093
+ STRING_AGG(a.provider, ',') as providers
1094
+ FROM _accounts u
1095
+ LEFT JOIN _account_providers a ON u.id = a.user_id
1096
+ `;
1097
+ const params: (string | number)[] = [];
1098
+
1099
+ if (search) {
1100
+ query += ' WHERE u.email LIKE $1 OR u.name LIKE $2';
1101
+ params.push(`%${search}%`, `%${search}%`);
1102
+ }
1103
+
1104
+ query += ` GROUP BY u.id ORDER BY u.created_at DESC LIMIT $${params.length + 1} OFFSET $${params.length + 2}`;
1105
+ params.push(limit, offset);
1106
+
1107
+ const result = await pool.query(query, params);
1108
+ const dbUsers = result.rows as UserRecord[];
1109
+
1110
+ // Transform users
1111
+ const users = dbUsers.map((dbUser) => this.transformUserRecordToSchema(dbUser));
1112
+
1113
+ // Get total count
1114
+ let countQuery = 'SELECT COUNT(*) as count FROM _accounts';
1115
+ const countParams: string[] = [];
1116
+ if (search) {
1117
+ countQuery += ' WHERE email LIKE $1 OR name LIKE $2';
1118
+ countParams.push(`%${search}%`, `%${search}%`);
1119
+ }
1120
+ const countResult = await pool.query(countQuery, countParams);
1121
+ const count = countResult.rows[0].count;
1122
+
1123
+ return {
1124
+ users,
1125
+ total: parseInt(count, 10),
1126
+ };
1127
+ }
1128
+
1129
+ /**
1130
+ * Get user by ID (returns UserSchema for API)
1131
+ */
1132
+ async getUserSchemaById(userId: string): Promise<UserSchema | null> {
1133
+ const dbUser = await this.getUserById(userId);
1134
+ if (!dbUser) {
1135
+ return null;
1136
+ }
1137
+ return this.transformUserRecordToSchema(dbUser);
1138
+ }
1139
+
1140
+ /**
1141
+ * Delete multiple users by IDs
1142
+ */
1143
+ async deleteUsers(userIds: string[]): Promise<number> {
1144
+ const pool = this.getPool();
1145
+ const placeholders = userIds.map((_, i) => `$${i + 1}`).join(',');
1146
+ const result = await pool.query(`DELETE FROM _accounts WHERE id IN (${placeholders})`, userIds);
1147
+
1148
+ return result.rowCount || 0;
1149
+ }
1150
+ }