import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; import nodemailer from 'nodemailer'; import cors from 'cors'; import dotenv from 'dotenv'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = process.env.PORT || 3000; // Security app.use(helmet({ contentSecurityPolicy: { directives: { ...helmet.contentSecurityPolicy.getDefaultDirectives(), "img-src": ["'self'", "data:"], }, }, })); app.use(cors()); app.use(express.json()); // Rate limiting for API const apiLimiter = rateLimit({ windowMs: 10 * 60 * 1000, // 10 minutes max: 5, // limit each IP to 5 requests per windowMs message: 'Zu viele Anfragen von dieser IP, bitte versuchen Sie es später erneut.', standardHeaders: true, legacyHeaders: false, }); // API Endpoint app.post('/api/contact', apiLimiter, async (req, res) => { const { name, email, company, message, website } = req.body; // Honeypot check if (website) { console.log('Spam detected (honeypot)'); return res.status(200).json({ message: 'Ok' }); // Generic success } // Validation if (!name || name.length < 2 || name.length > 100) { return res.status(400).json({ error: 'Ungültiger Name' }); } if (!email || !/^\S+@\S+\.\S+$/.test(email)) { return res.status(400).json({ error: 'Ungültige E-Mail' }); } if (!message || message.length < 20 || message.length > 4000) { return res.status(400).json({ error: 'Nachricht zu kurz oder zu lang' }); } try { const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT || '587'), secure: process.env.SMTP_SECURE === 'true', auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, }, }); await transporter.sendMail({ from: process.env.SMTP_FROM, to: process.env.CONTACT_RECIPIENT, replyTo: email, subject: `Kontaktanfrage von ${name}`, text: ` Name: ${name} Firma: ${company || 'Nicht angegeben'} E-Mail: ${email} Zeitpunkt: ${new Date().toISOString()} Nachricht: ${message} `, }); res.status(200).json({ message: 'Ok' }); } catch (error) { console.error('SMTP Error:', error); res.status(500).json({ error: 'Interner Serverfehler' }); } }); // Health check app.get('/healthz', (req, res) => { res.status(200).send('OK'); }); // Serve static files from the React app const distPath = path.join(__dirname, 'dist/frontend'); app.use(express.static(distPath)); // The "catchall" handler: for any request that doesn't // match one above, send back React's index.html file. app.get('*', (req, res) => { res.sendFile(path.join(distPath, 'index.html')); }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });