File tree Expand file tree Collapse file tree 7 files changed +107
-19
lines changed
Expand file tree Collapse file tree 7 files changed +107
-19
lines changed Original file line number Diff line number Diff line change 1717 "cors" : " ^2.8.5" ,
1818 "dotenv" : " ^17.2.3" ,
1919 "express" : " ^5.1.0" ,
20+ "express-rate-limit" : " ^8.2.1" ,
2021 "jsonwebtoken" : " ^9.0.2" ,
2122 "mongodb" : " ^6.20.0" ,
2223 "mongoose" : " ^8.19.4" ,
Original file line number Diff line number Diff line change @@ -14,6 +14,9 @@ import path from "path";
1414dotenv . config ( ) ;
1515const app = express ( ) ;
1616
17+ if ( process . env . TRUST_PROXY === "true" ) {
18+ app . set ( "trust proxy" , true ) ;
19+ }
1720// Required for __dirname in ES modules / TS
1821// (TS compiles to CJS so this works fine)
1922const __dirnameLocal = path . resolve ( ) ;
Original file line number Diff line number Diff line change 1+ import rateLimit from "express-rate-limit" ;
2+ import type { Request , Response } from "express" ;
3+ import logger from "../utils/logger.js" ;
4+
5+ export const authRateLimiter = rateLimit ( {
6+ windowMs : 15 * 60 * 1000 ,
7+ max : 5 ,
8+ standardHeaders : true ,
9+ legacyHeaders : false ,
10+ handler : ( req : Request , res : Response ) => {
11+ const clientIP = req . ip || req . socket . remoteAddress || "unknown" ;
12+ logger . warn (
13+ `Rate limit exceeded for IP: ${ clientIP } on ${ req . method } ${ req . originalUrl } `
14+ ) ;
15+
16+ const retryAfter = req . rateLimit ?. resetTime
17+ ? Math . round ( ( req . rateLimit . resetTime - Date . now ( ) ) / 1000 )
18+ : 900 ;
19+
20+ res . status ( 429 ) . json ( {
21+ success : false ,
22+ message : "Too many authentication attempts. Please try again after 15 minutes." ,
23+ retryAfter,
24+ } ) ;
25+ } ,
26+ keyGenerator : ( req : Request ) => {
27+ return req . ip || req . socket . remoteAddress || "unknown" ;
28+ } ,
29+ } ) ;
30+
Original file line number Diff line number Diff line change 99import passport from "passport" ;
1010import { Session } from "../models/sessionModel.js" ;
1111import { protect } from "../middleware/authMiddleware.js" ;
12+ import { authRateLimiter } from "../middleware/rateLimiter.js" ;
1213import { generateToken , generateRefreshToken } from "../utils/generateToken.js" ;
1314import jwt from "jsonwebtoken" ;
1415import dotenv from "dotenv" ;
@@ -20,9 +21,8 @@ dotenv.config();
2021const router = express . Router ( ) ;
2122const FRONTEND_URL = process . env . FRONTEND_URL || "http://localhost:5173" ;
2223
23- // REST endpoints
24- router . post ( "/signup" , registerUser ) ;
25- router . post ( "/signin" , loginUser ) ;
24+ router . post ( "/signup" , authRateLimiter , registerUser ) ;
25+ router . post ( "/signin" , authRateLimiter , loginUser ) ;
2626router . post ( "/logout" , logoutUser ) ;
2727router . get ( "/refresh" , handleRefreshToken ) ;
2828router . get ( "/me" , protect , getUserProfile ) ;
Original file line number Diff line number Diff line change @@ -54,12 +54,23 @@ const SignIn = () => {
5454 reset ( ) ;
5555 navigate ( "/" ) ; // redirect to home/dashboard
5656 } catch ( error : any ) {
57- toast ( {
58- title : "Error" ,
59- description :
60- error . response ?. data ?. message || "Login failed. Please try again." ,
61- variant : "destructive" ,
62- } ) ;
57+ // Handle rate limiting errors
58+ if ( error . response ?. status === 429 ) {
59+ const retryAfter = error . response ?. data ?. retryAfter || 15 ;
60+ const minutes = Math . ceil ( retryAfter / 60 ) ;
61+ toast ( {
62+ title : "Too Many Attempts" ,
63+ description : `Too many login attempts. Please try again after ${ minutes } minute${ minutes > 1 ? 's' : '' } .` ,
64+ variant : "destructive" ,
65+ } ) ;
66+ } else {
67+ toast ( {
68+ title : "Error" ,
69+ description :
70+ error . response ?. data ?. message || "Login failed. Please try again." ,
71+ variant : "destructive" ,
72+ } ) ;
73+ }
6374 } finally {
6475 setIsLoading ( false ) ;
6576 }
Original file line number Diff line number Diff line change @@ -83,16 +83,30 @@ const SignUp = () => {
8383 reset ( ) ;
8484 setTimeout ( ( ) => navigate ( "/signin" ) , 1500 ) ;
8585 } catch ( error : any ) {
86- const msg =
87- error . response ?. data ?. message ||
88- "Registration failed. Please try again." ;
89- toast ( {
90- title : "Error" ,
91- description : msg ,
92- variant : "destructive" ,
93- } ) ;
94- setServerMessage ( msg ) ;
95- setIsError ( true ) ;
86+ // Handle rate limiting errors
87+ if ( error . response ?. status === 429 ) {
88+ const retryAfter = error . response ?. data ?. retryAfter || 15 ;
89+ const minutes = Math . ceil ( retryAfter / 60 ) ;
90+ const msg = `Too many registration attempts. Please try again after ${ minutes } minute${ minutes > 1 ? 's' : '' } .` ;
91+ toast ( {
92+ title : "Too Many Attempts" ,
93+ description : msg ,
94+ variant : "destructive" ,
95+ } ) ;
96+ setServerMessage ( msg ) ;
97+ setIsError ( true ) ;
98+ } else {
99+ const msg =
100+ error . response ?. data ?. message ||
101+ "Registration failed. Please try again." ;
102+ toast ( {
103+ title : "Error" ,
104+ description : msg ,
105+ variant : "destructive" ,
106+ } ) ;
107+ setServerMessage ( msg ) ;
108+ setIsError ( true ) ;
109+ }
96110 } finally {
97111 setIsLoading ( false ) ;
98112 }
You can’t perform that action at this time.
0 commit comments