insforge 1.3.0 → 1.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/auth/package.json +5 -3
  3. package/auth/src/lib/broadcastService.ts +115 -117
  4. package/auth/src/lib/insforge.ts +8 -0
  5. package/auth/src/main.tsx +2 -4
  6. package/auth/src/pages/SignInPage.tsx +60 -60
  7. package/auth/src/pages/SignUpPage.tsx +60 -60
  8. package/auth/src/pages/VerifyEmailPage.tsx +18 -0
  9. package/auth/tsconfig.json +2 -1
  10. package/backend/package.json +10 -6
  11. package/backend/src/api/middlewares/rate-limiters.ts +127 -127
  12. package/backend/src/api/routes/ai/index.routes.ts +475 -468
  13. package/backend/src/api/routes/auth/index.routes.ts +85 -32
  14. package/backend/src/api/routes/auth/oauth.routes.ts +11 -6
  15. package/backend/src/api/routes/database/index.routes.ts +2 -0
  16. package/backend/src/api/routes/database/records.routes.ts +39 -175
  17. package/backend/src/api/routes/database/rpc.routes.ts +69 -0
  18. package/backend/src/api/routes/deployments/index.routes.ts +192 -0
  19. package/backend/src/api/routes/docs/index.routes.ts +3 -2
  20. package/backend/src/api/routes/email/index.routes.ts +35 -35
  21. package/backend/src/api/routes/functions/index.routes.ts +3 -3
  22. package/backend/src/api/routes/metadata/index.routes.ts +26 -0
  23. package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
  24. package/backend/src/infra/database/database.manager.ts +0 -10
  25. package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
  26. package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
  27. package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
  28. package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
  29. package/backend/src/infra/security/token.manager.ts +1 -4
  30. package/backend/src/providers/ai/openrouter.provider.ts +12 -3
  31. package/backend/src/providers/database/base.provider.ts +39 -0
  32. package/backend/src/providers/database/cloud.provider.ts +159 -0
  33. package/backend/src/providers/deployments/vercel.provider.ts +516 -0
  34. package/backend/src/server.ts +19 -7
  35. package/backend/src/services/ai/ai-config.service.ts +6 -6
  36. package/backend/src/services/ai/ai-model.service.ts +60 -60
  37. package/backend/src/services/ai/ai-usage.service.ts +7 -7
  38. package/backend/src/services/ai/chat-completion.service.ts +415 -220
  39. package/backend/src/services/ai/helpers.ts +64 -64
  40. package/backend/src/services/ai/index.ts +13 -13
  41. package/backend/src/services/auth/auth-config.service.ts +4 -4
  42. package/backend/src/services/auth/auth-otp.service.ts +6 -6
  43. package/backend/src/services/auth/auth.service.ts +134 -74
  44. package/backend/src/services/auth/index.ts +4 -4
  45. package/backend/src/services/auth/oauth-config.service.ts +12 -12
  46. package/backend/src/services/database/database-advance.service.ts +19 -55
  47. package/backend/src/services/database/database-table.service.ts +38 -85
  48. package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
  49. package/backend/src/services/deployments/deployment.service.ts +693 -0
  50. package/backend/src/services/functions/function.service.ts +61 -41
  51. package/backend/src/services/logs/audit.service.ts +10 -10
  52. package/backend/src/services/secrets/secret.service.ts +101 -27
  53. package/backend/src/services/storage/storage.service.ts +30 -30
  54. package/backend/src/services/usage/usage.service.ts +6 -6
  55. package/backend/src/types/ai.ts +8 -0
  56. package/backend/src/types/auth.ts +5 -1
  57. package/backend/src/types/database.ts +2 -0
  58. package/backend/src/types/deployments.ts +33 -0
  59. package/backend/src/types/storage.ts +1 -1
  60. package/backend/src/types/webhooks.ts +45 -0
  61. package/backend/src/utils/cookies.ts +34 -35
  62. package/backend/src/utils/environment.ts +0 -14
  63. package/backend/src/utils/s3-config-loader.ts +64 -64
  64. package/backend/src/utils/seed.ts +334 -301
  65. package/backend/src/utils/sql-parser.ts +126 -0
  66. package/backend/src/utils/utils.ts +114 -114
  67. package/backend/src/utils/validations.ts +10 -10
  68. package/backend/tests/local/test-rpc.sh +141 -0
  69. package/backend/tests/local/test-secrets.sh +1 -1
  70. package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
  71. package/backend/tests/manual/test-rawsql-modes.sh +24 -24
  72. package/backend/tests/unit/database-advance.test.ts +326 -0
  73. package/backend/tests/unit/helpers.test.ts +2 -2
  74. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +13 -10
  75. package/docker-compose.prod.yml +1 -1
  76. package/docker-compose.yml +1 -1
  77. package/docs/agent-docs/deployment.md +79 -0
  78. package/docs/changelog.mdx +165 -72
  79. package/docs/core-concepts/ai/architecture.mdx +1 -23
  80. package/docs/core-concepts/ai/sdk.mdx +26 -1
  81. package/docs/core-concepts/authentication/architecture.mdx +6 -8
  82. package/docs/core-concepts/authentication/sdk.mdx +387 -91
  83. package/docs/core-concepts/authentication/ui-components/customization.mdx +460 -256
  84. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +50 -24
  85. package/docs/core-concepts/authentication/ui-components/react-router.mdx +18 -19
  86. package/docs/core-concepts/authentication/ui-components/react.mdx +26 -19
  87. package/docs/core-concepts/database/architecture.mdx +58 -21
  88. package/docs/core-concepts/database/pgvector.mdx +138 -0
  89. package/docs/core-concepts/database/sdk.mdx +17 -17
  90. package/docs/core-concepts/deployments/architecture.mdx +152 -0
  91. package/docs/core-concepts/email/architecture.mdx +4 -2
  92. package/docs/core-concepts/functions/architecture.mdx +1 -1
  93. package/docs/core-concepts/functions/sdk.mdx +0 -1
  94. package/docs/core-concepts/realtime/architecture.mdx +1 -1
  95. package/docs/core-concepts/storage/architecture.mdx +1 -1
  96. package/docs/core-concepts/storage/sdk.mdx +25 -25
  97. package/docs/docs.json +14 -6
  98. package/docs/favicon.png +0 -0
  99. package/docs/favicon.svg +3 -18
  100. package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
  101. package/docs/images/changelog/dec-2025/moreModels.png +0 -0
  102. package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
  103. package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
  104. package/docs/images/changelog/dec-2025/realtime2.png +0 -0
  105. package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
  106. package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
  107. package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
  108. package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
  109. package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
  110. package/docs/images/mcp-setup/claude-code-connect.png +0 -0
  111. package/docs/images/mcp-setup/cline-1.png +0 -0
  112. package/docs/images/mcp-setup/cline-2.png +0 -0
  113. package/docs/images/mcp-setup/cline-3.png +0 -0
  114. package/docs/images/mcp-setup/connect-project.png +0 -0
  115. package/docs/images/mcp-setup/copilot-1.png +0 -0
  116. package/docs/images/mcp-setup/copilot-2.png +0 -0
  117. package/docs/images/mcp-setup/copilot-3.png +0 -0
  118. package/docs/images/mcp-setup/mcp-json-1.png +0 -0
  119. package/docs/images/mcp-setup/mcp-json-2.png +0 -0
  120. package/docs/images/mcp-setup/qoder-1.png +0 -0
  121. package/docs/images/mcp-setup/qoder-2.png +0 -0
  122. package/docs/images/mcp-setup/roocode-1.png +0 -0
  123. package/docs/images/mcp-setup/roocode-2.png +0 -0
  124. package/docs/images/mcp-setup/trae-1.png +0 -0
  125. package/docs/images/mcp-setup/trae-2.png +0 -0
  126. package/docs/images/mcp-setup/trae-3.png +0 -0
  127. package/docs/images/mcp-setup/trae-4.png +0 -0
  128. package/docs/images/mcp-setup/trae-5.png +0 -0
  129. package/docs/images/mcp-setup/windsurf-1.png +0 -0
  130. package/docs/images/mcp-setup/windsurf-2.png +0 -0
  131. package/docs/insforge-instructions-sdk.md +7 -3
  132. package/docs/introduction.mdx +9 -8
  133. package/docs/mcp-setup.mdx +332 -0
  134. package/docs/oauth-server.mdx +563 -0
  135. package/docs/partnership.mdx +79 -10
  136. package/docs/quickstart.mdx +1 -1
  137. package/docs/vscode-extension.mdx +74 -0
  138. package/eslint.config.js +1 -0
  139. package/examples/response-examples.md +1 -1
  140. package/frontend/package.json +1 -1
  141. package/frontend/src/App.tsx +8 -3
  142. package/frontend/src/assets/logos/antigravity.svg +1 -0
  143. package/frontend/src/assets/logos/copilot.svg +10 -0
  144. package/frontend/src/assets/logos/deepseek.svg +139 -0
  145. package/frontend/src/assets/logos/kiro.svg +9 -0
  146. package/frontend/src/assets/logos/qoder.svg +4 -0
  147. package/frontend/src/assets/logos/qwen.svg +15 -0
  148. package/frontend/src/components/CodeBlock.tsx +2 -2
  149. package/frontend/src/components/ConnectCTA.tsx +3 -2
  150. package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
  151. package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
  152. package/frontend/src/components/datagrid/index.ts +1 -1
  153. package/frontend/src/components/index.ts +0 -1
  154. package/frontend/src/components/layout/AppHeader.tsx +4 -27
  155. package/frontend/src/components/layout/AppSidebar.tsx +85 -100
  156. package/frontend/src/components/layout/Layout.tsx +34 -32
  157. package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
  158. package/frontend/src/components/radix/Select.tsx +151 -151
  159. package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
  160. package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
  161. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
  162. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
  163. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
  164. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
  165. package/frontend/src/features/ai/components/index.ts +6 -6
  166. package/frontend/src/features/ai/helpers.ts +147 -141
  167. package/frontend/src/features/ai/pages/AIPage.tsx +166 -166
  168. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
  169. package/frontend/src/features/auth/components/UsersDataGrid.tsx +55 -31
  170. package/frontend/src/features/auth/components/index.ts +5 -5
  171. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -275
  172. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +1 -1
  173. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
  174. package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
  175. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
  176. package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
  177. package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
  178. package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
  179. package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
  180. package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
  181. package/frontend/src/features/database/constants.ts +16 -28
  182. package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
  183. package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
  184. package/frontend/src/features/database/hooks/useTables.ts +5 -7
  185. package/frontend/src/features/database/pages/FunctionsPage.tsx +0 -5
  186. package/frontend/src/features/database/pages/IndexesPage.tsx +0 -5
  187. package/frontend/src/features/database/pages/PoliciesPage.tsx +0 -5
  188. package/frontend/src/features/database/pages/SQLEditorPage.tsx +2 -2
  189. package/frontend/src/features/database/pages/TriggersPage.tsx +0 -5
  190. package/frontend/src/features/database/services/advance.service.ts +1 -15
  191. package/frontend/src/features/database/services/record.service.ts +4 -20
  192. package/frontend/src/features/database/services/table.service.ts +1 -4
  193. package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
  194. package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
  195. package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
  196. package/frontend/src/features/database/templates/notion-clone.ts +8 -8
  197. package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
  198. package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
  199. package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
  200. package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
  201. package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
  202. package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
  203. package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
  204. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
  205. package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
  206. package/frontend/src/features/functions/components/index.ts +5 -5
  207. package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
  208. package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
  209. package/frontend/src/features/functions/pages/SecretsPage.tsx +118 -118
  210. package/frontend/src/features/functions/services/function.service.ts +8 -25
  211. package/frontend/src/features/functions/services/secret.service.ts +23 -41
  212. package/frontend/src/features/login/pages/CloudLoginPage.tsx +125 -118
  213. package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
  214. package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
  215. package/frontend/src/features/logs/components/index.ts +1 -0
  216. package/frontend/src/features/logs/pages/LogsPage.tsx +36 -6
  217. package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
  218. package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
  219. package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
  220. package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
  221. package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
  222. package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
  223. package/frontend/src/features/onboard/components/index.ts +9 -4
  224. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
  225. package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
  226. package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
  227. package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
  228. package/frontend/src/features/onboard/index.ts +17 -13
  229. package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
  230. package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
  231. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +21 -8
  232. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +10 -1
  233. package/frontend/src/index.css +249 -249
  234. package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
  235. package/frontend/src/lib/hooks/useMetadata.ts +45 -1
  236. package/frontend/src/lib/hooks/useModal.tsx +2 -0
  237. package/frontend/src/lib/routing/AppRoutes.tsx +103 -99
  238. package/frontend/src/lib/services/metadata.service.ts +20 -3
  239. package/frontend/src/lib/utils/menuItems.ts +223 -207
  240. package/frontend/src/lib/utils/utils.ts +196 -196
  241. package/functions/server.ts +315 -315
  242. package/functions/worker-template.js +1 -1
  243. package/openapi/ai.yaml +115 -5
  244. package/openapi/auth.yaml +97 -17
  245. package/openapi/logs.yaml +0 -2
  246. package/openapi/metadata.yaml +0 -2
  247. package/openapi/records.yaml +21 -21
  248. package/openapi/tables.yaml +1 -2
  249. package/package.json +1 -1
  250. package/shared-schemas/package.json +1 -1
  251. package/shared-schemas/src/ai-api.schema.ts +251 -143
  252. package/shared-schemas/src/ai.schema.ts +63 -63
  253. package/shared-schemas/src/auth-api.schema.ts +34 -6
  254. package/shared-schemas/src/auth.schema.ts +17 -10
  255. package/shared-schemas/src/cloud-events.schema.ts +26 -0
  256. package/shared-schemas/src/deployments-api.schema.ts +55 -0
  257. package/shared-schemas/src/deployments.schema.ts +30 -0
  258. package/shared-schemas/src/docs.schema.ts +8 -2
  259. package/shared-schemas/src/email-api.schema.ts +30 -30
  260. package/shared-schemas/src/functions-api.schema.ts +13 -4
  261. package/shared-schemas/src/functions.schema.ts +1 -1
  262. package/shared-schemas/src/index.ts +22 -18
  263. package/shared-schemas/src/metadata.schema.ts +30 -4
  264. package/shared-schemas/src/secrets-api.schema.ts +44 -0
  265. package/shared-schemas/src/secrets.schema.ts +15 -0
  266. package/zeabur/README.md +13 -0
  267. package/zeabur/template.yml +20 -51
  268. package/backend/src/types/profile.ts +0 -55
  269. package/frontend/src/components/ProjectInfoModal.tsx +0 -128
