insforge 0.3.2 → 1.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.cursor/rules/cursor-rules.mdc +94 -0
  3. package/.dockerignore +3 -0
  4. package/.env.example +33 -4
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +13 -60
  6. package/.github/ISSUE_TEMPLATE/config.yml +2 -2
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +10 -63
  8. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  9. package/.github/workflows/build-image.yml +2 -1
  10. package/.github/workflows/e2e.yml +63 -0
  11. package/CHANGELOG.md +41 -0
  12. package/CLAUDE_PLUGIN.md +104 -0
  13. package/CODE_OF_CONDUCT.md +128 -0
  14. package/CONTRIBUTING.md +1 -1
  15. package/Dockerfile +4 -1
  16. package/README.md +66 -18
  17. package/assets/mcpInstallv2.png +0 -0
  18. package/assets/sampleResponse.png +0 -0
  19. package/auth/index.html +13 -0
  20. package/auth/package.json +28 -0
  21. package/auth/public/favicon.ico +0 -0
  22. package/auth/src/App.tsx +33 -0
  23. package/auth/src/components/ErrorCard.tsx +37 -0
  24. package/auth/src/components/Layout.tsx +13 -0
  25. package/auth/src/index.css +19 -0
  26. package/auth/src/lib/broadcastService.ts +115 -0
  27. package/auth/src/lib/utils.ts +11 -0
  28. package/auth/src/main.tsx +22 -0
  29. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  30. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  31. package/auth/src/pages/SignInPage.tsx +57 -0
  32. package/auth/src/pages/SignUpPage.tsx +57 -0
  33. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  34. package/auth/src/vite-env.d.ts +10 -0
  35. package/auth/tsconfig.json +32 -0
  36. package/auth/tsconfig.node.json +11 -0
  37. package/auth/vite.config.ts +25 -0
  38. package/backend/package.json +9 -9
  39. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  40. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  41. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +20 -24
  42. package/backend/src/api/routes/auth/index.routes.ts +570 -0
  43. package/backend/src/api/routes/auth/oauth.routes.ts +448 -0
  44. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +107 -65
  45. package/backend/src/api/routes/database/index.routes.ts +13 -0
  46. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +22 -8
  47. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +20 -23
  48. package/backend/src/api/routes/docs/index.routes.ts +76 -0
  49. package/backend/src/api/routes/functions/index.routes.ts +188 -0
  50. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  51. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +21 -31
  52. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  53. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +34 -53
  54. package/backend/src/api/routes/usage/index.routes.ts +89 -0
  55. package/backend/src/infra/config/app.config.ts +51 -0
  56. package/backend/src/{core/database/manager.ts → infra/database/database.manager.ts} +76 -85
  57. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  58. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  59. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  60. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  61. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  62. package/backend/src/infra/security/token.manager.ts +125 -0
  63. package/backend/src/{core/socket/socket.ts → infra/socket/socket.manager.ts} +15 -15
  64. package/backend/src/providers/ai/openrouter.provider.ts +377 -0
  65. package/backend/src/providers/email/base.provider.ts +41 -0
  66. package/backend/src/providers/email/cloud.provider.ts +187 -0
  67. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  68. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  69. package/backend/src/providers/logs/local.provider.ts +185 -0
  70. package/backend/src/providers/oauth/base.provider.ts +29 -0
  71. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  72. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  73. package/backend/src/providers/oauth/github.provider.ts +208 -0
  74. package/backend/src/providers/oauth/google.provider.ts +249 -0
  75. package/backend/src/providers/oauth/index.ts +7 -0
  76. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  77. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  78. package/backend/src/providers/oauth/x.provider.ts +202 -0
  79. package/backend/src/providers/storage/base.provider.ts +29 -0
  80. package/backend/src/providers/storage/local.provider.ts +103 -0
  81. package/backend/src/providers/storage/s3.provider.ts +313 -0
  82. package/backend/src/server.ts +70 -74
  83. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  84. package/backend/src/services/ai/ai-model.service.ts +60 -0
  85. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  86. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  87. package/backend/src/services/ai/helpers.ts +64 -0
  88. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  89. package/backend/src/services/ai/index.ts +13 -0
  90. package/backend/src/services/auth/auth-config.service.ts +250 -0
  91. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  92. package/backend/src/services/auth/auth.service.ts +1136 -0
  93. package/backend/src/services/auth/index.ts +4 -0
  94. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  95. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  96. package/backend/src/services/database/database-table.service.ts +811 -0
  97. package/backend/src/services/email/email.service.ts +75 -0
  98. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  99. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  100. package/backend/src/services/logs/log.service.ts +73 -0
  101. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  102. package/backend/src/services/storage/storage.service.ts +617 -0
  103. package/backend/src/services/usage/usage.service.ts +149 -0
  104. package/backend/src/types/auth.ts +66 -2
  105. package/backend/src/types/email.ts +8 -0
  106. package/backend/src/types/error-constants.ts +4 -0
  107. package/backend/src/types/logs.ts +0 -29
  108. package/backend/src/{core/socket/types.ts → types/socket.ts} +5 -6
  109. package/backend/src/utils/environment.ts +9 -3
  110. package/backend/src/utils/logger.ts +20 -2
  111. package/backend/src/utils/seed.ts +150 -57
  112. package/backend/src/utils/sql-parser.ts +1 -1
  113. package/backend/src/utils/utils.ts +114 -0
  114. package/backend/src/utils/validations.ts +40 -4
  115. package/backend/tests/local/test-ai-config.sh +129 -0
  116. package/backend/tests/local/test-ai-usage.sh +80 -0
  117. package/backend/tests/local/test-auth-router.sh +1 -1
  118. package/backend/tests/local/test-e2e.sh +1 -1
  119. package/backend/tests/local/test-functions.sh +123 -0
  120. package/backend/tests/local/test-logs.sh +132 -0
  121. package/backend/tests/local/test-public-bucket.sh +3 -3
  122. package/backend/tests/local/test-secrets.sh +14 -12
  123. package/backend/tests/local/test-traditional-rest.sh +2 -2
  124. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  125. package/backend/tests/test-config.sh +37 -1
  126. package/backend/tests/unit/cloud-token.test.ts +48 -0
  127. package/backend/tests/unit/constant.test.ts +8 -0
  128. package/backend/tests/unit/email.test.ts +372 -0
  129. package/backend/tests/unit/environment.test.ts +59 -0
  130. package/backend/tests/unit/helpers.test.ts +63 -0
  131. package/backend/tests/unit/logger.test.ts +22 -0
  132. package/backend/tests/unit/rate-limit.test.ts +154 -0
  133. package/backend/tests/unit/response.test.ts +58 -0
  134. package/backend/tests/unit/sql-parser.test.ts +74 -0
  135. package/backend/tests/unit/uuid.test.ts +21 -0
  136. package/backend/tests/unit/validations.test.ts +80 -0
  137. package/backend/tsconfig.json +1 -1
  138. package/backend/vitest.config.ts +11 -0
  139. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  140. package/claude-plugin/README.md +133 -0
  141. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  142. package/docker-compose.prod.yml +60 -4
  143. package/docker-compose.yml +65 -4
  144. package/docker-init/db/db-init.sql +6 -34
  145. package/docker-init/logs/vector.yml +236 -0
  146. package/docs/README.md +44 -0
  147. package/docs/changelog.mdx +67 -0
  148. package/docs/core-concepts/ai/architecture.mdx +373 -0
  149. package/docs/core-concepts/ai/sdk.mdx +213 -0
  150. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  151. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  152. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  153. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  154. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  155. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  156. package/docs/core-concepts/database/architecture.mdx +256 -0
  157. package/docs/core-concepts/database/sdk.mdx +382 -0
  158. package/docs/core-concepts/functions/architecture.mdx +105 -0
  159. package/docs/core-concepts/functions/sdk.mdx +184 -0
  160. package/docs/core-concepts/storage/architecture.mdx +243 -0
  161. package/docs/core-concepts/storage/sdk.mdx +253 -0
  162. package/docs/deployment/README.md +94 -0
  163. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  164. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  165. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  166. package/docs/deployment/deploy-to-render.md +441 -0
  167. package/docs/docs.json +210 -0
  168. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  169. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  170. package/docs/examples/framework-guides/react.mdx +165 -0
  171. package/docs/examples/framework-guides/svelte.mdx +153 -0
  172. package/docs/examples/framework-guides/vue.mdx +159 -0
  173. package/docs/examples/overview.mdx +67 -0
  174. package/docs/favicon.svg +19 -0
  175. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  176. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  177. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  178. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  179. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  180. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  181. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  182. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  183. package/docs/images/checks-passed.png +0 -0
  184. package/docs/images/dashboard-connect-expanded.png +0 -0
  185. package/docs/images/dashboard-connect.png +0 -0
  186. package/docs/images/hero-dark.png +0 -0
  187. package/docs/images/hero-light.png +0 -0
  188. package/docs/images/icons/ai.svg +4 -0
  189. package/docs/images/icons/auth.svg +1 -0
  190. package/docs/images/icons/database.svg +1 -0
  191. package/docs/images/icons/function.svg +1 -0
  192. package/docs/images/icons/storage.svg +1 -0
  193. package/docs/images/logos/nextjs.svg +4 -0
  194. package/docs/images/logos/nuxt.svg +4 -0
  195. package/docs/images/logos/react.svg +5 -0
  196. package/docs/images/logos/svelte.svg +4 -0
  197. package/docs/images/logos/vue.svg +5 -0
  198. package/docs/images/mcp-install.png +0 -0
  199. package/docs/images/onboarding-mcp.png +0 -0
  200. package/docs/insforge-instructions-sdk.md +55 -374
  201. package/docs/introduction.mdx +45 -0
  202. package/docs/logo/dark.svg +22 -0
  203. package/docs/logo/light.svg +20 -0
  204. package/docs/partnership.mdx +647 -0
  205. package/docs/quickstart.mdx +83 -0
  206. package/docs/showcase/2048-arena.png +0 -0
  207. package/docs/showcase/framegen-cloud.png +0 -0
  208. package/docs/showcase/line-connect-race.png +0 -0
  209. package/docs/showcase/moment-vibe.png +0 -0
  210. package/docs/showcase/national-flags.png +0 -0
  211. package/docs/showcase/pokemon-vibe.png +0 -0
  212. package/docs/showcase/pure-browse-buy.png +0 -0
  213. package/docs/showcase.mdx +52 -0
  214. package/docs/snippets/sdk-installation.mdx +22 -0
  215. package/docs/snippets/service-icons.mdx +27 -0
  216. package/eslint.config.js +10 -3
  217. package/frontend/package.json +10 -4
  218. package/frontend/src/App.tsx +13 -82
  219. package/frontend/src/assets/icons/connected.svg +3 -0
  220. package/frontend/src/assets/icons/loader.svg +9 -0
  221. package/frontend/src/assets/logos/apple.svg +4 -0
  222. package/frontend/src/assets/logos/discord.svg +1 -1
  223. package/frontend/src/assets/logos/facebook.svg +3 -0
  224. package/frontend/src/assets/logos/instagram.svg +2 -0
  225. package/frontend/src/assets/logos/linkedin.svg +3 -0
  226. package/frontend/src/assets/logos/microsoft.svg +1 -0
  227. package/frontend/src/assets/logos/spotify.svg +17 -0
  228. package/frontend/src/assets/logos/tiktok.svg +6 -0
  229. package/frontend/src/assets/logos/x.svg +3 -0
  230. package/frontend/src/components/Checkbox.tsx +27 -29
  231. package/frontend/src/components/CodeBlock.tsx +55 -2
  232. package/frontend/src/components/CodeEditor.tsx +92 -0
  233. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  234. package/frontend/src/components/ConnectCTA.tsx +38 -0
  235. package/frontend/src/components/CopyButton.tsx +52 -15
  236. package/frontend/src/components/ErrorState.tsx +1 -2
  237. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  238. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  239. package/frontend/src/components/JsonHighlight.tsx +21 -9
  240. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  241. package/frontend/src/components/PromptDialog.tsx +1 -4
  242. package/frontend/src/components/SearchInput.tsx +1 -2
  243. package/frontend/src/components/Stepper.tsx +53 -0
  244. package/frontend/src/components/ThemeToggle.tsx +3 -3
  245. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  246. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  247. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  248. package/frontend/src/components/datagrid/index.ts +23 -0
  249. package/frontend/src/components/index.ts +23 -30
  250. package/frontend/src/components/layout/AppHeader.tsx +133 -92
  251. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  252. package/frontend/src/components/layout/Layout.tsx +12 -23
  253. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  254. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  255. package/frontend/src/components/layout/index.ts +5 -0
  256. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  257. package/frontend/src/components/radix/index.ts +22 -0
  258. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  259. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  260. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  261. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  262. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  263. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  264. package/frontend/src/features/ai/components/index.ts +6 -0
  265. package/frontend/src/features/ai/helpers.ts +57 -71
  266. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  267. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  268. package/frontend/src/features/ai/page/AIPage.tsx +67 -79
  269. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  270. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  271. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +53 -30
  272. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  273. package/frontend/src/features/auth/components/UsersDataGrid.tsx +44 -14
  274. package/frontend/src/features/auth/components/index.ts +5 -0
  275. package/frontend/src/features/auth/helpers.tsx +200 -0
  276. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  277. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  278. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  279. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  280. package/frontend/src/features/auth/index.ts +3 -2
  281. package/frontend/src/features/auth/page/AuthMethodsPage.tsx +275 -0
  282. package/frontend/src/features/auth/page/ConfigurationPage.tsx +395 -0
  283. package/frontend/src/features/auth/page/UsersPage.tsx +285 -0
  284. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  285. package/frontend/src/features/auth/services/config.service.ts +19 -0
  286. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  287. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  288. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  289. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  290. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  291. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  292. package/frontend/src/features/dashboard/components/index.ts +4 -0
  293. package/frontend/src/features/dashboard/page/DashboardPage.tsx +187 -169
  294. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  295. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  296. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  297. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  298. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  299. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  300. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  301. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  302. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  303. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  304. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  305. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  306. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  307. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  308. package/frontend/src/features/database/components/TableForm.tsx +28 -15
  309. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  310. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  311. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  312. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  313. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  314. package/frontend/src/features/database/components/index.ts +19 -0
  315. package/frontend/src/features/database/constants.ts +28 -2
  316. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  317. package/frontend/src/features/database/helpers.ts +2 -2
  318. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  319. package/frontend/src/features/database/hooks/useFullMetadata.ts +18 -0
  320. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  321. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  322. package/frontend/src/features/database/hooks/useTables.ts +131 -0
  323. package/frontend/src/features/database/index.ts +6 -1
  324. package/frontend/src/features/database/page/FunctionsPage.tsx +211 -0
  325. package/frontend/src/features/database/page/IndexesPage.tsx +240 -0
  326. package/frontend/src/features/database/page/PoliciesPage.tsx +248 -0
  327. package/frontend/src/features/database/page/SQLEditorPage.tsx +382 -0
  328. package/frontend/src/features/database/page/{DatabasePage.tsx → TablesPage.tsx} +186 -185
  329. package/frontend/src/features/database/page/TemplatesPage.tsx +39 -0
  330. package/frontend/src/features/database/page/TriggersPage.tsx +242 -0
  331. package/frontend/src/features/database/services/advance.service.ts +66 -0
  332. package/frontend/src/features/database/services/{database.service.ts → record.service.ts} +67 -64
  333. package/frontend/src/features/database/services/table.service.ts +64 -0
  334. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  335. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  336. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  337. package/frontend/src/features/database/templates/index.ts +34 -0
  338. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  339. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  340. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  341. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  342. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  343. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  344. package/frontend/src/features/functions/components/index.ts +5 -0
  345. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  346. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  347. package/frontend/src/features/functions/page/FunctionsPage.tsx +160 -17
  348. package/frontend/src/features/functions/{components/SecretsContent.tsx → page/SecretsPage.tsx} +8 -12
  349. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  350. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  351. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  352. package/frontend/src/features/login/page/CloudLoginPage.tsx +79 -54
  353. package/frontend/src/features/login/page/LoginPage.tsx +16 -23
  354. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  355. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  356. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  357. package/frontend/src/features/logs/components/index.ts +2 -0
  358. package/frontend/src/features/logs/helpers.ts +24 -0
  359. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  360. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  361. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  362. package/frontend/src/features/logs/hooks/useMcpUsage.ts +181 -0
  363. package/frontend/src/features/logs/index.ts +8 -2
  364. package/frontend/src/features/logs/page/AuditsPage.tsx +91 -38
  365. package/frontend/src/features/logs/page/LogsPage.tsx +152 -0
  366. package/frontend/src/features/logs/page/MCPLogsPage.tsx +84 -0
  367. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  368. package/frontend/src/features/logs/services/log.service.ts +15 -110
  369. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  370. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  371. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  372. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  373. package/frontend/src/features/onboard/components/index.ts +4 -0
  374. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  375. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  376. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  377. package/frontend/src/features/onboard/index.ts +13 -3
  378. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  379. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  380. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  381. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  382. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  383. package/frontend/src/features/storage/components/index.ts +12 -0
  384. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  385. package/frontend/src/features/storage/page/StoragePage.tsx +41 -115
  386. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  387. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  388. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  389. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  390. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  391. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  392. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +33 -29
  393. package/frontend/src/index.css +1 -0
  394. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  395. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  396. package/frontend/src/lib/contexts/SocketContext.tsx +5 -6
  397. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  398. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  399. package/frontend/src/lib/routing/AppRoutes.tsx +84 -0
  400. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  401. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  402. package/frontend/src/lib/utils/menuItems.ts +183 -0
  403. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  404. package/frontend/src/lib/utils/utils.ts +19 -1
  405. package/frontend/src/vite-env.d.ts +1 -0
  406. package/frontend/vite.config.ts +5 -3
  407. package/functions/server.ts +28 -3
  408. package/functions/worker-template.js +15 -4
  409. package/i18n/README.ar.md +130 -0
  410. package/i18n/README.de.md +130 -0
  411. package/i18n/README.es.md +154 -0
  412. package/i18n/README.fr.md +134 -0
  413. package/i18n/README.hi.md +129 -0
  414. package/i18n/README.ja.md +174 -0
  415. package/i18n/README.ko.md +137 -0
  416. package/i18n/README.pt-BR.md +131 -0
  417. package/i18n/README.ru.md +129 -0
  418. package/i18n/README.zh-CN.md +133 -0
  419. package/openapi/ai.yaml +31 -4
  420. package/openapi/auth.yaml +827 -146
  421. package/package.json +16 -7
  422. package/shared-schemas/package.json +1 -1
  423. package/shared-schemas/src/ai-api.schema.ts +34 -58
  424. package/shared-schemas/src/ai.schema.ts +5 -0
  425. package/shared-schemas/src/auth-api.schema.ts +154 -8
  426. package/shared-schemas/src/auth.schema.ts +42 -6
  427. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  428. package/shared-schemas/src/database-api.schema.ts +3 -3
  429. package/shared-schemas/src/database.schema.ts +1 -1
  430. package/shared-schemas/src/index.ts +1 -0
  431. package/shared-schemas/src/logs-api.schema.ts +7 -1
  432. package/shared-schemas/src/logs.schema.ts +26 -0
  433. package/shared-schemas/src/metadata.schema.ts +9 -4
  434. package/test-gemini.sh +35 -0
  435. package/test-usage-admin.sh +57 -0
  436. package/test-usage.sh +50 -0
  437. package/zeabur/README.md +13 -0
  438. package/zeabur/template.yml +1032 -0
  439. package/.github/workflows/deploy-aws.yml +0 -130
  440. package/backend/src/api/routes/agent.ts +0 -29
  441. package/backend/src/api/routes/auth.oauth.ts +0 -482
  442. package/backend/src/api/routes/auth.ts +0 -386
  443. package/backend/src/api/routes/docs.ts +0 -66
  444. package/backend/src/api/routes/functions.ts +0 -183
  445. package/backend/src/api/routes/openapi.ts +0 -82
  446. package/backend/src/api/routes/usage.ts +0 -96
  447. package/backend/src/core/ai/client.ts +0 -242
  448. package/backend/src/core/ai/model.ts +0 -117
  449. package/backend/src/core/auth/auth.ts +0 -781
  450. package/backend/src/core/database/table.ts +0 -772
  451. package/backend/src/core/documentation/agent.ts +0 -689
  452. package/backend/src/core/documentation/openapi.ts +0 -856
  453. package/backend/src/core/logs/analytics.ts +0 -76
  454. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  455. package/backend/src/core/storage/storage.ts +0 -923
  456. package/backend/src/utils/cloud-token.ts +0 -39
  457. package/backend/src/utils/helpers.ts +0 -49
  458. package/backend/src/utils/uuid.ts +0 -9
  459. package/backend/tests/manual/test-better-auth.sh +0 -303
  460. package/docker-init/db/logs.sql +0 -9
  461. package/frontend/README.md +0 -112
  462. package/frontend/src/components/datagrid/index.tsx +0 -20
  463. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  464. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  465. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  466. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  467. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  468. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  469. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  470. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  471. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  472. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  473. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  474. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  475. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  476. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  477. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  478. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  479. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  480. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  481. package/frontend/src/features/metadata/index.ts +0 -0
  482. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  483. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  484. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  485. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  486. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  487. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  488. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  489. package/frontend/src/features/onboard/types.ts +0 -8
  490. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  491. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  492. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  493. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  494. /package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +0 -0
  495. /package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +0 -0
  496. /package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +0 -0
  497. /package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +0 -0
  498. /package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +0 -0
  499. /package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +0 -0
  500. /package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +0 -0
  501. /package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +0 -0
  502. /package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +0 -0
  503. /package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +0 -0
  504. /package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +0 -0
  505. /package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +0 -0
  506. /package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +0 -0
  507. /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
