diff --git a/app.js b/app.js index 471c326..2323707 100644 --- a/app.js +++ b/app.js @@ -45,9 +45,30 @@ const memberRoutes = require("./routes/member"); const trainingRoutes = require("./routes/training"); const anwesendRoutes = require("./routes/anwesend"); -app.use("/user", userRoutes); -app.use("/spiele", spieleRoutes); -app.use("/feature", featureRoutes); +app.use( + "/:abteilung?/user", + (req, res, next) => { + req.abteilung = req.params.abteilung; // `abteilung` in req speichern + next(); + }, + userRoutes +); +app.use( + "/:abteilung?/spiele", + (req, res, next) => { + req.abteilung = req.params.abteilung; // `abteilung` in req speichern + next(); + }, + spieleRoutes +); +app.use( + "/:abteilung?/feature", + (req, res, next) => { + req.abteilung = req.params.abteilung; // `abteilung` in req speichern + next(); + }, + featureRoutes +); app.use( "/:abteilung?/member", (req, res, next) => { diff --git a/middleware/auth.js b/middleware/auth.js index ac7e6a4..d6ac2e9 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -20,6 +20,36 @@ const requireAuth = (req, res, next) => { } }; +function requireRole(minRole) { + return (req, res, next) => { + const user = req.user; // Aus decoded JWT + const abteilung = req.abteilung; + + if (!user || !user.roles || !abteilung) { + return res + .status(403) + .json({ error: "Keine Berechtigung (keine Abteilung gesetzt)" }); + } + + const role = user.roles[abteilung]; + + if (!role) { + return res + .status(403) + .json({ error: "Keine Rechte für diese Abteilung" }); + } + console.log(role); + console.log(minRole); + console.log(role <= minRole); + if (role > minRole) { + // z.B. 1: Admin, 2: ÜL, etc. + return res.status(403).json({ error: "Nicht genügend Rechte" }); + } + + next(); + }; +} + const requireAdmin = async (req, res, next) => { try { const result = await pool.query("SELECT role FROM users WHERE id = $1", [ @@ -37,4 +67,4 @@ const requireAdmin = async (req, res, next) => { } }; -module.exports = { requireAuth, requireAdmin }; +module.exports = { requireAuth, requireRole, requireAdmin }; diff --git a/routes/anwesend.js b/routes/anwesend.js index a55a082..2984841 100644 --- a/routes/anwesend.js +++ b/routes/anwesend.js @@ -1,11 +1,15 @@ const express = require("express"); const pool = require("../db"); // PostgreSQL-Datenbankverbindung -const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware +const { + requireAuth, + requireRole, + requireAdmin, +} = require("../middleware/auth"); // Auth-Middleware const router = express.Router(); // **1. Alle Anwesenheiten eines Trainings abrufen** -router.get("/:id", requireAuth, async (req, res) => { +router.get("/:id", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; // Training-ID const abteilung = req.abteilung; // Abteilung aus Middleware @@ -34,7 +38,7 @@ router.get("/:id", requireAuth, async (req, res) => { }); // **2. Anwesenheit für ein Training setzen** -router.post("/:id", requireAuth, async (req, res) => { +router.post("/:id", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; // Training-ID const { inriege, anw } = req.body; // Arrays mit IDs diff --git a/routes/member.js b/routes/member.js index cfffaf8..f2dd2ec 100644 --- a/routes/member.js +++ b/routes/member.js @@ -1,20 +1,25 @@ const express = require("express"); const pool = require("../db"); // PostgreSQL-Datenbankverbindung -const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware +const { + requireAuth, + requireRole, + requireAdmin, +} = require("../middleware/auth"); // Auth-Middleware const router = express.Router(); -router.get("/:id?", requireAuth, async (req, res) => { +router.get("/:id?", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; // Mitglieds-ID (optional) const abteilung = req.abteilung; // Abteilung aus Middleware + console.log(abteilung); try { let query; let values = []; if (id) { if (abteilung) { - // **Einzelnes Mitglied mit aktueller Riege abrufen** + console.log("**Einzelnes Mitglied mit aktueller Riege abrufen**"); query = ` SELECT m.*, @@ -32,12 +37,12 @@ router.get("/:id?", requireAuth, async (req, res) => { `; values = [id, abteilung]; } else { - // **Einzelnes Mitglied ohne Riege abrufen** + console.log(" **Einzelnes Mitglied ohne Riege abrufen**"); query = `SELECT * FROM members WHERE id = $1`; values = [id]; } } else if (abteilung) { - // **Alle Mitglieder einer Abteilung abrufen** + console.log("**Alle Mitglieder einer Abteilung abrufen**"); query = ` SELECT m.*, @@ -51,11 +56,11 @@ router.get("/:id?", requireAuth, async (req, res) => { AND riegenzuordnung.bis IS NULL LEFT JOIN riegen r ON riegenzuordnung.fid_riege = r.id - WHERE r.fid_abteilung = $1 OR r.fid_abteilung IS NULL + WHERE r.fid_abteilung = $1 `; values = [abteilung]; } else { - // **Alle Mitglieder abrufen** + console.log("**Alle Mitglieder abrufen**"); query = `SELECT * FROM mitglieder`; } @@ -73,7 +78,7 @@ router.get("/:id?", requireAuth, async (req, res) => { }); // **2. Mitglied anlegen oder aktualisieren (Nur für Admins)** -router.put("/:id?", requireAuth, requireAdmin, async (req, res) => { +router.put("/:id?", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; const { vorname, @@ -174,7 +179,7 @@ router.put("/:id?", requireAuth, requireAdmin, async (req, res) => { }); // **3. Mitglied löschen (Nur für Admins)** -router.delete("/:id", requireAuth, requireAdmin, async (req, res) => { +router.delete("/:id", requireAuth, requireRole(2), async (req, res) => { const { id } = req.params; try { @@ -197,7 +202,7 @@ router.delete("/:id", requireAuth, requireAdmin, async (req, res) => { }); // **1. notfallnummern abrufen (Falls ID gegeben: Nur die Nummern eines Mitglieds)** -router.get("/phone/:id", requireAuth, async (req, res) => { +router.get("/phone/:id", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; try { @@ -220,7 +225,7 @@ router.get("/phone/:id", requireAuth, async (req, res) => { }); // **2. Telefonnummer anlegen oder aktualisieren (Nur für Admins)** -router.put("/phone/:id?", requireAuth, requireAdmin, async (req, res) => { +router.put("/phone/:id?", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; const { fid_teilnehmer, name, nummer, verbindung, stand } = req.body; @@ -285,7 +290,7 @@ router.put("/phone/:id?", requireAuth, requireAdmin, async (req, res) => { }); // **3. Telefonnummer löschen (Nur für Admins)** -router.delete("/phone/:id", requireAuth, requireAdmin, async (req, res) => { +router.delete("/phone/:id", requireAuth, requireRole(2), async (req, res) => { const { id } = req.params; try { @@ -307,7 +312,7 @@ router.delete("/phone/:id", requireAuth, requireAdmin, async (req, res) => { } }); -router.post("/riege/:id", requireAuth, requireAdmin, async (req, res) => { +router.post("/riege/:id", requireAuth, requireRole(2), async (req, res) => { const { id } = req.params; const abteilung = req.abteilung; const { neue_riege } = req.body; diff --git a/routes/spiele.js b/routes/spiele.js index cc52547..c20a1e7 100644 --- a/routes/spiele.js +++ b/routes/spiele.js @@ -61,7 +61,7 @@ router.delete("/:id", requireAuth, requireAdmin, async (req, res) => { } }); -router.put("/:id?", requireAuth, requireAdmin, async (req, res) => { +router.put("/:id?", requireAuth, async (req, res) => { const { id } = req.params; const { name, material, regeln, variationen, dauer, type } = req.body; @@ -115,5 +115,4 @@ router.put("/:id?", requireAuth, requireAdmin, async (req, res) => { } }); - module.exports = router; diff --git a/routes/training.js b/routes/training.js index b3b71ea..87f47e7 100644 --- a/routes/training.js +++ b/routes/training.js @@ -1,6 +1,6 @@ const express = require("express"); const pool = require("../db"); // PostgreSQL-Datenbankverbindung -const { requireAuth, requireAdmin } = require("../middleware/auth"); // Auth-Middleware +const { requireAuth, requireRole } = require("../middleware/auth"); // Auth-Middleware const router = express.Router(); @@ -55,7 +55,7 @@ router.get("/:year?/:kw?", requireAuth, async (req, res) => { } }); -router.post("/leiten/:id", requireAuth, async (req, res) => { +router.post("/leiten/:id", requireAuth, requireRole(3), async (req, res) => { const { id } = req.params; // Trainings-ID const abteilung = req.abteilung; // Abteilung aus Middleware const { fid_helfer, fid_spiel, typ, action } = req.body; // Daten aus POST-Body @@ -122,7 +122,7 @@ router.post("/leiten/:id", requireAuth, async (req, res) => { } }); -router.post("/new/:jahr/:kw", requireAuth, requireAdmin, async (req, res) => { +router.post("/new/:jahr/:kw", requireAuth, requireRole(2), async (req, res) => { const { jahr, kw } = req.params; const abteilung = req.abteilung; // Abteilung aus Middleware diff --git a/routes/user.js b/routes/user.js index 4b6fd8d..03b49a2 100644 --- a/routes/user.js +++ b/routes/user.js @@ -4,7 +4,11 @@ const bcrypt = require("bcryptjs"); const jwt = require("jsonwebtoken"); const crypto = require("crypto"); const nodemailer = require("nodemailer"); -const { requireAuth, requireAdmin } = require("../middleware/auth"); // Falls Middleware in einer extra Datei liegt +const { + requireAuth, + requireRole, + requireAdmin, +} = require("../middleware/auth"); // Falls Middleware in einer extra Datei liegt const { transporter, sendActivationEmail } = require("../middleware/mail"); const router = express.Router(); @@ -47,7 +51,7 @@ router.post("/login", async (req, res) => { 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", + "SELECT * FROM users WHERE username = $1", [username] ); if (userResult.rows.length > 0) { @@ -56,20 +60,24 @@ router.post("/login", async (req, res) => { const match = await bcrypt.compare(password, user.password); if (match) { if (user.is_active) { + const rightsResult = await pool.query( + `SELECT fid_abteilung, fid_role FROM userrights WHERE fid_user = $1`, + [user.id] + ); + + // Rollen-Map erstellen: + const roles = {}; + rightsResult.rows.forEach((row) => { + roles[row.fid_abteilung] = row.fid_role; + }); const token = jwt.sign( - { id: user.id, username: user.username, role: user.role }, + { id: user.id, username: user.username, roles }, process.env.JWT_SECRET, { - expiresIn: "24h", + expiresIn: "24d", } ); - if (user.admin_status === "expired") { - await pool.query( - "UPDATE users SET role = $1, admin_temp = NULL WHERE id = $2", - ["user", user.id] - ); - } - res.json({ token }); + res.json({ token, roles }); } else { return res.status(401).json({ error: "Auf Freischlatung warten" }); } @@ -209,10 +217,40 @@ router.post("/update", requireAuth, async (req, res) => { // Liste der User mit Rollen und is_active router.get("/admin", requireAuth, requireAdmin, async (req, res) => { try { - const result = await pool.query( - "SELECT id, username, email, role, is_active FROM users ORDER BY is_active ASC, role DESC" + // 1️⃣ Alle Benutzer abfragen: + const usersResult = await pool.query( + "SELECT id, username, email, is_active FROM users ORDER BY is_active ASC" ); - res.json(result.rows); + + const users = usersResult.rows; + + // 2️⃣ Alle Rollen pro User abfragen: + const rolesResult = await pool.query( + `SELECT ur.fid_user, ur.fid_abteilung, ur.fid_role, r.role + FROM userrights ur + JOIN userroles r ON ur.fid_role = r.id` + ); + + // 3️⃣ Rollen für Benutzer gruppieren: + const userRolesMap = {}; + rolesResult.rows.forEach((row) => { + if (!userRolesMap[row.fid_user]) { + userRolesMap[row.fid_user] = []; + } + userRolesMap[row.fid_user].push({ + abteilung: row.fid_abteilung, + role_id: row.fid_role, + role_name: row.role, + }); + }); + + // 4️⃣ Benutzer + Rollen zusammenführen: + const enrichedUsers = users.map((user) => ({ + ...user, + roles: userRolesMap[user.id] || [], + })); + + res.json(enrichedUsers); } catch (err) { console.error(err); res.status(500).json({ error: "Interner Serverfehler" }); @@ -258,6 +296,49 @@ router.put("/activate/:id", requireAuth, requireAdmin, async (req, res) => { } }); +router.put( + "/rights/:userId/:role", + requireAuth, + requireRole(2), + async (req, res) => { + const { userId, role } = req.params; + const abteilung = req.abteilung; // Abteilung aus Middleware + + if (!abteilung || !role) { + return res + .status(400) + .json({ error: "Abteilung und Rolle müssen angegeben werden" }); + } + + try { + // Prüfen, ob bereits ein Recht für User & Abteilung existiert + const existing = await pool.query( + `SELECT id FROM userrights WHERE fid_user = $1 AND fid_abteilung = $2`, + [userId, abteilung] + ); + + if (existing.rows.length > 0) { + // Eintrag existiert → UPDATE + await pool.query(`UPDATE userrights SET fid_role = $1 WHERE id = $2`, [ + role, + existing.rows[0].id, + ]); + } else { + // Kein Eintrag → INSERT + await pool.query( + `INSERT INTO userrights (fid_user, fid_abteilung, fid_role) VALUES ($1, $2, $3)`, + [userId, abteilung, role] + ); + } + + res.json({ message: "Rolle erfolgreich vergeben/geändert" }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "Interner Serverfehler" }); + } + } +); + router.delete("/:id", requireAuth, requireAdmin, async (req, res) => { const { id } = req.params;