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.
- package/CHANGELOG.md +2 -0
- package/auth/package.json +5 -3
- package/auth/src/lib/broadcastService.ts +115 -117
- package/auth/src/lib/insforge.ts +8 -0
- package/auth/src/main.tsx +2 -4
- package/auth/src/pages/SignInPage.tsx +60 -60
- package/auth/src/pages/SignUpPage.tsx +60 -60
- package/auth/src/pages/VerifyEmailPage.tsx +18 -0
- package/auth/tsconfig.json +2 -1
- package/backend/package.json +10 -6
- package/backend/src/api/middlewares/rate-limiters.ts +127 -127
- package/backend/src/api/routes/ai/index.routes.ts +475 -468
- package/backend/src/api/routes/auth/index.routes.ts +85 -32
- package/backend/src/api/routes/auth/oauth.routes.ts +11 -6
- package/backend/src/api/routes/database/index.routes.ts +2 -0
- package/backend/src/api/routes/database/records.routes.ts +39 -175
- package/backend/src/api/routes/database/rpc.routes.ts +69 -0
- package/backend/src/api/routes/deployments/index.routes.ts +192 -0
- package/backend/src/api/routes/docs/index.routes.ts +3 -2
- package/backend/src/api/routes/email/index.routes.ts +35 -35
- package/backend/src/api/routes/functions/index.routes.ts +3 -3
- package/backend/src/api/routes/metadata/index.routes.ts +26 -0
- package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
- package/backend/src/infra/database/database.manager.ts +0 -10
- package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
- package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
- package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
- package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
- package/backend/src/infra/security/token.manager.ts +1 -4
- package/backend/src/providers/ai/openrouter.provider.ts +12 -3
- package/backend/src/providers/database/base.provider.ts +39 -0
- package/backend/src/providers/database/cloud.provider.ts +159 -0
- package/backend/src/providers/deployments/vercel.provider.ts +516 -0
- package/backend/src/server.ts +19 -7
- package/backend/src/services/ai/ai-config.service.ts +6 -6
- package/backend/src/services/ai/ai-model.service.ts +60 -60
- package/backend/src/services/ai/ai-usage.service.ts +7 -7
- package/backend/src/services/ai/chat-completion.service.ts +415 -220
- package/backend/src/services/ai/helpers.ts +64 -64
- package/backend/src/services/ai/index.ts +13 -13
- package/backend/src/services/auth/auth-config.service.ts +4 -4
- package/backend/src/services/auth/auth-otp.service.ts +6 -6
- package/backend/src/services/auth/auth.service.ts +134 -74
- package/backend/src/services/auth/index.ts +4 -4
- package/backend/src/services/auth/oauth-config.service.ts +12 -12
- package/backend/src/services/database/database-advance.service.ts +19 -55
- package/backend/src/services/database/database-table.service.ts +38 -85
- package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
- package/backend/src/services/deployments/deployment.service.ts +693 -0
- package/backend/src/services/functions/function.service.ts +61 -41
- package/backend/src/services/logs/audit.service.ts +10 -10
- package/backend/src/services/secrets/secret.service.ts +101 -27
- package/backend/src/services/storage/storage.service.ts +30 -30
- package/backend/src/services/usage/usage.service.ts +6 -6
- package/backend/src/types/ai.ts +8 -0
- package/backend/src/types/auth.ts +5 -1
- package/backend/src/types/database.ts +2 -0
- package/backend/src/types/deployments.ts +33 -0
- package/backend/src/types/storage.ts +1 -1
- package/backend/src/types/webhooks.ts +45 -0
- package/backend/src/utils/cookies.ts +34 -35
- package/backend/src/utils/environment.ts +0 -14
- package/backend/src/utils/s3-config-loader.ts +64 -64
- package/backend/src/utils/seed.ts +334 -301
- package/backend/src/utils/sql-parser.ts +126 -0
- package/backend/src/utils/utils.ts +114 -114
- package/backend/src/utils/validations.ts +10 -10
- package/backend/tests/local/test-rpc.sh +141 -0
- package/backend/tests/local/test-secrets.sh +1 -1
- package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
- package/backend/tests/manual/test-rawsql-modes.sh +24 -24
- package/backend/tests/unit/database-advance.test.ts +326 -0
- package/backend/tests/unit/helpers.test.ts +2 -2
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +13 -10
- package/docker-compose.prod.yml +1 -1
- package/docker-compose.yml +1 -1
- package/docs/agent-docs/deployment.md +79 -0
- package/docs/changelog.mdx +165 -72
- package/docs/core-concepts/ai/architecture.mdx +1 -23
- package/docs/core-concepts/ai/sdk.mdx +26 -1
- package/docs/core-concepts/authentication/architecture.mdx +6 -8
- package/docs/core-concepts/authentication/sdk.mdx +387 -91
- package/docs/core-concepts/authentication/ui-components/customization.mdx +460 -256
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +50 -24
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +18 -19
- package/docs/core-concepts/authentication/ui-components/react.mdx +26 -19
- package/docs/core-concepts/database/architecture.mdx +58 -21
- package/docs/core-concepts/database/pgvector.mdx +138 -0
- package/docs/core-concepts/database/sdk.mdx +17 -17
- package/docs/core-concepts/deployments/architecture.mdx +152 -0
- package/docs/core-concepts/email/architecture.mdx +4 -2
- package/docs/core-concepts/functions/architecture.mdx +1 -1
- package/docs/core-concepts/functions/sdk.mdx +0 -1
- package/docs/core-concepts/realtime/architecture.mdx +1 -1
- package/docs/core-concepts/storage/architecture.mdx +1 -1
- package/docs/core-concepts/storage/sdk.mdx +25 -25
- package/docs/docs.json +14 -6
- package/docs/favicon.png +0 -0
- package/docs/favicon.svg +3 -18
- package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
- package/docs/images/changelog/dec-2025/moreModels.png +0 -0
- package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
- package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
- package/docs/images/changelog/dec-2025/realtime2.png +0 -0
- package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
- package/docs/images/mcp-setup/claude-code-connect.png +0 -0
- package/docs/images/mcp-setup/cline-1.png +0 -0
- package/docs/images/mcp-setup/cline-2.png +0 -0
- package/docs/images/mcp-setup/cline-3.png +0 -0
- package/docs/images/mcp-setup/connect-project.png +0 -0
- package/docs/images/mcp-setup/copilot-1.png +0 -0
- package/docs/images/mcp-setup/copilot-2.png +0 -0
- package/docs/images/mcp-setup/copilot-3.png +0 -0
- package/docs/images/mcp-setup/mcp-json-1.png +0 -0
- package/docs/images/mcp-setup/mcp-json-2.png +0 -0
- package/docs/images/mcp-setup/qoder-1.png +0 -0
- package/docs/images/mcp-setup/qoder-2.png +0 -0
- package/docs/images/mcp-setup/roocode-1.png +0 -0
- package/docs/images/mcp-setup/roocode-2.png +0 -0
- package/docs/images/mcp-setup/trae-1.png +0 -0
- package/docs/images/mcp-setup/trae-2.png +0 -0
- package/docs/images/mcp-setup/trae-3.png +0 -0
- package/docs/images/mcp-setup/trae-4.png +0 -0
- package/docs/images/mcp-setup/trae-5.png +0 -0
- package/docs/images/mcp-setup/windsurf-1.png +0 -0
- package/docs/images/mcp-setup/windsurf-2.png +0 -0
- package/docs/insforge-instructions-sdk.md +7 -3
- package/docs/introduction.mdx +9 -8
- package/docs/mcp-setup.mdx +332 -0
- package/docs/oauth-server.mdx +563 -0
- package/docs/partnership.mdx +79 -10
- package/docs/quickstart.mdx +1 -1
- package/docs/vscode-extension.mdx +74 -0
- package/eslint.config.js +1 -0
- package/examples/response-examples.md +1 -1
- package/frontend/package.json +1 -1
- package/frontend/src/App.tsx +8 -3
- package/frontend/src/assets/logos/antigravity.svg +1 -0
- package/frontend/src/assets/logos/copilot.svg +10 -0
- package/frontend/src/assets/logos/deepseek.svg +139 -0
- package/frontend/src/assets/logos/kiro.svg +9 -0
- package/frontend/src/assets/logos/qoder.svg +4 -0
- package/frontend/src/assets/logos/qwen.svg +15 -0
- package/frontend/src/components/CodeBlock.tsx +2 -2
- package/frontend/src/components/ConnectCTA.tsx +3 -2
- package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
- package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
- package/frontend/src/components/datagrid/index.ts +1 -1
- package/frontend/src/components/index.ts +0 -1
- package/frontend/src/components/layout/AppHeader.tsx +4 -27
- package/frontend/src/components/layout/AppSidebar.tsx +85 -100
- package/frontend/src/components/layout/Layout.tsx +34 -32
- package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
- package/frontend/src/components/radix/Select.tsx +151 -151
- package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
- package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
- package/frontend/src/features/ai/components/index.ts +6 -6
- package/frontend/src/features/ai/helpers.ts +147 -141
- package/frontend/src/features/ai/pages/AIPage.tsx +166 -166
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +55 -31
- package/frontend/src/features/auth/components/index.ts +5 -5
- package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -275
- package/frontend/src/features/dashboard/pages/DashboardPage.tsx +1 -1
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
- package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
- package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
- package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
- package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
- package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
- package/frontend/src/features/database/constants.ts +16 -28
- package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
- package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
- package/frontend/src/features/database/hooks/useTables.ts +5 -7
- package/frontend/src/features/database/pages/FunctionsPage.tsx +0 -5
- package/frontend/src/features/database/pages/IndexesPage.tsx +0 -5
- package/frontend/src/features/database/pages/PoliciesPage.tsx +0 -5
- package/frontend/src/features/database/pages/SQLEditorPage.tsx +2 -2
- package/frontend/src/features/database/pages/TriggersPage.tsx +0 -5
- package/frontend/src/features/database/services/advance.service.ts +1 -15
- package/frontend/src/features/database/services/record.service.ts +4 -20
- package/frontend/src/features/database/services/table.service.ts +1 -4
- package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
- package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
- package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
- package/frontend/src/features/database/templates/notion-clone.ts +8 -8
- package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
- package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
- package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
- package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
- package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
- package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
- package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
- package/frontend/src/features/functions/components/index.ts +5 -5
- package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
- package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
- package/frontend/src/features/functions/pages/SecretsPage.tsx +118 -118
- package/frontend/src/features/functions/services/function.service.ts +8 -25
- package/frontend/src/features/functions/services/secret.service.ts +23 -41
- package/frontend/src/features/login/pages/CloudLoginPage.tsx +125 -118
- package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
- package/frontend/src/features/logs/components/index.ts +1 -0
- package/frontend/src/features/logs/pages/LogsPage.tsx +36 -6
- package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
- package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
- package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
- package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
- package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
- package/frontend/src/features/onboard/components/index.ts +9 -4
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
- package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
- package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
- package/frontend/src/features/onboard/index.ts +17 -13
- package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +21 -8
- package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +10 -1
- package/frontend/src/index.css +249 -249
- package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
- package/frontend/src/lib/hooks/useMetadata.ts +45 -1
- package/frontend/src/lib/hooks/useModal.tsx +2 -0
- package/frontend/src/lib/routing/AppRoutes.tsx +103 -99
- package/frontend/src/lib/services/metadata.service.ts +20 -3
- package/frontend/src/lib/utils/menuItems.ts +223 -207
- package/frontend/src/lib/utils/utils.ts +196 -196
- package/functions/server.ts +315 -315
- package/functions/worker-template.js +1 -1
- package/openapi/ai.yaml +115 -5
- package/openapi/auth.yaml +97 -17
- package/openapi/logs.yaml +0 -2
- package/openapi/metadata.yaml +0 -2
- package/openapi/records.yaml +21 -21
- package/openapi/tables.yaml +1 -2
- package/package.json +1 -1
- package/shared-schemas/package.json +1 -1
- package/shared-schemas/src/ai-api.schema.ts +251 -143
- package/shared-schemas/src/ai.schema.ts +63 -63
- package/shared-schemas/src/auth-api.schema.ts +34 -6
- package/shared-schemas/src/auth.schema.ts +17 -10
- package/shared-schemas/src/cloud-events.schema.ts +26 -0
- package/shared-schemas/src/deployments-api.schema.ts +55 -0
- package/shared-schemas/src/deployments.schema.ts +30 -0
- package/shared-schemas/src/docs.schema.ts +8 -2
- package/shared-schemas/src/email-api.schema.ts +30 -30
- package/shared-schemas/src/functions-api.schema.ts +13 -4
- package/shared-schemas/src/functions.schema.ts +1 -1
- package/shared-schemas/src/index.ts +22 -18
- package/shared-schemas/src/metadata.schema.ts +30 -4
- package/shared-schemas/src/secrets-api.schema.ts +44 -0
- package/shared-schemas/src/secrets.schema.ts +15 -0
- package/zeabur/README.md +13 -0
- package/zeabur/template.yml +20 -51
- package/backend/src/types/profile.ts +0 -55
- 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];
|