@@ -0,0 +1,250 @@
1
+ import { Pool } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import { AppError } from '@/api/middlewares/error.js';
4
+ import { ERROR_CODES } from '@/types/error-constants.js';
5
+ import logger from '@/utils/logger.js';
6
+ import type { AuthConfigSchema, UpdateAuthConfigRequest } from '@insforge/shared-schemas';
7
+
8
+ export class AuthConfigService {
9
+ private static instance: AuthConfigService;
10
+ private pool: Pool | null = null;
11
+
12
+ private constructor() {
13
+ logger.info('AuthConfigService initialized');
14
+ }
15
+
16
+ public static getInstance(): AuthConfigService {
17
+ if (!AuthConfigService.instance) {
18
+ AuthConfigService.instance = new AuthConfigService();
19
+ }
20
+ return AuthConfigService.instance;
21
+ }
22
+
23
+ private getPool(): Pool {
24
+ if (!this.pool) {
25
+ this.pool = DatabaseManager.getInstance().getPool();
26
+ }
27
+ return this.pool;
28
+ }
29
+
30
+ /**
31
+ * Get public authentication configuration (safe for public API)
32
+ * Returns all configuration fields except metadata (id, created_at, updated_at)
33
+ */
34
+ async getPublicAuthConfig() {
35
+ try {
36
+ const result = await this.getPool().query(
37
+ `SELECT
38
+ require_email_verification as "requireEmailVerification",
39
+ password_min_length as "passwordMinLength",
40
+ require_number as "requireNumber",
41
+ require_lowercase as "requireLowercase",
42
+ require_uppercase as "requireUppercase",
43
+ require_special_char as "requireSpecialChar",
44
+ verify_email_method as "verifyEmailMethod",
45
+ reset_password_method as "resetPasswordMethod"
46
+ FROM _auth_configs
47
+ LIMIT 1`
48
+ );
49
+
50
+ // If no config exists, return fallback values
51
+ if (!result.rows.length) {
52
+ logger.warn('No auth config found, returning default fallback values');
53
+ return {
54
+ requireEmailVerification: false,
55
+ passwordMinLength: 6,
56
+ requireNumber: false,
57
+ requireLowercase: false,
58
+ requireUppercase: false,
59
+ requireSpecialChar: false,
60
+ verifyEmailMethod: 'code' as const,
61
+ resetPasswordMethod: 'code' as const,
62
+ };
63
+ }
64
+
65
+ return result.rows[0];
66
+ } catch (error) {
67
+ logger.error('Failed to get public auth config', { error });
68
+ throw new AppError(
69
+ 'Failed to get authentication configuration',
70
+ 500,
71
+ ERROR_CODES.INTERNAL_ERROR
72
+ );
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get authentication configuration
78
+ * Returns the singleton configuration row with all columns
79
+ */
80
+ async getAuthConfig(): Promise<AuthConfigSchema> {
81
+ try {
82
+ const result = await this.getPool().query(
83
+ `SELECT
84
+ id,
85
+ require_email_verification as "requireEmailVerification",
86
+ password_min_length as "passwordMinLength",
87
+ require_number as "requireNumber",
88
+ require_lowercase as "requireLowercase",
89
+ require_uppercase as "requireUppercase",
90
+ require_special_char as "requireSpecialChar",
91
+ verify_email_method as "verifyEmailMethod",
92
+ reset_password_method as "resetPasswordMethod",
93
+ sign_in_redirect_to as "signInRedirectTo",
94
+ created_at as "createdAt",
95
+ updated_at as "updatedAt"
96
+ FROM _auth_configs
97
+ LIMIT 1`
98
+ );
99
+
100
+ // If no config exists, return fallback values
101
+ if (!result.rows.length) {
102
+ logger.warn('No auth config found, returning default fallback values');
103
+ // Return a config with fallback values and generate a temporary ID
104
+ return {
105
+ id: '00000000-0000-0000-0000-000000000000',
106
+ requireEmailVerification: false,
107
+ passwordMinLength: 6,
108
+ requireNumber: false,
109
+ requireLowercase: false,
110
+ requireUppercase: false,
111
+ requireSpecialChar: false,
112
+ verifyEmailMethod: 'code' as const,
113
+ resetPasswordMethod: 'code' as const,
114
+ signInRedirectTo: null,
115
+ createdAt: new Date().toISOString(),
116
+ updatedAt: new Date().toISOString(),
117
+ };
118
+ }
119
+
120
+ return result.rows[0];
121
+ } catch (error) {
122
+ logger.error('Failed to get auth config', { error });
123
+ throw new AppError(
124
+ 'Failed to get authentication configuration',
125
+ 500,
126
+ ERROR_CODES.INTERNAL_ERROR
127
+ );
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Update authentication configuration
133
+ * Updates the singleton configuration row
134
+ */
135
+ async updateAuthConfig(input: UpdateAuthConfigRequest): Promise<AuthConfigSchema> {
136
+ const client = await this.getPool().connect();
137
+ try {
138
+ await client.query('BEGIN');
139
+
140
+ // Ensure config exists and lock row to prevent concurrent modifications
141
+ const existingResult = await client.query('SELECT id FROM _auth_configs LIMIT 1 FOR UPDATE');
142
+
143
+ if (!existingResult.rows.length) {
144
+ // Config doesn't exist, rollback and throw error
145
+ // The migration should have created the default config
146
+ await client.query('ROLLBACK');
147
+ throw new AppError(
148
+ 'Authentication configuration not found. Please run migrations.',
149
+ 500,
150
+ ERROR_CODES.INTERNAL_ERROR
151
+ );
152
+ }
153
+
154
+ // Build update query
155
+ const updates: string[] = [];
156
+ const values: (string | number | boolean | null)[] = [];
157
+ let paramCount = 1;
158
+
159
+ if (input.requireEmailVerification !== undefined) {
160
+ updates.push(`require_email_verification = $${paramCount++}`);
161
+ values.push(input.requireEmailVerification);
162
+ }
163
+
164
+ if (input.passwordMinLength !== undefined) {
165
+ updates.push(`password_min_length = $${paramCount++}`);
166
+ values.push(input.passwordMinLength);
167
+ }
168
+
169
+ if (input.requireNumber !== undefined) {
170
+ updates.push(`require_number = $${paramCount++}`);
171
+ values.push(input.requireNumber);
172
+ }
173
+
174
+ if (input.requireLowercase !== undefined) {
175
+ updates.push(`require_lowercase = $${paramCount++}`);
176
+ values.push(input.requireLowercase);
177
+ }
178
+
179
+ if (input.requireUppercase !== undefined) {
180
+ updates.push(`require_uppercase = $${paramCount++}`);
181
+ values.push(input.requireUppercase);
182
+ }
183
+
184
+ if (input.requireSpecialChar !== undefined) {
185
+ updates.push(`require_special_char = $${paramCount++}`);
186
+ values.push(input.requireSpecialChar);
187
+ }
188
+
189
+ if (input.verifyEmailMethod !== undefined) {
190
+ updates.push(`verify_email_method = $${paramCount++}`);
191
+ values.push(input.verifyEmailMethod);
192
+ }
193
+
194
+ if (input.resetPasswordMethod !== undefined) {
195
+ updates.push(`reset_password_method = $${paramCount++}`);
196
+ values.push(input.resetPasswordMethod);
197
+ }
198
+
199
+ if (input.signInRedirectTo !== undefined) {
200
+ updates.push(`sign_in_redirect_to = $${paramCount++}`);
201
+ values.push(input.signInRedirectTo);
202
+ }
203
+
204
+ if (!updates.length) {
205
+ await client.query('COMMIT');
206
+ // Return current config if no updates
207
+ return await this.getAuthConfig();
208
+ }
209
+
210
+ // Add updated_at to updates
211
+ updates.push('updated_at = NOW()');
212
+
213
+ const result = await client.query(
214
+ `UPDATE _auth_configs
215
+ SET ${updates.join(', ')}
216
+ RETURNING
217
+ id,
218
+ require_email_verification as "requireEmailVerification",
219
+ password_min_length as "passwordMinLength",
220
+ require_number as "requireNumber",
221
+ require_lowercase as "requireLowercase",
222
+ require_uppercase as "requireUppercase",
223
+ require_special_char as "requireSpecialChar",
224
+ verify_email_method as "verifyEmailMethod",
225
+ reset_password_method as "resetPasswordMethod",
226
+ sign_in_redirect_to as "signInRedirectTo",
227
+ created_at as "createdAt",
228
+ updated_at as "updatedAt"`,
229
+ values
230
+ );
231
+
232
+ await client.query('COMMIT');
233
+ logger.info('Auth config updated', { updatedFields: Object.keys(input) });
234
+ return result.rows[0];
235
+ } catch (error) {
236
+ await client.query('ROLLBACK');
237
+ logger.error('Failed to update auth config', { error });
238
+ if (error instanceof AppError) {
239
+ throw error;
240
+ }
241
+ throw new AppError(
242
+ 'Failed to update authentication configuration',
243
+ 500,
244
+ ERROR_CODES.INTERNAL_ERROR
245
+ );
246
+ } finally {
247
+ client.release();
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,424 @@
1
+ import { Pool, PoolClient } from 'pg';
2
+ import bcrypt from 'bcryptjs';
3
+ import crypto from 'crypto';
4
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
5
+ import { AppError } from '@/api/middlewares/error.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import logger from '@/utils/logger.js';
8
+ import { generateNumericCode, generateSecureToken } from '@/utils/utils.js';
9
+
10
+ /**
11
+ * OTP purpose types - used to categorize different OTP use cases
12
+ */
13
+ export enum OTPPurpose {
14
+ VERIFY_EMAIL = 'VERIFY_EMAIL',
15
+ RESET_PASSWORD = 'RESET_PASSWORD',
16
+ }
17
+
18
+ /**
19
+ * Token type - determines token format and expiration
20
+ */
21
+ export enum OTPType {
22
+ NUMERIC_CODE = 'NUMERIC_CODE', // Short 6-digit numeric code for manual entry
23
+ HASH_TOKEN = 'HASH_TOKEN', // Long cryptographic token with hash-based lookup
24
+ }
25
+
26
+ /**
27
+ * Result of OTP creation
28
+ */
29
+ export interface CreateOTPResult {
30
+ success: boolean;
31
+ otp: string;
32
+ expiresAt: Date;
33
+ }
34
+
35
+ /**
36
+ * Result of OTP verification
37
+ */
38
+ export interface VerifyOTPResult {
39
+ success: boolean;
40
+ email: string;
41
+ purpose: OTPPurpose;
42
+ }
43
+
44
+ /**
45
+ * Service for managing email-based one-time passwords (OTPs)
46
+ *
47
+ * Supports two delivery methods:
48
+ * 1. Short numeric codes (6 digits) - displayed in email for manual entry
49
+ * - Stored as bcrypt hash (defense against brute force if DB compromised)
50
+ * - Brute force protection handled by API-level rate limiting
51
+ * 2. Long cryptographic tokens (64 chars) - embedded in clickable links for one-click verification
52
+ * - Stored as SHA-256 hash (high entropy makes bcrypt unnecessary, allows direct lookup)
53
+ *
54
+ * The dual hashing strategy balances security and performance:
55
+ * - NUMERIC_CODE: Low entropy (10^6 combinations) requires slow bcrypt + API rate limiting
56
+ * - HASH_TOKEN: High entropy (2^256 combinations) only needs fast SHA-256
57
+ */
58
+ export class AuthOTPService {
59
+ private static instance: AuthOTPService;
60
+ private pool: Pool | null = null;
61
+
62
+ // Configuration constants
63
+ private readonly NUMERIC_CODE_LENGTH = 6; // 6 digits = 1 million combinations
64
+ private readonly NUMERIC_CODE_EXPIRY_MINUTES = 15; // 15 minutes expiry for numeric codes
65
+ private readonly HASH_TOKEN_BYTES = 32; // 32 bytes = 64 hex characters = 256 bits entropy
66
+ private readonly HASH_TOKEN_EXPIRY_HOURS = 24; // 24 hours expiry for hash tokens
67
+ private readonly BCRYPT_SALT_ROUNDS = 10; // Salt rounds for numeric codes (2^10 iterations)
68
+
69
+ private constructor() {
70
+ logger.info('AuthOTPService initialized');
71
+ }
72
+
73
+ public static getInstance(): AuthOTPService {
74
+ if (!AuthOTPService.instance) {
75
+ AuthOTPService.instance = new AuthOTPService();
76
+ }
77
+ return AuthOTPService.instance;
78
+ }
79
+
80
+ private getPool(): Pool {
81
+ if (!this.pool) {
82
+ this.pool = DatabaseManager.getInstance().getPool();
83
+ }
84
+ return this.pool;
85
+ }
86
+
87
+ /**
88
+ * Create or update an email OTP
89
+ * Supports both short numeric codes (for manual entry) and long cryptographic tokens (for clickable links)
90
+ * Uses upsert to ensure only one active token exists per email/purpose combination
91
+ *
92
+ * Hashing strategy:
93
+ * - NUMERIC_CODE: Uses bcrypt (slow hash) due to low entropy (10^6 combinations)
94
+ * - HASH_TOKEN: Uses SHA-256 (fast hash) - high entropy (2^256) makes bcrypt unnecessary
95
+ *
96
+ * @param email - The email address for the token
97
+ * @param purpose - The purpose of the token (e.g., 'VERIFY_EMAIL', 'RESET_PASSWORD')
98
+ * @param otpType - The type of token to generate ('NUMERIC_CODE' or 'HASH_TOKEN')
99
+ * @returns Promise with creation result including the token and expiry time
100
+ */
101
+ async createEmailOTP(
102
+ email: string,
103
+ purpose: OTPPurpose,
104
+ otpType: OTPType = OTPType.NUMERIC_CODE
105
+ ): Promise<CreateOTPResult> {
106
+ try {
107
+ // Generate token based on type
108
+ let otp: string;
109
+ let expiresAt: Date;
110
+ let otpHash: string;
111
+
112
+ if (otpType === OTPType.NUMERIC_CODE) {
113
+ // Generate 6-digit numeric code for manual entry
114
+ otp = generateNumericCode(this.NUMERIC_CODE_LENGTH);
115
+ expiresAt = new Date(Date.now() + this.NUMERIC_CODE_EXPIRY_MINUTES * 60 * 1000);
116
+ // Use bcrypt for low-entropy codes (defense against brute force)
117
+ otpHash = await bcrypt.hash(otp, this.BCRYPT_SALT_ROUNDS);
118
+ } else {
119
+ // Generate cryptographically secure token for hash-based lookup
120
+ otp = generateSecureToken(this.HASH_TOKEN_BYTES);
121
+ expiresAt = new Date(Date.now() + this.HASH_TOKEN_EXPIRY_HOURS * 60 * 60 * 1000);
122
+ // Use SHA-256 for high-entropy tokens (enables direct lookup)
123
+ otpHash = crypto.createHash('sha256').update(otp).digest('hex');
124
+ }
125
+
126
+ // Upsert token record - insert or update if email+purpose combination already exists
127
+ // This ensures only one active token per email/purpose (replaces any existing token)
128
+ await this.getPool().query(
129
+ `INSERT INTO _email_otps (email, purpose, otp_hash, expires_at, consumed_at)
130
+ VALUES ($1, $2, $3, $4, NULL)
131
+ ON CONFLICT (email, purpose)
132
+ DO UPDATE SET
133
+ otp_hash = EXCLUDED.otp_hash,
134
+ expires_at = EXCLUDED.expires_at,
135
+ consumed_at = NULL,
136
+ updated_at = NOW()`,
137
+ [email, purpose, otpHash, expiresAt]
138
+ );
139
+
140
+ logger.info('Email verification token created successfully', {
141
+ purpose,
142
+ otpType,
143
+ expiresAt: expiresAt.toISOString(),
144
+ });
145
+
146
+ return {
147
+ success: true,
148
+ otp,
149
+ expiresAt,
150
+ };
151
+ } catch (error) {
152
+ logger.error('Failed to create email verification token', { error, purpose, otpType });
153
+ throw new AppError('Failed to create verification token', 500, ERROR_CODES.INTERNAL_ERROR);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Verify a numeric OTP code (6 digits)
159
+ * Looks up by email and verifies the bcrypt-hashed code
160
+ *
161
+ * Brute force protection is handled by API-level rate limiting.
162
+ *
163
+ * @param email - The email address associated with the OTP
164
+ * @param purpose - The purpose of the OTP
165
+ * @param code - The 6-digit numeric code to verify
166
+ * @param externalClient - Optional external database client for transaction support
167
+ * @returns Promise with verification result
168
+ * @throws AppError if verification fails (with generic error message)
169
+ */
170
+ async verifyEmailOTPWithCode(
171
+ email: string,
172
+ purpose: OTPPurpose,
173
+ code: string,
174
+ externalClient?: PoolClient
175
+ ): Promise<VerifyOTPResult> {
176
+ const client = externalClient || (await this.getPool().connect());
177
+ const shouldManageTransaction = !externalClient;
178
+
179
+ try {
180
+ if (shouldManageTransaction) {
181
+ await client.query('BEGIN');
182
+ }
183
+
184
+ // Lookup by email and lock the row
185
+ const result = await client.query(
186
+ `SELECT id, email, purpose, otp_hash, expires_at, consumed_at
187
+ FROM _email_otps
188
+ WHERE email = $1 AND purpose = $2
189
+ FOR UPDATE`,
190
+ [email, purpose]
191
+ );
192
+
193
+ // Check if OTP record exists
194
+ if (result.rows.length === 0) {
195
+ throw new AppError('Invalid or expired verification code', 400, ERROR_CODES.INVALID_INPUT);
196
+ }
197
+
198
+ const otpRecord = result.rows[0];
199
+
200
+ // Validate OTP record is still usable
201
+ if (new Date() > new Date(otpRecord.expires_at) || otpRecord.consumed_at !== null) {
202
+ throw new AppError('Invalid or expired verification code', 400, ERROR_CODES.INVALID_INPUT);
203
+ }
204
+
205
+ // Verify bcrypt hash
206
+ const isValid = await bcrypt.compare(code, otpRecord.otp_hash);
207
+
208
+ if (!isValid) {
209
+ throw new AppError('Invalid or expired verification code', 400, ERROR_CODES.INVALID_INPUT);
210
+ }
211
+
212
+ // Mark OTP as consumed atomically
213
+ const consume = await client.query(
214
+ `UPDATE _email_otps
215
+ SET consumed_at = NOW(), updated_at = NOW()
216
+ WHERE id = $1 AND consumed_at IS NULL`,
217
+ [otpRecord.id]
218
+ );
219
+
220
+ if (consume.rowCount !== 1) {
221
+ throw new AppError('Invalid or expired verification code', 400, ERROR_CODES.INVALID_INPUT);
222
+ }
223
+
224
+ if (shouldManageTransaction) {
225
+ await client.query('COMMIT');
226
+ }
227
+
228
+ logger.info('Numeric OTP code verified successfully', { purpose });
229
+
230
+ return {
231
+ success: true,
232
+ email: otpRecord.email,
233
+ purpose: otpRecord.purpose,
234
+ };
235
+ } catch (error) {
236
+ if (shouldManageTransaction) {
237
+ await client.query('ROLLBACK');
238
+ }
239
+
240
+ if (error instanceof AppError) {
241
+ throw error;
242
+ }
243
+
244
+ logger.error('Failed to verify numeric OTP code', { error, purpose });
245
+ throw new AppError('Failed to verify code', 500, ERROR_CODES.INTERNAL_ERROR);
246
+ } finally {
247
+ if (shouldManageTransaction) {
248
+ client.release();
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Verify a hash token (64 hex characters)
255
+ * Performs direct lookup using SHA-256 hash without knowing the email
256
+ *
257
+ * @param purpose - The purpose of the OTP
258
+ * @param token - The 64-character hex token to verify
259
+ * @param externalClient - Optional external database client for transaction support
260
+ * @returns Promise with verification result including the associated email
261
+ * @throws AppError if verification fails (with generic error message)
262
+ */
263
+ async verifyEmailOTPWithToken(
264
+ purpose: OTPPurpose,
265
+ token: string,
266
+ externalClient?: PoolClient
267
+ ): Promise<VerifyOTPResult> {
268
+ const client = externalClient || (await this.getPool().connect());
269
+ const shouldManageTransaction = !externalClient;
270
+
271
+ try {
272
+ if (shouldManageTransaction) {
273
+ await client.query('BEGIN');
274
+ }
275
+
276
+ // Hash the token and perform direct lookup
277
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
278
+
279
+ // Direct lookup by hash - O(1) with index on otp_hash
280
+ const result = await client.query(
281
+ `SELECT id, email, purpose, otp_hash, expires_at, consumed_at
282
+ FROM _email_otps
283
+ WHERE purpose = $1
284
+ AND otp_hash = $2
285
+ AND expires_at > NOW()
286
+ AND consumed_at IS NULL
287
+ FOR UPDATE`,
288
+ [purpose, tokenHash]
289
+ );
290
+
291
+ // Check if token exists and is valid
292
+ if (result.rows.length === 0) {
293
+ throw new AppError('Invalid or expired verification token', 400, ERROR_CODES.INVALID_INPUT);
294
+ }
295
+
296
+ const otpRecord = result.rows[0];
297
+
298
+ // Mark OTP as consumed atomically
299
+ const consume = await client.query(
300
+ `UPDATE _email_otps
301
+ SET consumed_at = NOW(), updated_at = NOW()
302
+ WHERE id = $1 AND consumed_at IS NULL`,
303
+ [otpRecord.id]
304
+ );
305
+
306
+ if (consume.rowCount !== 1) {
307
+ throw new AppError('Invalid or expired verification token', 400, ERROR_CODES.INVALID_INPUT);
308
+ }
309
+
310
+ if (shouldManageTransaction) {
311
+ await client.query('COMMIT');
312
+ }
313
+
314
+ logger.info('Hash token verified successfully', { purpose });
315
+
316
+ return {
317
+ success: true,
318
+ email: otpRecord.email,
319
+ purpose: otpRecord.purpose,
320
+ };
321
+ } catch (error) {
322
+ if (shouldManageTransaction) {
323
+ await client.query('ROLLBACK');
324
+ }
325
+
326
+ if (error instanceof AppError) {
327
+ throw error;
328
+ }
329
+
330
+ logger.error('Failed to verify hash token', { error, purpose });
331
+ throw new AppError('Failed to verify token', 500, ERROR_CODES.INTERNAL_ERROR);
332
+ } finally {
333
+ if (shouldManageTransaction) {
334
+ client.release();
335
+ }
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Exchange a verified numeric code for a long-lived hash token
341
+ * This is a common pattern in multi-step verification flows:
342
+ * 1. User receives numeric code via email
343
+ * 2. User submits code to verify
344
+ * 3. System issues a long-lived token for subsequent operations
345
+ *
346
+ * The entire exchange happens atomically within a single transaction to ensure:
347
+ * - Numeric code is consumed only if token creation succeeds
348
+ * - No race conditions between verification and token issuance
349
+ *
350
+ * Example use cases:
351
+ * - Password reset: verify code → get reset token → reset password
352
+ * - Email verification: verify code → get session token → auto-login
353
+ *
354
+ * @param email - The email address associated with the code
355
+ * @param purpose - The purpose of the OTP (e.g., RESET_PASSWORD)
356
+ * @param numericCode - The 6-digit numeric code to verify
357
+ * @param externalClient - Optional external database client for broader transaction support
358
+ * @returns Promise with the long-lived token and its expiration
359
+ * @throws AppError if verification fails or token creation fails
360
+ */
361
+ async exchangeCodeForToken(
362
+ email: string,
363
+ purpose: OTPPurpose,
364
+ numericCode: string,
365
+ externalClient?: PoolClient
366
+ ): Promise<{ token: string; expiresAt: Date }> {
367
+ const client = externalClient || (await this.getPool().connect());
368
+ const shouldManageTransaction = !externalClient;
369
+ let transactionActive = false;
370
+
371
+ try {
372
+ if (shouldManageTransaction) {
373
+ await client.query('BEGIN');
374
+ transactionActive = true;
375
+ }
376
+
377
+ // Step 1: Verify the numeric code (consumes it atomically)
378
+ await this.verifyEmailOTPWithCode(email, purpose, numericCode, client);
379
+
380
+ // Step 2: Generate a long-lived hash token
381
+ const token = generateSecureToken(this.HASH_TOKEN_BYTES);
382
+ const expiresAt = new Date(Date.now() + this.HASH_TOKEN_EXPIRY_HOURS * 60 * 60 * 1000);
383
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
384
+
385
+ // Step 3: Insert the new token (replaces the consumed numeric code)
386
+ // Uses upsert to overwrite the consumed code record with the new token
387
+ await client.query(
388
+ `INSERT INTO _email_otps (email, purpose, otp_hash, expires_at, consumed_at)
389
+ VALUES ($1, $2, $3, $4, NULL)
390
+ ON CONFLICT (email, purpose)
391
+ DO UPDATE SET
392
+ otp_hash = EXCLUDED.otp_hash,
393
+ expires_at = EXCLUDED.expires_at,
394
+ consumed_at = NULL,
395
+ updated_at = NOW()`,
396
+ [email, purpose, tokenHash, expiresAt]
397
+ );
398
+
399
+ if (shouldManageTransaction) {
400
+ await client.query('COMMIT');
401
+ transactionActive = false;
402
+ }
403
+
404
+ logger.info('Successfully exchanged numeric code for hash token', { email, purpose });
405
+
406
+ return { token, expiresAt };
407
+ } catch (error) {
408
+ if (shouldManageTransaction && transactionActive) {
409
+ await client.query('ROLLBACK');
410
+ }
411
+
412
+ if (error instanceof AppError) {
413
+ throw error;
414
+ }
415
+
416
+ logger.error('Failed to exchange code for token', { error, email, purpose });
417
+ throw new AppError('Failed to exchange verification code', 500, ERROR_CODES.INTERNAL_ERROR);
418
+ } finally {
419
+ if (shouldManageTransaction) {
420
+ client.release();
421
+ }
422
+ }
423
+ }
424
+ }