import express from 'express'; import * as path from 'path'; import * as nodemailer from 'nodemailer'; import cors from 'cors'; import * as dotenv from 'dotenv'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; // Security app.use(helmet({ contentSecurityPolicy: { directives: { ...helmet.contentSecurityPolicy.getDefaultDirectives(), "script-src": ["'self'", "'sha256-YX4iJw93x5SU0ple+RI+95HNdNBZSA60gR8a5v7HfOA='", "'sha256-ieoeWczDHkReVBsRBqaal5AFMlBtNjMzgwKvLqi/tSU='"], "img-src": ["'self'", "data:", "blob:"], "style-src": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], "font-src": ["'self'", "https://fonts.gstatic.com"], }, }, })); 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('/health', (req, res) => { res.status(200).send('OK'); }); // Serve static files from the React app const distPath = path.join(process.cwd(), '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}`); });