152 lines
5.9 KiB
JavaScript
152 lines
5.9 KiB
JavaScript
const express = require('express');
|
||
const crypto = require('crypto');
|
||
const router = express.Router();
|
||
const dns = require('dns').promises;
|
||
const db = require('../config/database');
|
||
|
||
// Email Validierung via DNS MX-Record Check
|
||
async function verifyEmailDomain(email) {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) return { valid: false, reason: 'Ungültiges E-Mail-Format' };
|
||
|
||
const domain = email.split('@')[1];
|
||
try {
|
||
const records = await dns.resolveMx(domain);
|
||
if (records && records.length > 0) {
|
||
return { valid: true };
|
||
}
|
||
return { valid: false, reason: 'Domain hat keine E-Mail-Server (MX-Records fehlen)' };
|
||
} catch (err) {
|
||
return { valid: false, reason: 'E-Mail-Domain konnte nicht verifiziert werden' };
|
||
}
|
||
}
|
||
|
||
|
||
// Server-seitige IBAN Prüfung (Modulo-97)
|
||
const IBAN_LENGTHS = {
|
||
AL:28,AD:24,AT:20,AZ:28,BH:22,BE:16,BA:20,BR:29,BG:22,CR:22,HR:21,
|
||
CY:28,CZ:24,DK:18,DO:28,EE:20,FO:18,FI:18,FR:27,GE:22,DE:22,GI:23,
|
||
GL:18,GT:28,HU:28,IS:26,IE:22,IL:23,IT:27,JO:30,KZ:20,KW:30,LV:21,
|
||
LB:28,LI:21,LT:20,LU:20,MK:19,MT:31,MR:27,MU:30,MC:27,MD:24,ME:22,
|
||
NL:18,NO:15,PK:24,PS:29,PL:28,PT:25,QA:29,RO:24,SM:27,SA:24,RS:22,
|
||
SK:24,SI:19,ES:24,SE:24,CH:21,TN:24,TR:26,AE:23,GB:22,VG:24
|
||
};
|
||
function validateIBANServer(iban) {
|
||
if (!iban) return { valid: true }; // Optional – kein IBAN = ok
|
||
const clean = iban.replace(/\s/g, '').toUpperCase();
|
||
if (clean.length < 5) return { valid: false, error: 'IBAN zu kurz' };
|
||
const country = clean.substring(0, 2);
|
||
if (!/^[A-Z]{2}$/.test(country)) return { valid: false, error: 'Ungültiger Ländercode' };
|
||
const expectedLen = IBAN_LENGTHS[country];
|
||
if (!expectedLen) return { valid: false, error: 'Ländercode nicht unterstützt' };
|
||
if (clean.length !== expectedLen) return { valid: false, error: country + '-IBAN muss ' + expectedLen + ' Zeichen haben' };
|
||
const rearranged = clean.substring(4) + clean.substring(0, 4);
|
||
const numeric = rearranged.split('').map(c => {
|
||
const code = c.charCodeAt(0);
|
||
return code >= 65 ? (code - 55).toString() : c;
|
||
}).join('');
|
||
let remainder = 0;
|
||
for (let i = 0; i < numeric.length; i++) {
|
||
remainder = (remainder * 10 + parseInt(numeric[i])) % 97;
|
||
}
|
||
if (remainder !== 1) return { valid: false, error: 'IBAN Prüfziffer ungültig' };
|
||
return { valid: true };
|
||
}
|
||
|
||
// POST /api/verify-email
|
||
router.post('/verify-email', async (req, res) => {
|
||
const { email } = req.body;
|
||
if (!email) return res.json({ valid: false, reason: 'Keine E-Mail angegeben' });
|
||
|
||
const result = await verifyEmailDomain(email);
|
||
res.json(result);
|
||
});
|
||
|
||
// POST /api/submit-membership
|
||
router.post('/submit-membership', async (req, res) => {
|
||
try {
|
||
const {
|
||
tariff_id, salutation, title, first_name, last_name, birth_date,
|
||
email, phone, street, address_addition, zip, city,
|
||
bank_name, account_holder, iban,
|
||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct,
|
||
guardian_consent
|
||
} = req.body;
|
||
|
||
// E-Mail validieren
|
||
const emailCheck = await verifyEmailDomain(email);
|
||
if (!emailCheck.valid) {
|
||
return res.json({ success: false, error: 'E-Mail-Adresse ist nicht erreichbar: ' + emailCheck.reason });
|
||
}
|
||
|
||
// IBAN prüfen (falls angegeben)
|
||
if (iban && iban.trim()) {
|
||
const ibanCheck = validateIBANServer(iban.trim());
|
||
if (!ibanCheck.valid) {
|
||
return res.json({ success: false, error: 'IBAN ungültig: ' + ibanCheck.error });
|
||
}
|
||
}
|
||
|
||
// Pflichtfelder prüfen
|
||
if (!tariff_id || !first_name || !last_name || !birth_date || !email || !street || !zip || !city) {
|
||
return res.json({ success: false, error: 'Bitte alle Pflichtfelder ausfüllen.' });
|
||
}
|
||
|
||
if (!agb_accepted || !datenschutz_accepted || !data_correct) {
|
||
return res.json({ success: false, error: 'Bitte alle Einverständniserklärungen bestätigen.' });
|
||
}
|
||
|
||
// Alter berechnen
|
||
const birthDateObj = new Date(birth_date);
|
||
const today = new Date();
|
||
let age = today.getFullYear() - birthDateObj.getFullYear();
|
||
const m = today.getMonth() - birthDateObj.getMonth();
|
||
if (m < 0 || (m === 0 && today.getDate() < birthDateObj.getDate())) age--;
|
||
|
||
const is_minor = age < 18 ? 1 : 0;
|
||
|
||
if (is_minor && !guardian_consent) {
|
||
return res.json({ success: false, error: 'Bei Minderjährigen ist die Einverständniserklärung der Erziehungsberechtigten erforderlich.' });
|
||
}
|
||
|
||
if (age < 14) {
|
||
return res.json({ success: false, error: 'Das Mindestalter für eine Mitgliedschaft beträgt 14 Jahre.' });
|
||
}
|
||
|
||
// Tarif prüfen
|
||
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE id = ? AND active = 1', [tariff_id]);
|
||
if (tariffs.length === 0) {
|
||
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
|
||
}
|
||
|
||
// Zugangstoken generieren
|
||
const access_token = crypto.randomBytes(16).toString('hex');
|
||
|
||
// In DB speichern
|
||
await db.query(`
|
||
INSERT INTO memberships
|
||
(tariff_id, salutation, title, first_name, last_name, birth_date, email, phone,
|
||
street, address_addition, zip, city, bank_name, account_holder, iban,
|
||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct, guardian_consent, is_minor, access_token,
|
||
agreed_price, agreed_duration)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
`, [
|
||
tariff_id, salutation, title || '', first_name, last_name, birth_date,
|
||
email, phone || '', street, address_addition || '', zip, city,
|
||
bank_name || '', account_holder || '', iban || '',
|
||
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
|
||
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
|
||
guardian_consent ? 1 : 0, is_minor, access_token,
|
||
tariffs[0].price_monthly, tariffs[0].duration_months
|
||
]);
|
||
|
||
res.json({ success: true });
|
||
|
||
} catch (err) {
|
||
console.error('Submit error:', err);
|
||
res.json({ success: false, error: 'Serverfehler. Bitte versuche es später erneut.' });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|