const express = require("express"); const session = require("express-session"); const bcrypt = require("bcrypt"); const crypto = require("crypto"); const nodemailer = require("nodemailer"); const { Pool } = require("pg"); const path = require("path"); const moment = require("moment"); const { marked } = require("marked"); const axios = require("axios"); require("dotenv").config(); const log = require("node-file-logger"); const QRCode = require("qrcode"); const app = express(); const port = process.env.PORT; const TeleBot = require("telebot"); const bot = new TeleBot(process.env.TELEBOT); telebotChatID = process.env.TELECHAT; bot.on("text", (msg) => { if (msg.from.id == telebotChatID) { msg.reply.text(msg.text); } else { msg.reply.text( "Entschuldige, " + msg.from.username + "\nIch darf nicht mit fremden reden." ); log.Info("Telebot-nachricht: " + msg.from.username + " - " + msg.text); } }); //bot.start(); const options = { folderPath: "./logs/", dateBasedFileNaming: true, fileNamePrefix: "DailyLogs_", fileNameExtension: ".log", dateFormat: "YYYY_MM_D", timeFormat: "h:mm:ss A", }; log.SetUserOptions(options); // Middleware app.use(express.static(path.join(__dirname, "public"))); app.use(express.json()); app.set("view engine", "ejs"); app.use(express.urlencoded({ extended: false })); // Session-Konfiguration app.use( session({ secret: process.env.SESSIONSECRET, resave: false, saveUninitialized: true, cookie: { maxAge: 1000 * 60 * 60 * 24 * 2 }, }) ); // Authentifizierungs-Middleware const requireAuth = (req, res, next) => { if (!req.session.userId) { return res.redirect("/login"); } next(); }; const requireAdmin = (req, res, next) => { if (req.session.role !== "admin") { return res.status(403).send("Access denied"); } next(); }; // Email-Konfiguration const transporter = nodemailer.createTransport({ host: process.env.MAILHOST, port: 465, secure: true, auth: { user: process.env.MAILUSER, pass: process.env.MAILPASS, }, }); // Datenbankverbindung const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); // Registrierung app.post("/register", async (req, res) => { const { username, email, password } = req.body; try { const hashedPassword = await bcrypt.hash(password, 10); await pool.query( "INSERT INTO users (username, email, password) VALUES ($1, $2, $3)", [username, email, hashedPassword] ); const message = "Registrierung erfolgreich. Ein Admin wird dich in kürze freischalten"; const mailOptions = { to: "admin@boergmann.it", from: "admin@boergmann.it", subject: "Neue Registrierung", text: `${username} hat sich registriert`, }; transporter.sendMail(mailOptions, (error) => { if (error) { console.error("Error sending email:", error); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } }); res.render("error", { session: req.session, message }); } catch (error) { console.error("Error registering user:", error); const message = "Error registering user:" + error; res.render("error", { session: req.session, message }); } }); // Login app.post("/login", async (req, res) => { const { username, password } = req.body; try { const userResult = await pool.query( "SELECT *, CASE WHEN admin_temp IS NOT NULL AND (now() - admin_temp) > interval '22 hours' THEN 'expired' ELSE 'valid' END AS admin_status FROM users WHERE username = $1", [username] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; const match = await bcrypt.compare(password, user.password); if (match) { if (user.is_active) { req.session.userId = user.id; req.session.userName = user.username; req.session.activeRiege = 1; req.session.activeTab = "geraete"; req.session.message = [(title = ""), (message = ""), (type = "none")]; if (user.admin_status === "expired") { await pool.query( "UPDATE users SET role = $1, admin_temp = NULL WHERE id = $2", ["user", user.id] ); req.session.role = "user"; } else { req.session.role = user.role; } res.redirect("/"); } else { res.redirect("/freischaltung"); } } else { const message = "Falscher Benutzername oder falsches Passwort"; res.render("login", { session: req.session, username, message }); } } else { const message = "Falscher Benutzername oder falsches Passwort"; res.render("login", { session: req.session, username, message }); } } catch (error) { console.error("Error logging in:", error); const message = "Error logging in:" + error; res.render("error", { session: req.session, message }); } }); //Wird angezeigt, wenn ein nicht freigeschalteter User sich anmelden will. app.get("/freischaltung", async (req, res) => { res.render("freischaltung", { session: req.session }); }); // Logout app.get("/logout", (req, res) => { req.session.destroy((err) => { if (err) { return res.status(500).send("Internal Server Error"); } res.redirect("/"); }); }); // Benutzer freischalten (nur Admin) app.post("/userrights", requireAuth, requireAdmin, async (req, res) => { const { userId, type } = req.body; try { if (type === "activate") { await pool.query("UPDATE users SET is_active = TRUE WHERE id = $1", [ userId, ]); const userResult = await pool.query("SELECT * FROM users WHERE id = $1", [ userId, ]); if (userResult.rows.length > 0) { if (userResult.rows[0].email) { const mailOptions = { to: userResult.rows[0].email, from: "admin@boergmann.it", subject: "Freischaltung", text: `Hallo ${userResult.rows[0].username}, du wurdest soeben freigeschaltet.`, }; transporter.sendMail(mailOptions, (error) => { if (error) { console.error("Error sending email:", error); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } }); } } } else if (type === "admin") { await pool.query("UPDATE users SET role = $1 WHERE id = $2", [ "admin", userId, ]); } else if (type === "admint") { await pool.query( "UPDATE users SET role = $1, admin_temp = $2 WHERE id = $3", ["admin", moment().toDate(), userId] ); } else if (type === "delete") { await pool.query("DELETE FROM users WHERE id = $1", [userId]); } res.redirect("/admin"); } catch (error) { console.error("Error activating user:", error); const message = "Error activating user:" + error; res.render("error", { session: req.session, message }); } }); // Passwort-Zurücksetzung anfordern app.post("/send-password", async (req, res) => { const { email } = req.body; try { const userResult = await pool.query( "SELECT * FROM users WHERE email = $1", [email] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; const token = crypto.randomBytes(20).toString("hex"); const resetLink = `http://tkd.boergmann.it/reset-password/${token}`; await pool.query( "UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE id = $3", [token, (selectedDate = moment().add(1, "d").toDate()), user.id] ); const mailOptions = { to: user.email, from: "admin@boergmann.it", subject: "Password Reset", text: `Click the following link to reset your password: ${resetLink}`, }; transporter.sendMail(mailOptions, (err) => { if (err) { console.error("Error sending email:", err); const message = "Error sending Mail:" + error; res.render("error", { session: req.session, message }); } else { const message = "Password reset link sent"; res.render("error", { session: req.session, message }); } }); } else { const message = "Email not found"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in forgot-password:", error); const message = "Error in forgot-password"; res.render("error", { session: req.session, message }); } }); app.get("/forgot-password", async (req, res) => { res.render("forgot-password", { session: req.session }); }); // Passwort zurücksetzen app.get("/reset-password/:token", async (req, res) => { const { token } = req.params; try { const userResult = await pool.query( "SELECT * FROM users WHERE reset_password_token = $1 AND reset_password_expires > $2", [token, Date.now()] ); if (userResult.rows.length > 0) { res.render("reset-password", { token }); // Stelle sicher, dass es eine reset-password.ejs gibt } else { const message = "Token ungültig oder abgelaufen"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in reset-password:", error); const message = "Error in reset-password"; res.render("error", { session: req.session, message }); } }); app.post("/reset-password/:token", async (req, res) => { const { token } = req.params; const { password } = req.body; try { const userResult = await pool.query( "SELECT * FROM users WHERE reset_password_token = $1 AND reset_password_expires > $2", [token, Date.now()] ); if (userResult.rows.length > 0) { const user = userResult.rows[0]; const hashedPassword = await bcrypt.hash(password, 10); await pool.query( "UPDATE users SET password = $1, reset_password_token = NULL, reset_password_expires = NULL WHERE id = $2", [hashedPassword, user.id] ); res.redirect("/login"); } else { const message = "Token ungültig oder abgelaufen"; res.render("error", { session: req.session, message }); } } catch (error) { console.error("Error in reset-password:", error); const message = "Error in reset-password"; res.render("error", { session: req.session, message }); } }); // Profilseite app.get("/profile", requireAuth, (req, res) => { res.render("profile", { session: req.session }); // Stelle sicher, dass es eine profile.ejs gibt }); app.post("/profile", requireAuth, async (req, res) => { const { email, password } = req.body; try { if (email) { await pool.query("UPDATE users SET email = $1 WHERE id = $2", [ email, req.session.userId, ]); } if (password) { const hashedPassword = await bcrypt.hash(password, 10); await pool.query("UPDATE users SET password = $1 WHERE id = $2", [ hashedPassword, req.session.userId, ]); } res.redirect("/profile"); } catch (error) { console.error("Error updating profile:", error); const message = "Error updating profile:" + error; res.render("error", { session: req.session, message }); } }); // Route für die Projektliste app.get("/projects", async (req, res) => { req.session.message = ["", "", "none"]; try { const result = await pool.query( "SELECT id, name, beschreibung FROM projects" ); const projekte = result.rows; res.render("projects", { projekte, session: req.session }); } catch (err) { console.error(err); res.send(err); } }); app.post("/projects", async (req, res) => { const { name, beschreibung } = req.body; req.session.message = ["", "", "none"]; try { await pool.query( "INSERT INTO projects (name, beschreibung) VALUES ($1, $2)", [name, beschreibung] ); } catch (err) { console.error(err); res.send(err); } try { const result = await pool.query( "SELECT id, name, beschreibung FROM projects" ); const projekte = result.rows; res.render("projects", { projekte, session: req.session }); } catch (err) { console.error(err); res.send(err); } }); app.get("/project/:id", async (req, res) => { req.session.message = ["", "", "none"]; try { const { id } = req.params; const result = await pool.query("SELECT * FROM projects WHERE id = $1", [ id, ]); const projekt = result.rows[0]; var htmlContent = projekt.beschreibung; try { const url = `${projekt.repository_link}/raw/branch/main/README.md`; // Abrufen der Datei const response = await axios.get(url); // Markdown-Inhalt in HTML umwandeln htmlContent = marked(response.data); //const htmlContent = "Testdata"; if (!projekt) { return res.status(404).send("Projekt nicht gefunden"); } } catch (err) { console.error(err); } res.render("project", { projekt, session: req.session, content: htmlContent, }); } catch (err) { console.error(err); } }); app.post("/projectedit", async (req, res) => { req.session.message = ["", "", "none"]; const { id, name, kurzbeschreibung, beschreibung, repository_link } = req.body; try { await pool.query( "UPDATE projects SET name = $1, kurzbeschreibung = $2, beschreibung = $3, repository_link =$4 WHERE id = $5", [name, kurzbeschreibung, beschreibung, repository_link, id] ); } catch (err) { console.error(err); res.send(err); } res.redirect("/project/" + id); }); // Admin-Seite app.get("/admin", requireAuth, requireAdmin, async (req, res) => { const usersResult = await pool.query("SELECT * FROM users"); res.render("admin", { users: usersResult.rows, session: req.session }); // Stelle sicher, dass es eine admin.ejs gibt }); // Login und Registrierung anzeigen app.get("/login", (req, res) => { req.session.message = ["", "", "none"]; res.render("login", { session: req.session }); // Stelle sicher, dass es eine login.ejs gibt }); // Registrierung app.get("/register", (req, res) => { res.render("register", { session: req.session }); // Stelle sicher, dass es eine register.ejs gibt }); app.get("/contact", (req, res) => { res.render("contact", { session: req.session }); }); app.get("/car", (req, res) => { res.render("car", { session: req.session }); }); app.post("/car", (req, res) => { const { kennzeichen, message, contact } = req.body; let formattedLicensePlate = kennzeichen .replace(/[^a-zA-Z0-9]/g, "") .toUpperCase(); if (formattedLicensePlate == "DUKL445") { if (contact != "") { bot.sendMessage(telebotChatID, contact); } bot.sendMessage(telebotChatID, message); console.log(message + contact); req.session.message = ["Success", "Nachricht versendet.", "none"]; } else { log.Info( "Kontakt: " + contact + " - Nachricht: " + message + " - Kennzeichen: " + kennzeichen ); } res.render("carsend", { session: req.session }); }); app.get("/qr", requireAuth, (req, res) => { res.render("qrcode", { session: req.session }); }); app.post("/qr", requireAuth, (req, res) => { const { text, invert } = req.body; const options = { color: { dark: invert ? "#FFFFFF" : "#000000", // Farbe der QR-Code-Muster light: invert ? "#000000" : "#FFFFFF", // Farbe des Hintergrunds }, }; QRCode.toDataURL(text, options, (err, url) => { if (err) { return res.send("Fehler beim Generieren des QR-Codes"); } res.render("qrcodeshow", { session: req.session, qrCodeUrl: url, }); }); }); // Startseite app.get("/", (req, res) => { req.session.message = ["", "", "none"]; res.render("index", { session: req.session }); }); //Datenschutz und Impressum app.get("/impressum", (req, res) => { req.session.message = ["", "", "none"]; res.render("impressum", { session: req.session }); }); const server = app.listen(port, "0.0.0.0", () => { log.Info(`Server is running on ${process.env.HOST}:${port}/`); });