@@ -41,7 +41,7 @@ const limiter = rateLimit({
4141 standardHeaders : true , // Return rate limit info in the `RateLimit-*` headers
4242 legacyHeaders : false , // Disable the `X-RateLimit-*` headers
4343} ) ;
44- // Strict Auth Rate Limiter Config for signup, password recover, account verification, login by email
44+ // Strict Auth Rate Limiter Config for signup, password recover, account verification, login by email, send 2FA email
4545const strictLimiter = rateLimit ( {
4646 windowMs : 60 * 60 * 1000 , // 1 hour
4747 max : RATE_LIMIT_STRICT , // attempts per hour
@@ -56,6 +56,15 @@ const loginLimiter = rateLimit({
5656 standardHeaders : true ,
5757 legacyHeaders : false ,
5858} ) ;
59+ // Login 2FA Rate Limiter Config - allow more requests for 2FA pages per login to avoid UX issues.
60+ // This is after a valid username/password submission, so the attack surface is smaller
61+ // and we want to avoid locking out legitimate users who mistype their 2FA code.
62+ const login2FALimiter = rateLimit ( {
63+ windowMs : 60 * 60 * 1000 , // 1 hour
64+ max : RATE_LIMIT_LOGIN * 5 ,
65+ standardHeaders : true ,
66+ legacyHeaders : false ,
67+ } ) ;
5968
6069// This logic for numberOfProxies works for local testing, ngrok use, single host deployments
6170// behind cloudflare, etc. You may need to change it for more complex network settings.
@@ -205,6 +214,11 @@ app.get('/', homeController.index);
205214app . get ( '/login' , userController . getLogin ) ;
206215app . post ( '/login' , loginLimiter , userController . postLogin ) ;
207216app . get ( '/login/verify/:token' , loginLimiter , userController . getLoginByEmail ) ;
217+ app . get ( '/login/2fa' , login2FALimiter , userController . getTwoFactor ) ;
218+ app . post ( '/login/2fa' , login2FALimiter , userController . postTwoFactor ) ;
219+ app . post ( '/login/2fa/resend' , strictLimiter , userController . resendTwoFactorCode ) ;
220+ app . get ( '/login/2fa/totp' , login2FALimiter , userController . getTotpVerify ) ;
221+ app . post ( '/login/2fa/totp' , login2FALimiter , userController . postTotpVerify ) ;
208222app . post ( '/login/webauthn-start' , loginLimiter , webauthnController . postLoginStart ) ;
209223app . get ( '/login/webauthn-start' , ( req , res ) => res . redirect ( '/login' ) ) ; // webauthn-start requires a POST
210224app . post ( '/login/webauthn-verify' , loginLimiter , webauthnController . postLoginVerify ) ;
@@ -222,6 +236,11 @@ app.get('/account/verify/:token', passportConfig.isAuthenticated, userController
222236app . get ( '/account' , passportConfig . isAuthenticated , userController . getAccount ) ;
223237app . post ( '/account/profile' , passportConfig . isAuthenticated , userController . postUpdateProfile ) ;
224238app . post ( '/account/password' , passportConfig . isAuthenticated , userController . postUpdatePassword ) ;
239+ app . post ( '/account/2fa/email/enable' , passportConfig . isAuthenticated , userController . postEnable2FA ) ;
240+ app . post ( '/account/2fa/email/remove' , passportConfig . isAuthenticated , userController . postRemoveEmail2FA ) ;
241+ app . get ( '/account/2fa/totp/setup' , passportConfig . isAuthenticated , userController . getTotpSetup ) ;
242+ app . post ( '/account/2fa/totp/setup' , passportConfig . isAuthenticated , userController . postTotpSetup ) ;
243+ app . post ( '/account/2fa/totp/remove' , passportConfig . isAuthenticated , userController . postRemoveTotp ) ;
225244app . post ( '/account/delete' , passportConfig . isAuthenticated , userController . postDeleteAccount ) ;
226245app . post ( '/account/logout-everywhere' , passportConfig . isAuthenticated , userController . postLogoutEverywhere ) ;
227246app . get ( '/account/unlink/:provider' , passportConfig . isAuthenticated , userController . getOauthUnlink ) ;
0 commit comments