tkd-api/routes/user.js

423 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require("express");
const pool = require("../db"); // Stelle sicher, dass dein DB-Pool importiert wird
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const nodemailer = require("nodemailer");
const {
requireAuth,
requireRole,
requireAdmin,
} = require("../middleware/auth"); // Falls Middleware in einer extra Datei liegt
const { transporter, sendActivationEmail } = require("../middleware/mail");
const router = express.Router();
// Registrierung
router.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);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
res.status(201).json({ message: message });
} catch (error) {
console.error("Error registering user:", error);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
// Login
router.post("/login", async (req, res) => {
const { username, password } = req.body;
try {
const userResult = await pool.query(
"SELECT * FROM users WHERE username = $1",
[username]
);
if (userResult.rows.length > 0) {
const user = userResult.rows[0];
console.log(user);
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, roles },
process.env.JWT_SECRET,
{
expiresIn: "24d",
}
);
res.json({ token, roles });
} else {
return res.status(401).json({ error: "Auf Freischlatung warten" });
}
} else {
const message = "Falsches Passwort";
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
}
} else {
const message = "Unbekannter Benutzer";
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
}
} catch (error) {
console.error("Error logging in:", error);
const message = "Error logging in:" + error;
return res.status(401).json({ error: "Ungültige Anmeldedaten" });
}
});
// Reset initiieren
router.post("/reset-password", async (req, res) => {
const { email } = req.body;
if (!email) {
return res.status(400).json({ error: "E-Mail-Adresse ist erforderlich" });
}
try {
// Prüfen, ob die E-Mail existiert
const userResult = await pool.query(
"SELECT id, username FROM users WHERE email = $1",
[email]
);
if (userResult.rows.length === 0) {
return res
.status(404)
.json({ error: "Benutzer mit dieser E-Mail nicht gefunden" });
}
const { id, username } = userResult.rows[0];
// Token generieren (random 32 Byte)
const token = crypto.randomBytes(32).toString("hex");
const expires = new Date(Date.now() + 3600000); // Token 1 Stunde gültig
// Token und Ablaufdatum in die DB speichern
await pool.query(
"UPDATE users SET reset_password_token = $1, reset_password_expires = $2 WHERE id = $3",
[token, expires, id]
);
// Mail senden
const resetLink = `https://${process.env.HOST}/reset/${token}`;
const mailOptions = {
from: process.env.MAILUSER,
to: email,
subject: "Passwort zurücksetzen",
text: `Hallo ${username},\n\nKlicke auf den folgenden Link, um dein Passwort zurückzusetzen:\n\n${resetLink}\n\nDer Link ist 1 Stunde gültig.\n\nFalls du das Zurücksetzen nicht beantragt hast, ignoriere diese Nachricht.`,
};
await transporter.sendMail(mailOptions);
res.json({ message: "Passwort-Reset-Link wurde per E-Mail gesendet." });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
// Reset Abschließen
router.post("/reset/:token", async (req, res) => {
const { token } = req.params;
const { password } = req.body;
if (!password) {
return res.status(400).json({ error: "Neues Passwort ist erforderlich" });
}
try {
// Token in der Datenbank suchen und prüfen, ob es noch gültig ist
const result = await pool.query(
"SELECT id FROM users WHERE reset_password_token = $1 AND reset_password_expires > NOW()",
[token]
);
if (result.rows.length === 0) {
return res
.status(400)
.json({ error: "Ungültiges oder abgelaufenes Token" });
}
const userId = result.rows[0].id;
// Passwort hashen
const hashedPassword = await bcrypt.hash(password, 10);
// Passwort speichern und Token-Felder leeren
await pool.query(
"UPDATE users SET password = $1, reset_password_token = NULL, reset_password_expires = NULL WHERE id = $2",
[hashedPassword, userId]
);
res.json({ message: "Passwort erfolgreich zurückgesetzt" });
} catch (err) {
console.error(err);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
// Email und/oder Passwort ändern
router.post("/update", 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.user.id,
]);
}
if (password) {
const hashedPassword = await bcrypt.hash(password, 10);
await pool.query("UPDATE users SET password = $1 WHERE id = $2", [
hashedPassword,
req.user.id,
]);
}
res.json({
message: "Benutzerdaten erfolgreich aktualisiert",
});
} catch (error) {
console.error("Error updating profile:", error);
const message = "Error updating profile:" + error;
res.status(400).json({ error: "Keine Änderungen vorgenommen" });
}
});
// Liste der User mit Rollen und is_active
router.get("/admin", requireAuth, requireAdmin, async (req, res) => {
try {
// 1⃣ Alle Benutzer abfragen:
const usersResult = await pool.query(
"SELECT id, username, email, is_active FROM users ORDER BY is_active ASC"
);
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" });
}
});
router.put("/activate/:id", requireAuth, requireAdmin, async (req, res) => {
const { id } = req.params;
try {
// Benutzer-Daten abrufen (inklusive E-Mail und Benutzername)
const userResult = await pool.query(
"SELECT username, email FROM users WHERE id = $1",
[id]
);
if (userResult.rows.length === 0) {
return res.status(404).json({ error: "Benutzer nicht gefunden" });
}
const { username, email } = userResult.rows[0];
// Benutzer aktivieren
const result = await pool.query(
"UPDATE users SET is_active = true WHERE id = $1 RETURNING id, username, is_active",
[id]
);
if (result.rows.length === 0) {
return res.status(404).json({ error: "Benutzer nicht gefunden" });
}
// E-Mail senden (wenn vorhanden)
await sendActivationEmail(email, username);
res.json({
message: "Benutzer erfolgreich aktiviert",
user: result.rows[0],
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
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;
try {
// Prüfen, ob der Benutzer existiert
const userResult = await pool.query(
"SELECT id, username FROM users WHERE id = $1",
[id]
);
if (userResult.rows.length === 0) {
return res.status(404).json({ error: "Benutzer nicht gefunden" });
}
// Verhindern, dass sich Admins selbst löschen
if (req.user.id == id) {
return res
.status(400)
.json({ error: "Ein Admin kann sich nicht selbst löschen" });
}
// Benutzer löschen
await pool.query("DELETE FROM users WHERE id = $1", [id]);
res.json({
message: "Benutzer erfolgreich gelöscht",
user: userResult.rows[0],
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
// User zu Admins machen
router.put("/admin/:id", requireAuth, requireAdmin, async (req, res) => {
const { id } = req.params;
const { temporary } = req.body; // erwartet { "temporary": true } oder { "temporary": false }
if (temporary === undefined) {
return res.status(400).json({
error: "Es muss angegeben werden, ob der Admin-Zugang temporär sein soll",
});
}
try {
let query, values;
if (temporary) {
query = `
UPDATE users
SET role = 'admin', admin_temp = NOW() + INTERVAL '48 hours'
WHERE id = $1 RETURNING id, username, role, admin_temp
`;
values = [id];
} else {
query = `
UPDATE users
SET role = 'admin', admin_temp = NULL
WHERE id = $1 RETURNING id, username, role, admin_temp
`;
values = [id];
}
const result = await pool.query(query, values);
if (result.rows.length === 0) {
return res.status(404).json({ error: "Benutzer nicht gefunden" });
}
res.json({
message: "Benutzer wurde zum Admin gemacht",
user: result.rows[0],
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "Interner Serverfehler" });
}
});
module.exports = router;