import express from 'express'; import cors from 'cors'; import cookieParser from 'cookie-parser'; import crypto from 'crypto'; import { readFileSync, writeFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import nodemailer from 'nodemailer'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DATA_FILE = join(__dirname, 'data', 'access-requests.json'); // --- Config --- const PORT = process.env.PORT || 3003; const ADMIN_USER = process.env.ADMIN_USER || 'admin'; const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'changeme'; const SESSION_SECRET = process.env.SESSION_SECRET || 'dev-secret-change-in-production'; const SMTP_HOST = process.env.SMTP_HOST; const SMTP_PORT = parseInt(process.env.SMTP_PORT || '587', 10); const SMTP_USER = process.env.SMTP_USER; const SMTP_PASS = process.env.SMTP_PASS; const ALERT_EMAIL = process.env.ALERT_EMAIL; // --- Data helpers --- function loadRequests() { if (!existsSync(DATA_FILE)) return []; try { return JSON.parse(readFileSync(DATA_FILE, 'utf-8')); } catch { return []; } } function saveRequests(requests) { writeFileSync(DATA_FILE, JSON.stringify(requests, null, 2), 'utf-8'); } // Ensure data directory exists import { mkdirSync } from 'fs'; mkdirSync(join(__dirname, 'data'), { recursive: true }); // --- Session store (in-memory, simple) --- const sessions = new Map(); function createSession(user) { const token = crypto.randomBytes(32).toString('hex'); sessions.set(token, { user, createdAt: Date.now() }); return token; } function getSession(token) { return sessions.get(token) || null; } function destroySession(token) { sessions.delete(token); } // --- Auth middleware --- function requireAuth(req, res, next) { const token = req.cookies?.kiq_session; if (!token) return res.status(401).json({ error: 'Nicht authentifiziert' }); const session = getSession(token); if (!session) return res.status(401).json({ error: 'Sitzung abgelaufen' }); req.user = session.user; next(); } // --- Email helper --- async function sendAlertEmail(requestData) { if (!SMTP_HOST || !SMTP_USER || !SMTP_PASS || !ALERT_EMAIL) { console.log('[Email] SMTP not configured, skipping alert email'); return; } try { const transporter = nodemailer.createTransport({ host: SMTP_HOST, port: SMTP_PORT, secure: SMTP_PORT === 465, auth: { user: SMTP_USER, pass: SMTP_PASS }, }); await transporter.sendMail({ from: SMTP_USER, to: ALERT_EMAIL, subject: `[KIQ] Neue Zugriffsanfrage von ${requestData.name}`, text: [ `Neue Zugriffsanfrage eingegangen:`, ``, `Name: ${requestData.name}`, `Unternehmen: ${requestData.company}`, `Rolle: ${requestData.role}`, `E-Mail: ${requestData.email}`, `Telefon: ${requestData.phone}`, `Gesprächsziel: ${requestData.purpose}`, `Motivation: ${requestData.reason}`, ``, `Zeitpunkt: ${new Date().toLocaleString('de-DE')}`, ``, `→ Admin-Dashboard: https://projekt-kiq.d-hive.de/admin`, ].join('\n'), }); console.log('[Email] Alert sent to', ALERT_EMAIL); } catch (err) { console.error('[Email] Failed to send alert:', err.message); } } // --- Express app --- const app = express(); app.use(cors({ origin: process.env.NODE_ENV === 'production' ? 'https://projekt-kiq.d-hive.de' : 'http://localhost:5173', credentials: true, })); app.use(express.json()); app.use(cookieParser(SESSION_SECRET)); // --- Routes --- // Health check app.get('/api/health', (_req, res) => { res.json({ status: 'ok', time: new Date().toISOString() }); }); // Login app.post('/api/login', (req, res) => { const { username, password } = req.body; if (username === ADMIN_USER && password === ADMIN_PASSWORD) { const token = createSession({ username, role: 'admin' }); res.cookie('kiq_session', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 24 * 60 * 60 * 1000, // 24 hours }); return res.json({ user: { username, role: 'admin' } }); } return res.status(401).json({ error: 'Ungültige Anmeldedaten' }); }); // Logout app.post('/api/logout', (req, res) => { const token = req.cookies?.kiq_session; if (token) destroySession(token); res.clearCookie('kiq_session'); res.json({ ok: true }); }); // Current user app.get('/api/me', (req, res) => { const token = req.cookies?.kiq_session; if (!token) return res.json({ user: null }); const session = getSession(token); if (!session) return res.json({ user: null }); res.json({ user: session.user }); }); // Submit access request (public) app.post('/api/access-requests', async (req, res) => { const { user_data } = req.body; if (!user_data || !user_data.name || !user_data.email || !user_data.company) { return res.status(400).json({ error: 'Pflichtfelder fehlen' }); } const requests = loadRequests(); const newRequest = { id: crypto.randomUUID(), user_data, status: 'pending', created_at: new Date().toISOString(), }; requests.push(newRequest); saveRequests(requests); // Send email alert (non-blocking) sendAlertEmail(user_data); res.status(201).json({ ok: true, id: newRequest.id }); }); // Get all access requests (admin only) app.get('/api/access-requests', requireAuth, (_req, res) => { const requests = loadRequests(); // Sort newest first requests.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); res.json(requests); }); // Update access request status (admin only) app.put('/api/access-requests/:id', requireAuth, (req, res) => { const { id } = req.params; const { status } = req.body; if (!['pending', 'approved', 'rejected'].includes(status)) { return res.status(400).json({ error: 'Ungültiger Status' }); } const requests = loadRequests(); const idx = requests.findIndex(r => r.id === id); if (idx === -1) return res.status(404).json({ error: 'Anfrage nicht gefunden' }); requests[idx].status = status; saveRequests(requests); res.json({ ok: true }); }); // --- Start --- app.listen(PORT, () => { console.log(`[KIQ Backend] Running on port ${PORT}`); });