From a055e1b6f8199c3680e34842d600af2e8fb37832 Mon Sep 17 00:00:00 2001 From: Sergei Date: Mon, 26 Jan 2026 16:44:27 -0800 Subject: [PATCH] fix(security): add rate limiting for OTP endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add verifyOtpLimiter: 5 attempts per 15 minutes per email/IP - Add requestOtpLimiter: 3 attempts per 15 minutes per email/IP - Use email as primary key, fallback to IP - Return JSON error messages for rate limit exceeded 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- backend/src/routes/auth.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 7c09874..29b0178 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -2,10 +2,39 @@ const express = require('express'); const router = express.Router(); const crypto = require('crypto'); const jwt = require('jsonwebtoken'); +const rateLimit = require('express-rate-limit'); const { supabase } = require('../config/supabase'); const { sendOTPEmail } = require('../services/email'); const storage = require('../services/storage'); +// Rate limiter for OTP verification: 5 attempts per 15 minutes per email/IP +const verifyOtpLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, + keyGenerator: (req) => { + // Use email if provided, otherwise fall back to IP + const email = req.body?.email?.toLowerCase()?.trim(); + return email || req.ip; + }, + message: { error: 'Too many verification attempts. Please try again in 15 minutes.' }, + standardHeaders: true, + legacyHeaders: false, +}); + +// Rate limiter for OTP request: 3 attempts per 15 minutes per email/IP +const requestOtpLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 3, + keyGenerator: (req) => { + // Use email if provided, otherwise fall back to IP + const email = req.body?.email?.toLowerCase()?.trim(); + return email || req.ip; + }, + message: { error: 'Too many OTP requests. Please try again in 15 minutes.' }, + standardHeaders: true, + legacyHeaders: false, +}); + /** * POST /api/auth/check-email * Проверяет существует ли пользователь с данным email @@ -49,8 +78,9 @@ router.post('/check-email', async (req, res) => { * POST /api/auth/request-otp * Отправляет OTP код на email * Если пользователя нет - создаёт нового + * Rate limited: 3 requests per 15 minutes per email/IP */ -router.post('/request-otp', async (req, res) => { +router.post('/request-otp', requestOtpLimiter, async (req, res) => { try { const { email } = req.body; @@ -137,8 +167,9 @@ router.post('/request-otp', async (req, res) => { /** * POST /api/auth/verify-otp * Проверяет OTP код и возвращает JWT токен + * Rate limited: 5 attempts per 15 minutes per email/IP */ -router.post('/verify-otp', async (req, res) => { +router.post('/verify-otp', verifyOtpLimiter, async (req, res) => { try { const { email, code } = req.body;