This commit is contained in:
2026-01-15 12:10:24 +01:00
parent 0cc0db81ba
commit e92b8e14f3
21 changed files with 5790 additions and 0 deletions

111
server.ts Normal file
View File

@@ -0,0 +1,111 @@
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}`);
});