@@ -1,127 +1,127 @@
1
- import rateLimit from 'express-rate-limit';
2
- import { Request, Response, NextFunction } from 'express';
3
- import { AppError } from './error.js';
4
- import { ERROR_CODES } from '@/types/error-constants.js';
5
-
6
- /**
7
- * Store for tracking per-email cooldowns
8
- * Maps email -> last request timestamp
9
- */
10
- const emailCooldowns = new Map<string, number>();
11
-
12
- /**
13
- * Cleanup old cooldown entries every 5 minutes
14
- */
15
- setInterval(
16
- () => {
17
- const now = Date.now();
18
- const fiveMinutes = 5 * 60 * 1000;
19
-
20
- for (const [email, timestamp] of emailCooldowns.entries()) {
21
- if (now - timestamp > fiveMinutes) {
22
- emailCooldowns.delete(email);
23
- }
24
- }
25
- },
26
- 5 * 60 * 1000
27
- );
28
-
29
- /**
30
- * Per-IP rate limiter for email otp requests
31
- * Prevents brute-force attacks, resource exhaustion, and enumeration from single IP
32
- *
33
- * Limits: 5 requests per 15 minutes per IP
34
- * Counts ALL requests (both successful and failed) to prevent abuse
35
- */
36
- export const sendEmailOTPRateLimiter = rateLimit({
37
- windowMs: 15 * 60 * 1000, // 15 minutes
38
- max: 5, // Limit each IP to 5 requests per windowMs
39
- standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
40
- legacyHeaders: false, // Disable the `X-RateLimit-*` headers
41
- handler: (_req: Request, _res: Response, next: NextFunction) => {
42
- next(
43
- new AppError(
44
- 'Too many send email verification requests from this IP. Please try again in 15 minutes.',
45
- 429,
46
- ERROR_CODES.TOO_MANY_REQUESTS
47
- )
48
- );
49
- },
50
- // Count all requests (both successes and failures) to prevent resource exhaustion and enumeration
51
- skipSuccessfulRequests: false,
52
- skipFailedRequests: false,
53
- });
54
-
55
- /**
56
- * Per-IP rate limiter for email OTP verification attempts
57
- * Prevents brute-force code guessing
58
- *
59
- * Limits: 10 attempts per 15 minutes per IP
60
- */
61
- export const verifyOTPRateLimiter = rateLimit({
62
- windowMs: 15 * 60 * 1000, // 15 minutes
63
- max: 10, // Limit each IP to 10 verification attempts per windowMs
64
- standardHeaders: true,
65
- legacyHeaders: false,
66
- handler: (_req: Request, _res: Response, next: NextFunction) => {
67
- next(
68
- new AppError(
69
- 'Too many verification attempts from this IP. Please try again in 15 minutes.',
70
- 429,
71
- ERROR_CODES.TOO_MANY_REQUESTS
72
- )
73
- );
74
- },
75
- skipSuccessfulRequests: true, // Don't count successful verifications
76
- skipFailedRequests: false, // Count failed attempts to prevent brute force
77
- });
78
-
79
- /**
80
- * Per-email cooldown middleware
81
- * Prevents enumeration attacks by enforcing minimum time between requests for same email
82
- *
83
- * Cooldown: 60 seconds between requests for same email
84
- */
85
- export const perEmailCooldown = (cooldownMs: number = 60000) => {
86
- return (req: Request, _res: Response, next: NextFunction) => {
87
- const email = req.body?.email?.toLowerCase();
88
-
89
- if (!email) {
90
- // If no email in body, let it pass (will be caught by validation)
91
- return next();
92
- }
93
-
94
- const now = Date.now();
95
- const lastRequest = emailCooldowns.get(email);
96
-
97
- if (lastRequest && now - lastRequest < cooldownMs) {
98
- const remainingMs = cooldownMs - (now - lastRequest);
99
- const remainingSec = Math.ceil(remainingMs / 1000);
100
-
101
- throw new AppError(
102
- `Please wait ${remainingSec} seconds before requesting another code for this email`,
103
- 429,
104
- ERROR_CODES.TOO_MANY_REQUESTS
105
- );
106
- }
107
-
108
- // Update last request time
109
- emailCooldowns.set(email, now);
110
- next();
111
- };
112
- };
113
-
114
- /**
115
- * Combined rate limiter for sending email otp requests
116
- * Applies both per-IP and per-email limits
117
- */
118
- export const sendEmailOTPLimiter = [
119
- sendEmailOTPRateLimiter,
120
- perEmailCooldown(60000), // 60 second cooldown per email
121
- ];
122
-
123
- /**
124
- * Rate limiter for OTP verification attempts (email OTP verification)
125
- * Only per-IP limit, no per-email limit (to allow legitimate retries)
126
- */
127
- export const verifyOTPLimiter = [verifyOTPRateLimiter];
1
+ import rateLimit from 'express-rate-limit';
2
+ import { Request, Response, NextFunction } from 'express';
3
+ import { AppError } from './error.js';
4
+ import { ERROR_CODES } from '@/types/error-constants.js';
5
+
6
+ /**
7
+ * Store for tracking per-email cooldowns
8
+ * Maps email -> last request timestamp
9
+ */
10
+ const emailCooldowns = new Map<string, number>();
11
+
12
+ /**
13
+ * Cleanup old cooldown entries every 5 minutes
14
+ */
15
+ setInterval(
16
+ () => {
17
+ const now = Date.now();
18
+ const fiveMinutes = 5 * 60 * 1000;
19
+
20
+ for (const [email, timestamp] of emailCooldowns.entries()) {
21
+ if (now - timestamp > fiveMinutes) {
22
+ emailCooldowns.delete(email);
23
+ }
24
+ }
25
+ },
26
+ 5 * 60 * 1000
27
+ );
28
+
29
+ /**
30
+ * Per-IP rate limiter for email otp requests
31
+ * Prevents brute-force attacks, resource exhaustion, and enumeration from single IP
32
+ *
33
+ * Limits: 5 requests per 15 minutes per IP
34
+ * Counts ALL requests (both successful and failed) to prevent abuse
35
+ */
36
+ export const sendEmailOTPRateLimiter = rateLimit({
37
+ windowMs: 15 * 60 * 1000, // 15 minutes
38
+ max: 5, // Limit each IP to 5 requests per windowMs
39
+ standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
40
+ legacyHeaders: false, // Disable the `X-RateLimit-*` headers
41
+ handler: (_req: Request, _res: Response, next: NextFunction) => {
42
+ next(
43
+ new AppError(
44
+ 'Too many send email verification requests from this IP. Please try again in 15 minutes.',
45
+ 429,
46
+ ERROR_CODES.TOO_MANY_REQUESTS
47
+ )
48
+ );
49
+ },
50
+ // Count all requests (both successes and failures) to prevent resource exhaustion and enumeration
51
+ skipSuccessfulRequests: false,
52
+ skipFailedRequests: false,
53
+ });
54
+
55
+ /**
56
+ * Per-IP rate limiter for email OTP verification attempts
57
+ * Prevents brute-force code guessing
58
+ *
59
+ * Limits: 10 attempts per 15 minutes per IP
60
+ */
61
+ export const verifyOTPRateLimiter = rateLimit({
62
+ windowMs: 15 * 60 * 1000, // 15 minutes
63
+ max: 10, // Limit each IP to 10 verification attempts per windowMs
64
+ standardHeaders: true,
65
+ legacyHeaders: false,
66
+ handler: (_req: Request, _res: Response, next: NextFunction) => {
67
+ next(
68
+ new AppError(
69
+ 'Too many verification attempts from this IP. Please try again in 15 minutes.',
70
+ 429,
71
+ ERROR_CODES.TOO_MANY_REQUESTS
72
+ )
73
+ );
74
+ },
75
+ skipSuccessfulRequests: true, // Don't count successful verifications
76
+ skipFailedRequests: false, // Count failed attempts to prevent brute force
77
+ });
78
+
79
+ /**
80
+ * Per-email cooldown middleware
81
+ * Prevents enumeration attacks by enforcing minimum time between requests for same email
82
+ *
83
+ * Cooldown: 60 seconds between requests for same email
84
+ */
85
+ export const perEmailCooldown = (cooldownMs: number = 60000) => {
86
+ return (req: Request, _res: Response, next: NextFunction) => {
87
+ const email = req.body?.email?.toLowerCase();
88
+
89
+ if (!email) {
90
+ // If no email in body, let it pass (will be caught by validation)
91
+ return next();
92
+ }
93
+
94
+ const now = Date.now();
95
+ const lastRequest = emailCooldowns.get(email);
96
+
97
+ if (lastRequest && now - lastRequest < cooldownMs) {
98
+ const remainingMs = cooldownMs - (now - lastRequest);
99
+ const remainingSec = Math.ceil(remainingMs / 1000);
100
+
101
+ throw new AppError(
102
+ `Please wait ${remainingSec} seconds before requesting another code for this email`,
103
+ 429,
104
+ ERROR_CODES.TOO_MANY_REQUESTS
105
+ );
106
+ }
107
+
108
+ // Update last request time
109
+ emailCooldowns.set(email, now);
110
+ next();
111
+ };
112
+ };
113
+
114
+ /**
115
+ * Combined rate limiter for sending email otp requests
116
+ * Applies both per-IP and per-email limits
117
+ */
118
+ export const sendEmailOTPLimiter = [
119
+ sendEmailOTPRateLimiter,
120
+ perEmailCooldown(60000), // 60 second cooldown per email
121
+ ];
122
+
123
+ /**
124
+ * Rate limiter for OTP verification attempts (email OTP verification)
125
+ * Only per-IP limit, no per-email limit (to allow legitimate retries)
126
+ */
127
+ export const verifyOTPLimiter = [verifyOTPRateLimiter];