import express from 'express'; import nodemailer from 'nodemailer'; const app = express(); app.use(express.json()); // Rate limiter: max 5 requests/min per IP const rateLimiter = new Map(); function checkRateLimit(ip) { const now = Date.now(); const window = 60_000; const max = 5; const entry = rateLimiter.get(ip) ?? { count: 0, start: now }; if (now - entry.start > window) { entry.count = 0; entry.start = now; } entry.count++; rateLimiter.set(ip, entry); return entry.count <= max; } // Prune rate limiter every 5 minutes setInterval(() => { const cutoff = Date.now() - 60_000; for (const [ip, entry] of rateLimiter) { if (entry.start < cutoff) rateLimiter.delete(ip); } }, 300_000); const transporter = nodemailer.createTransport({ host: 'smtp.fastmail.com', port: 465, secure: true, auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, }, }); app.post('/api/contact', async (req, res) => { const ip = (req.headers['x-forwarded-for'] ?? '').split(',')[0].trim() || req.socket.remoteAddress; if (!checkRateLimit(ip)) { return res.status(429).json({ error: 'Too many requests' }); } const { name, email, message, company } = req.body ?? {}; if (!name || typeof name !== 'string' || !name.trim()) { return res.status(400).json({ error: 'name required' }); } if (!email || typeof email !== 'string' || !email.includes('@')) { return res.status(400).json({ error: 'valid email required' }); } if (!message || typeof message !== 'string' || message.trim().length < 5) { return res.status(400).json({ error: 'message too short' }); } // Honeypot if (company) { return res.json({ ok: true }); } try { await transporter.sendMail({ from: 'oskar@gethumanai.com', to: 'oskar@gethumanai.com', replyTo: email, subject: `Kontakt od ${name.trim()}`, text: `ImiÄ™: ${name.trim()}\nEmail: ${email}\n\n${message.trim()}`, }); res.json({ ok: true }); } catch (err) { console.error('SMTP error:', err); res.status(502).json({ error: 'mail delivery failed' }); } }); app.listen(3000, () => console.log('mailer listening on :3000'));