213 lines
6.2 KiB
JavaScript
213 lines
6.2 KiB
JavaScript
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}`);
|
|
});
|