source: Klonkt/src/middleware/rate-limit.js@ 21522ae

main
Last change on this file since 21522ae was 7bc636b, checked in by Robin <robin@…>, 4 months ago

Initial commit — PrutFolio v1 source (pulled from Hetzner /srv/prutfolio)

  • Property mode set to 100644
File size: 2.1 KB
Line 
1/**
2 * Rate limiters — anti-brute-force.
3 *
4 * In-memory store via express-rate-limit's default. Fine for a single Node
5 * process. If we ever scale to multiple workers, swap to a shared store
6 * (Redis or rate-limit-redis).
7 *
8 * Friendly 429 response handles HTMX requests (returns HX-Redirect to the
9 * form page so the error is shown inline) and full requests (renders a
10 * small message page).
11 */
12
13import rateLimit from 'express-rate-limit';
14import { renderPage } from './render.js';
15
16function blockedHandler(viewName, bodyClass, friendlyMsg) {
17 return (req, res, next, options) => {
18 const message = `${friendlyMsg} Try again in a few minutes.`;
19 if (req.headers['hx-request'] === 'true') {
20 // HTMX: surface the error in the form's error slot via partial render.
21 return renderPage(req, res, viewName, {
22 pageTitle: 'Too many attempts',
23 bodyClass,
24 error: message,
25 username: req.body?.username || '',
26 email: req.body?.email || '',
27 });
28 }
29 res.status(options.statusCode).type('html').send(`
30 <div style="font-family:system-ui;max-width:520px;margin:4rem auto;padding:2rem;text-align:center;">
31 <h1 style="font-size:2rem;margin:0 0 0.5rem;">Too many attempts</h1>
32 <p>${message}</p>
33 <p><a href="/" style="color:#c2410c;">&larr; Home</a></p>
34 </div>
35 `);
36 };
37}
38
39export const loginLimiter = rateLimit({
40 windowMs: 15 * 60 * 1000, // 15 min
41 max: 5, // 5 attempts per IP per window
42 standardHeaders: true,
43 legacyHeaders: false,
44 // Only count failed attempts. Successful logins don't burn the budget.
45 skipSuccessfulRequests: true,
46 handler: blockedHandler('pages/auth-login', 'on-special', 'Too many login attempts.'),
47});
48
49export const registerLimiter = rateLimit({
50 windowMs: 60 * 60 * 1000, // 1 hour
51 max: 5, // 5 signups per IP per hour
52 standardHeaders: true,
53 legacyHeaders: false,
54 skipSuccessfulRequests: false, // any attempt counts (registration spam is the concern)
55 handler: blockedHandler('pages/auth-register', 'on-special', 'Too many signup attempts.'),
56});
Note: See TracBrowser for help on using the repository browser.