const express = require('express'); const crypto = require('crypto'); const router = express.Router(); const { COOKIE_NAME, verify, createSessionCookie, clearSessionCookie, } = require('../middleware/auth'); // In-memory rate limiter for login attempts const loginAttempts = new Map(); const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute const RATE_LIMIT_MAX = 5; function isRateLimited(ip) { const now = Date.now(); const attempts = loginAttempts.get(ip); if (!attempts) return false; // Remove expired entries const recent = attempts.filter(t => now - t < RATE_LIMIT_WINDOW_MS); if (recent.length === 0) { loginAttempts.delete(ip); return false; } loginAttempts.set(ip, recent); return recent.length >= RATE_LIMIT_MAX; } function recordAttempt(ip) { const now = Date.now(); const attempts = loginAttempts.get(ip) || []; attempts.push(now); loginAttempts.set(ip, attempts); } router.post('/login', (req, res) => { const password = process.env.APP_PASSWORD; if (!password) { return res.status(200).json({ ok: true }); } const ip = req.ip; if (isRateLimited(ip)) { return res.status(429).json({ error: 'Too many login attempts. Try again later.' }); } const submitted = req.body?.password || ''; const submittedBuf = Buffer.from(String(submitted)); const passwordBuf = Buffer.from(password); let match = false; if (submittedBuf.length === passwordBuf.length) { match = crypto.timingSafeEqual(submittedBuf, passwordBuf); } if (!match) { recordAttempt(ip); return res.status(401).json({ error: 'Invalid password' }); } const session = createSessionCookie(); if (!session) { return res.status(500).json({ error: 'Auth not configured' }); } const cookieOpts = { ...session.opts }; if (cookieOpts.secure && !req.secure) cookieOpts.secure = false; res.cookie(COOKIE_NAME, session.token, cookieOpts); res.json({ ok: true }); }); router.post('/logout', (req, res) => { res.cookie(COOKIE_NAME, '', clearSessionCookie()); res.status(204).send(); }); router.get('/me', (req, res) => { if (!process.env.APP_PASSWORD) { return res.status(200).json({ ok: true }); } const token = req.cookies?.[COOKIE_NAME] || null; if (!token || !verify(token)) { return res.status(401).json({ error: 'Unauthorized' }); } res.json({ ok: true }); }); module.exports = router;