Files
Orchestrator/bahn/Projekt-KIQ-HP/server/index.js
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

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}`);
});