import express from "express"; import bodyParser from "body-parser"; import cors from 'cors'; import { getData, getDateForWeekIndex } from "./service"; import dotenv from 'dotenv'; import path from 'path'; import { getQr } from "./qr"; import { generateToken, verify } from "./auth"; import { InsufficientPermissions } from "./utils"; import { initWebsocket } from "./websocket"; import pizzaDayRoutes from "./routes/pizzaDayRoutes"; import foodRoutes from "./routes/foodRoutes"; import votingRoutes from "./routes/votingRoutes"; import easterEggRoutes from "./routes/easterEggRoutes"; import statsRoutes from "./routes/statsRoutes"; const ENVIRONMENT = process.env.NODE_ENV ?? 'production'; dotenv.config({ path: path.resolve(__dirname, `./.env.${ENVIRONMENT}`) }); // Validace nastavení JWT tokenu - nemá bez něj smysl vůbec povolit server spustit if (!process.env.JWT_SECRET) { throw Error("Není vyplněna proměnná prostředí JWT_SECRET"); } const app = express(); const server = require("http").createServer(app); initWebsocket(server); // Body-parser middleware for parsing JSON app.use(bodyParser.json()); app.use(cors({ origin: '*' })); // Zapínatelný login přes hlavičky - pokud je zapnutý nepovolí "basicauth" const HTTP_REMOTE_USER_ENABLED = process.env.HTTP_REMOTE_USER_ENABLED === 'true' || false; const HTTP_REMOTE_USER_HEADER_NAME = process.env.HTTP_REMOTE_USER_HEADER_NAME ?? 'remote-user'; if (HTTP_REMOTE_USER_ENABLED) { if (!process.env.HTTP_REMOTE_TRUSTED_IPS) { throw new Error('Je zapnutý login z hlaviček, ale není nastaven rozsah adres ze kterých hlavička může přijít.'); } const HTTP_REMOTE_TRUSTED_IPS = process.env.HTTP_REMOTE_TRUSTED_IPS.split(',').map(ip => ip.trim()); //TODO: nevim jak udelat console.log pouze pro "debug" //console.log("Budu věřit hlavičkám z: " + HTTP_REMOTE_TRUSTED_IPS); app.set('trust proxy', HTTP_REMOTE_TRUSTED_IPS); console.log('Zapnutý login přes hlavičky z proxy.'); } // ----------- Metody nevyžadující token -------------- app.get("/api/whoami", (req, res) => { if (!HTTP_REMOTE_USER_ENABLED) { res.status(403).json({ error: 'Není zapnuté přihlášení z hlaviček' }); } res.send(req.header(HTTP_REMOTE_USER_HEADER_NAME)); }) app.post("/api/login", (req, res) => { if (HTTP_REMOTE_USER_ENABLED) { // je rovno app.enabled('trust proxy') // Autentizace pomocí trusted headers const remoteUser = req.header(HTTP_REMOTE_USER_HEADER_NAME); //const remoteName = req.header('remote-name'); if (remoteUser && remoteUser.length > 0 ) { res.status(200).json(generateToken(Buffer.from(remoteUser, 'latin1').toString(), true)); } else { throw Error("Je zapnuto přihlášení přes hlavičky, ale nepřišla hlavička nebo ??"); } } else { // Klasická autentizace loginem if (!req.body?.login || req.body.login.trim().length === 0) { throw Error("Nebyl předán login"); } // TODO zavést podmínky pro délku loginu (min i max) res.status(200).json(generateToken(req.body.login, false)); } }); // TODO dočasné řešení - QR se zobrazuje přes , nemáme sem jak dostat token app.get("/api/qr", (req, res) => { if (!req.query?.login) { throw Error("Nebyl předán login"); } const img = getQr(req.query.login as string); res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': img.length }); res.end(img); }); // ---------------------------------------------------- /** Middleware ověřující JWT token */ app.use("/api/", (req, res, next) => { if (HTTP_REMOTE_USER_ENABLED) { const userHeader = req.header(HTTP_REMOTE_USER_HEADER_NAME); const nameHeader = req.header('remote-name'); const emailHeader = req.header('remote-email'); if (userHeader !== undefined && nameHeader !== undefined) { const remoteName = Buffer.from(nameHeader, 'latin1').toString(); if (ENVIRONMENT !== "production") { console.log("Tvuj username, name a email: %s, %s, %s.", userHeader, remoteName, emailHeader); } } } if (!req.headers.authorization) { return res.status(401).json({ error: 'Nebyl předán autentizační token' }); } const token = req.headers.authorization.split(' ')[1]; if (!verify(token)) { return res.status(403).json({ error: 'Neplatný autentizační token' }); } next(); }); /** Vrátí data pro aktuální den. */ app.get("/api/data", async (req, res) => { let date = undefined; if (req.query.dayIndex != null && typeof req.query.dayIndex === 'string') { const index = parseInt(req.query.dayIndex); if (!isNaN(index)) { date = getDateForWeekIndex(parseInt(req.query.dayIndex)); } } res.status(200).json(await getData(date)); }); // Ostatní routes app.use("/api/pizzaDay", pizzaDayRoutes); app.use("/api/food", foodRoutes); app.use("/api/voting", votingRoutes); app.use("/api/easterEggs", easterEggRoutes); app.use("/api/stats", statsRoutes); app.use('/stats', express.static('public')); app.use(express.static('public')); // Middleware pro zpracování chyb app.use((err: any, req: any, res: any, next: any) => { if (err instanceof InsufficientPermissions) { res.status(403).send({ error: err.message }) } else { res.status(500).send({ error: err.message }) } next(); }); const PORT = process.env.PORT ?? 3001; const HOST = process.env.HOST ?? '0.0.0.0'; server.listen(PORT, () => { console.log(`Server listening on ${HOST}, port ${PORT}`); }); // Umožníme vypnutí serveru přes SIGINT, jinak Docker čeká než ho sestřelí process.on('SIGINT', function () { console.log("\nSIGINT (Ctrl-C), vypínám server"); process.exit(0); });