Skip to content

Commit 482ce9d

Browse files
committed
feat: Implement passwordless authentication system
- Add passwordless login option via email links - Add passwordless signup option with automatic login link - Refactor email verification system for unified security settings - Add IP hash verification to prevent MITM attacks - Add token expiration and automatic cleanup via middleware - Add database indexes for token fields - Improve email templates with security instructions - Enhance flash messages for better user feedback - Add token verification checks before processing links - Consolidate email sending logic for maintainability - Add security headers and CSRF protection for email links - Implement automatic cleanup of expired tokens on save - Add more restrictive rate limiting to auth routes This major update improves security and user experience by adding passwordless authentication while hardening the existing email verification system against common attack vectors.
1 parent 6e656d4 commit 482ce9d

File tree

7 files changed

+746
-176
lines changed

7 files changed

+746
-176
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ I also tried to make it as **generic** and **reusable** as possible to cover mos
7070
## Features
7171

7272
- Login
73-
- **Local Authentication** using Email and Password
73+
- **Local Authentication** using Email and Password, as well as Passwordless
7474
- **OAuth 2.0 Authentication:** Sign in with Google, Facebook, X (Twitter), Twitch, Github
7575
- **OpenID Connect:** Sign in with LinkedIn
7676
- **User Profile and Account Management**

app.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,34 @@ dotenv.config({ path: '.env.example' });
2424
*/
2525
const secureTransfer = process.env.BASE_URL.startsWith('https');
2626

27-
// Consider adding a proxy such as cloudflare for production.
27+
/**
28+
* Rate limiting configuration
29+
* This is a basic rate limiting configuration. You may want to adjust the settings
30+
* based on your application's needs and the expected traffic patterns.
31+
* Alos, consider adding a proxy such as cloudflare for production.
32+
*/
33+
// Global Rate Limiter Config
2834
const limiter = rateLimit({
2935
windowMs: 15 * 60 * 1000, // 15 minutes
3036
max: 200, // Limit each IP to 200 requests per `window` (here, per 15 minutes)
3137
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
3238
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
3339
});
40+
// Strict Auth Rate Limiter Config for signup, password recover, account verification, login by email
41+
const strictLimiter = rateLimit({
42+
windowMs: 60 * 60 * 1000, // 1 hour
43+
max: 5, // 5 attempts per hour
44+
standardHeaders: true,
45+
legacyHeaders: false,
46+
});
47+
48+
// Login Rate Limiter Config
49+
const loginLimiter = rateLimit({
50+
windowMs: 60 * 60 * 1000, // 1 hour
51+
max: 10, // 10 attempts per hour
52+
standardHeaders: true,
53+
legacyHeaders: false,
54+
});
3455

3556
// This logic for numberOfProxies works for local testing, ngrok use, single host deployments
3657
// behind cloudflare, etc. You may need to change it for more complex network settings.
@@ -158,15 +179,16 @@ app.locals.FACEBOOK_PIXEL_ID = process.env.FACEBOOK_PIXEL_ID ? process.env.FACEB
158179
*/
159180
app.get('/', homeController.index);
160181
app.get('/login', userController.getLogin);
161-
app.post('/login', userController.postLogin);
182+
app.post('/login', loginLimiter, userController.postLogin);
183+
app.get('/login/verify/:token', loginLimiter, userController.getLoginByEmail);
162184
app.get('/logout', userController.logout);
163185
app.get('/forgot', userController.getForgot);
164-
app.post('/forgot', userController.postForgot);
186+
app.post('/forgot', strictLimiter, userController.postForgot);
165187
app.get('/reset/:token', userController.getReset);
166-
app.post('/reset/:token', userController.postReset);
188+
app.post('/reset/:token', loginLimiter, userController.postReset);
167189
app.get('/signup', userController.getSignup);
168190
app.post('/signup', userController.postSignup);
169-
app.get('/contact', contactController.getContact);
191+
app.get('/contact', strictLimiter, contactController.getContact);
170192
app.post('/contact', contactController.postContact);
171193
app.get('/account/verify', passportConfig.isAuthenticated, userController.getVerifyEmail);
172194
app.get('/account/verify/:token', passportConfig.isAuthenticated, userController.getVerifyEmailToken);
@@ -199,7 +221,7 @@ app.get('/api/paypal/success', apiController.getPayPalSuccess);
199221
app.get('/api/paypal/cancel', apiController.getPayPalCancel);
200222
app.get('/api/lob', apiController.getLob);
201223
app.get('/api/upload', lusca({ csrf: true }), apiController.getFileUpload);
202-
app.post('/api/upload', apiController.uploadMiddleware, lusca({ csrf: true }), apiController.postFileUpload);
224+
app.post('/api/upload', strictLimiter, apiController.uploadMiddleware, lusca({ csrf: true }), apiController.postFileUpload);
203225
app.get('/api/here-maps', apiController.getHereMaps);
204226
app.get('/api/google-maps', apiController.getGoogleMaps);
205227
app.get('/api/google/drive', passportConfig.isAuthenticated, passportConfig.isAuthorized, apiController.getGoogleDrive);

0 commit comments

Comments
 (0)