import express from "express"; import cookieParser from "cookie-parser"; import bcrypt from "bcrypt"; import fs from "fs"; import crypto from "crypto"; import https from "https"; const USERS = JSON.parse(fs.readFileSync("./users.json")); const SESSIONS = {}; // in-memory session store // Robot-Driver hinter dem Tunnel (TLS, selbst-signiert -> Verify aus) const DRIVER_HOST = "appServer_TunnelHead"; const DRIVER_PORT = 9798; // Kleiner HTTPS-Proxy-Helfer zum Driver (Core-Modul, keine Extra-Dependency) function driverRequest(method, path) { return new Promise((resolve, reject) => { const req = https.request( { host: DRIVER_HOST, port: DRIVER_PORT, path, method, rejectUnauthorized: false, // entspricht nginx proxy_ssl_verify off timeout: 5000 }, (res) => { let body = ""; res.on("data", (c) => { body += c; }); res.on("end", () => resolve({ status: res.statusCode, body })); } ); req.on("timeout", () => req.destroy(new Error("driver timeout"))); req.on("error", reject); req.end(); }); } // Session-Guard: true wenn eingeloggt, sonst sendet selbst 401 function requireSession(req, res) { const sid = req.cookies.SESSIONID; if (sid && SESSIONS[sid]) return true; res.status(401).send({ ok: false, error: "not authenticated" }); return false; } const app = express(); app.use(express.json()); app.use(cookieParser()); app.post("/api/login", async (req,res)=>{ const { user, pass } = req.body; console.log(`Auth-Service login attempt for ${user}`); const hash = USERS[user]; if(!hash) return res.status(401).send({ ok:false }); const valid = await bcrypt.compare(pass, hash); if(!valid) return res.status(401).send({ ok:false }); // create secure random session const sessionID = crypto.randomBytes(32).toString("hex"); SESSIONS[sessionID] = { user, created: Date.now() }; res.cookie("SESSIONID", sessionID, { httpOnly: true, secure: true, domain: ".server.schooltech.ch", sameSite: "None", path: "/" }); res.status(200).send({ ok:true }); }); // Logout endpoint app.post("/api/logout", (req, res) => { const sid = req.cookies.SESSIONID; if (sid && SESSIONS[sid]) { delete SESSIONS[sid]; } // Cookie löschen res.clearCookie("SESSIONID", { httpOnly: true, secure: true, domain: ".server.schooltech.ch", sameSite: "None", path: "/" }); return res.status(200).send({ ok: true }); }); // Event logging endpoint for frontend button presses app.post('/api/event', (req,res)=>{ const svc = req.body.service || req.body.action || 'unknown'; const user = req.cookies.SESSIONID || 'anonymous'; console.log(`Event: user=${user} service=${svc} payload=${JSON.stringify(req.body)}`); res.status(200).send({ ok:true }); }); // Optional für Nginx auth_request app.get("/internal/auth", (req,res)=>{ const sid = req.cookies.SESSIONID; if (sid && SESSIONS[sid]) { return res.sendStatus(200); } return res.sendStatus(401); }); // Status endpoint (unter /api so dass Nginx /api/ auf appserverauth proxyt) app.get("/api/status", (req, res) => { const sid = req.cookies.SESSIONID; if (sid && SESSIONS[sid]) { return res.status(200).send({ ok: true, user: SESSIONS[sid].user }); } return res.status(401).send({ ok: false }); }); // =========================== // Robot-Driver Proxy (same-origin, auth-geschützt) // Nginx leitet /api/ auf diesen Service -> kein CORS, Cookie wird mitgeschickt. // =========================== // Armed-Status abfragen: GET /api/power-status app.get("/api/power-status", async (req, res) => { if (!requireSession(req, res)) return; try { const r = await driverRequest("GET", "/api/power-status"); res.status(r.status).type("application/json").send(r.body); } catch (e) { console.error("power-status proxy error:", e.message); // Failsafe: Driver nicht erreichbar -> armed:false, Button bleibt versteckt res.status(502).send({ ok: false, armed: false, error: "driver unreachable" }); } }); // Not-Aus auslösen: POST /api/emergency-stop app.post("/api/emergency-stop", async (req, res) => { if (!requireSession(req, res)) return; const user = SESSIONS[req.cookies.SESSIONID].user; console.log(`EMERGENCY-STOP ausgelöst von user=${user}`); try { const r = await driverRequest("POST", "/api/emergency-stop"); res.status(r.status).type("application/json").send(r.body); } catch (e) { console.error("emergency-stop proxy error:", e.message); res.status(502).send({ ok: false, error: "driver unreachable" }); } }); app.listen(3000, ()=>console.log("Auth-Service läuft auf 3000"));