109 lines
3.6 KiB
JavaScript
109 lines
3.6 KiB
JavaScript
// Kontaktformular-Dienst für atilgan-gartenbau.de
|
||
// Nimmt das Formular per POST entgegen und verschickt es als E-Mail über das
|
||
// eigene Postfach (SMTP). Speichert nichts – die Daten verlassen den eigenen
|
||
// Server nur als E-Mail an den Betreiber.
|
||
|
||
import express from 'express';
|
||
import cors from 'cors';
|
||
import rateLimit from 'express-rate-limit';
|
||
import nodemailer from 'nodemailer';
|
||
|
||
const {
|
||
PORT = 3000,
|
||
ALLOWED_ORIGIN = '', // z. B. "https://atilgan-gartenbau.de,https://www.atilgan-gartenbau.de"
|
||
SMTP_HOST,
|
||
SMTP_PORT = 587,
|
||
SMTP_SECURE = 'false', // "true" für Port 465
|
||
SMTP_USER,
|
||
SMTP_PASS,
|
||
MAIL_TO = 'info@adnan-atilgan.de',
|
||
MAIL_FROM, // Absender; wenn leer, wird SMTP_USER genutzt
|
||
MAIL_SUBJECT_PREFIX = '[Webseite] ',
|
||
} = process.env;
|
||
|
||
const app = express();
|
||
app.set('trust proxy', 1); // hinter Coolify/Reverse-Proxy korrekte IP
|
||
app.use(express.json({ limit: '32kb' }));
|
||
|
||
// --- CORS: nur die eigene Website darf das Formular abschicken ---
|
||
const allowList = ALLOWED_ORIGIN.split(',').map((s) => s.trim()).filter(Boolean);
|
||
app.use(
|
||
cors({
|
||
origin(origin, cb) {
|
||
// Anfragen ohne Origin (z. B. Health-Checks) erlauben
|
||
if (!origin || allowList.length === 0 || allowList.includes(origin)) return cb(null, true);
|
||
return cb(new Error('Origin nicht erlaubt'));
|
||
},
|
||
methods: ['POST', 'OPTIONS'],
|
||
})
|
||
);
|
||
|
||
// --- Rate-Limit gegen Spam/Missbrauch ---
|
||
const limiter = rateLimit({
|
||
windowMs: 10 * 60 * 1000, // 10 Minuten
|
||
max: 5, // max. 5 Nachrichten pro IP und Fenster
|
||
standardHeaders: true,
|
||
legacyHeaders: false,
|
||
message: { ok: false, error: 'Zu viele Anfragen. Bitte versuchen Sie es später erneut.' },
|
||
});
|
||
|
||
// --- SMTP-Transport ---
|
||
const transporter = nodemailer.createTransport({
|
||
host: SMTP_HOST,
|
||
port: Number(SMTP_PORT),
|
||
secure: String(SMTP_SECURE) === 'true',
|
||
auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined,
|
||
});
|
||
|
||
const isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v || '');
|
||
const clean = (v) => String(v || '').trim().slice(0, 5000);
|
||
|
||
// --- Health-Check ---
|
||
app.get('/', (_req, res) => res.json({ ok: true, service: 'kontakt-api' }));
|
||
app.get('/health', (_req, res) => res.json({ ok: true }));
|
||
|
||
// --- Formular-Endpunkt ---
|
||
app.post(['/api/kontakt', '/kontakt'], limiter, async (req, res) => {
|
||
try {
|
||
const b = req.body || {};
|
||
|
||
// Honeypot: echtes Formular lässt dieses Feld leer; Bots füllen es aus
|
||
if (clean(b.website)) return res.json({ ok: true }); // still & unauffällig „erfolgreich"
|
||
|
||
const name = clean(b.name);
|
||
const email = clean(b.email);
|
||
const telefon = clean(b.telefon);
|
||
const betreff = clean(b.betreff) || 'Anfrage über die Webseite';
|
||
const nachricht = clean(b.nachricht);
|
||
|
||
if (!name || !nachricht || !isEmail(email)) {
|
||
return res.status(400).json({ ok: false, error: 'Bitte Name, gültige E-Mail und Nachricht angeben.' });
|
||
}
|
||
|
||
const text =
|
||
`Neue Nachricht über das Kontaktformular\n\n` +
|
||
`Name: ${name}\n` +
|
||
`E-Mail: ${email}\n` +
|
||
`Telefon: ${telefon || '-'}\n` +
|
||
`Betreff: ${betreff}\n\n` +
|
||
`Nachricht:\n${nachricht}\n`;
|
||
|
||
await transporter.sendMail({
|
||
from: MAIL_FROM || SMTP_USER,
|
||
to: MAIL_TO,
|
||
replyTo: `${name} <${email}>`,
|
||
subject: `${MAIL_SUBJECT_PREFIX}${betreff}`,
|
||
text,
|
||
});
|
||
|
||
return res.json({ ok: true });
|
||
} catch (err) {
|
||
console.error('Mailversand fehlgeschlagen:', err.message);
|
||
return res.status(500).json({ ok: false, error: 'Nachricht konnte nicht gesendet werden.' });
|
||
}
|
||
});
|
||
|
||
app.listen(PORT, () => {
|
||
console.log(`kontakt-api läuft auf Port ${PORT}`);
|
||
});
|