/***************************************************************************** * Import package * *****************************************************************************/ //Importieren der Dattenbanken import express, {Express, NextFunction, Request, Response} from 'express'; //Web-Framework import {Connection, createConnection, ResultSetHeader, RowDataPacket} from "mysql2/promise"; // Datenbankinteraktion import session from "express-session"; //Sitzungsverwaltung import crypto from "crypto"; //Passwort-Hashing-Funktion import * as path from "node:path"; //Arbeiten mit Dateipfaden //Datenbankverbindung /***************************************************************************** * Database Connection * *****************************************************************************/ let database: Connection; // Funktion wird definiert, um die Verbindung zu einer Datenbank asynchron herzustellen async function connectDatabase() { //Versuch, die Verbindung herzustellen try { //Verbindung erstellen database = await createConnection({ host: "localhost", user: "root", password: "toortoor", database: "horizon_changers" }); //Verbindung herstellen await database.connect(); console.log("Database is connected"); //Erfolg } catch (error) { console.log(`Database connection failed: ${error}`);//Fehlerbehandlung } } connectDatabase(); /***************************************************************************** * Define and start web-app server, define json-Parser * *****************************************************************************/ //Start des Servers const app: Express = express(); app.listen(8080, () => { console.log('Server started: http://localhost:8080'); }); app.use(express.json()); /***************************************************************************** * session management configuration * *****************************************************************************/ //Sitzungsmanagement app.use(session({ // Sitzungen werden gespeichert, auch wenn sie nicht verändert wurden. resave: true, //Sitzungen werden gespeichert, auch wenn sie nicht genutzt werden. saveUninitialized: true, // Die Gültigkeit des Cookies wird bei jeder Anfrage erneuert. rolling: true, // Ein geheimer Schlüssel zur Verschlüsselung der Sitzungsdaten. secret: "f47ac10b-58cc-4372-a567-0e02b2c3d479", //Ablaufzeit des Cookies (1 Stunde). cookie: { maxAge: 1000 * 60 * 60 } // 1h })); declare module 'express-session' { interface SessionData { user?: User } } /***************************************************************************** * Datastructure * *****************************************************************************/ //Datenstruktur User: Interface zur Definition der Benutzerstruktur. export interface User { user_id: number; firstName: string; lastName: string; adress: string; role: string; eMail: string; } //Middleware: Login-Status prüfen --Prüft, ob ein Benutzer eingeloggt ist. Falls nicht, wird ein HTTP-Status 401 (Unauthorized) zurückgegeben. function isLoggedIn(req: Request, res: Response, next: NextFunction) { // Abstract middleware route for checking login state of the user if (req.session.user != null) { // User has an active session and is logged in, continue with route next(); } else { // User is not logged in res.status(401).send({ message: 'Session expired, please log in again', }); } } ////HTTP-Routen /***************************************************************************** * HTTP ROUTES: LOGIN * *****************************************************************************/ /** * @api {get} /login Login-Status überprüfen * @apiName LoginStatus * @apiGroup Login * @apiDescription Überprüft, ob ein Benutzer noch eingeloggt ist. Gibt bei Erfolg die Benutzerdaten zurück. * * @apiSuccess {User} user The user object * @apiSuccess {string} message Message stating that the user is still logged in * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "user":{ * "user_id":42, * "firstName":"Peter", * "lastName":"Kneisel", * "eMail":"admin", * }, * "message":"User still logged in" * } * * @apiError (Client Error) {401} SessionNotFound The session of the user is expired or was not set * * @apiErrorExample SessionNotFound: * HTTP/1.1 401 Unauthorized * { * "message":"Session expired, please log in again." * } */ // Login-Status prüfen (GET /login) app.get('/login', isLoggedIn, (req: Request, res: Response): void => { res.status(200).send({ message: 'User still logged in', user: req.session.user, // Send user object to client for greeting message }); }); /** * @api {post} /login Login * @apiName Login * @apiGroup Login * @apiDescription Authentifiziert einen Benutzer/ Admin anhand von E-Mail und Passwort. Speichert den Benutzer/ Admin bei erfolgreichem Login in der Session. * * @apiBody {String} eMail E-Mail-Adresse des Benutzers/ Admins. Muss angegeben werden. * @apiBody {String} password Passwort des Benutzers/ Admins. Muss angegeben werden. * * @apiSuccessExample Success-Response: * HTTP/1.1 201 OK * { * "message":"'Erfolgreich eingeloggt :)!'" * } * @apiSuccess {String} message Erfolgsnachricht. * @apiSuccess {Object} user Details des authentifizierten Benutzers. * @apiSuccess {Obejct} admin Details des authentifizierten Admins. * @apiSuccess {Number} user.user_id ID des Benutzers. * @apiSuccess {Number} user.admin_id ID des Admins. * @apiSuccess {String} user.firstName Vorname des Benutzers/ Admins. * @apiSuccess {String} user.lastName Nachname des Benutzers/ Admins. * @apiSuccess {String} user.eMail E-Mail-Adresse des Benutzers/ Admins. * @apiSuccess {String} user.adress Adresse des Benutzers/ Admins (bei Login nicht verpflichtete Angabe). * @apiSuccess {String} user.role Rolle des Benutzers/ Admins (Standardeinstellung user). * * @apiError {String} message Fehlermeldung. * * @apiErrorExample {json} 401 Fehler (Ungültige Login-Daten): * HTTP/1.1 401 Unauthorized * { * "message": "Passwort und/oder Email stimmt/stimmen nicht." * } * * @apiErrorExample {json} 500 Fehler (Datenbankproblem): * HTTP/1.1 500 Internal Server Error * { * "message": "Database request failed: [Fehlerdetails]" * } */ //Benutzer-Login (POST /login) app.post('/login', async (req: Request, res: Response): Promise<void> => { // Read data from request const eMail: string = req.body.eMail; const password: string = req.body.password; // Create database query and data const data: [string, string] = [ eMail, crypto.createHash("sha512").update(password).digest('hex'), ]; console.log(data) const query: string = 'SELECT * FROM user WHERE eMail = ? AND password = ?;'; try { const [rows] = await database.query<RowDataPacket[]>(query, data); // Check if database response contains exactly one entry if (rows.length === 1) { // Login data is correct, user is logged in const user: User = { user_id: rows[0].user_id, firstName: rows[0].firstName, lastName: rows[0].lastName, eMail: rows[0].eMail, adress: rows[0].adress, role: rows[0].role || 'user', }; req.session.user = user; // Store user object in session for authentication res.status(200).send({ message: 'Erfolgreich eingeloggt :)!', user: user, // Send user object to client for greeting message }); } else { // Login data is incorrect res.status(401).send({ message: 'Passwort und/oder Email stimmt/stimmen nicht.', }); } } catch (error: unknown) { // Unknown error res.status(500).send({ message: 'Database request failed: ' + error, }); } }); /** * @api {post} /logout Logout user * @apiName PostLogout * @apiGroup Logout * * @apiSuccess {string} message Message stating that the user is logged out * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * message: "Erfolgreich ausgeloggt! Bis zum nächsten mal :)" * } */ //Logout app.post('/logout', isLoggedIn, (req: Request, res: Response): void => { // Log out user req.session.user = undefined; // Delete user from session res.status(200).send({ message: 'Erfolgreich ausgeloggt! Bis zum nächsten mal :)', }); }); /***************************************************************************** * HTTP ROUTES: USER, USERS * *****************************************************************************/ /** * * @api {post} /user Benutzerregistrierung * @apiName BenutzerRegistrierung * @apiGroup User * * @apiDescription Registriert einen neuen Benutzer und loggt ihn anschließend automatisch ein. * * @apiBody {String} firstName Vorname des Benutzers. Muss angegeben werden. * @apiBody {String} lastName Nachname des Benutzers. Muss angegeben werden. * @apiBody {String} eMail E-Mail-Adresse des Benutzers. Muss angegeben werden. * @apiBody {String} password Passwort des Benutzers. Muss angegeben werden. * @apiBody {String} repeatPassword Wiederholung des Passworts. Muss mit `password` übereinstimmen. * * @apiSuccess {string} message Bestätigungsnachricht. * @apiSuccess {Object} user Details des neu erstellten Benutzers. * @apiSuccess {Number} user.user_id ID des Benutzers. * @apiSuccess {String} user.firstName Vorname des Benutzers. * @apiSuccess {String} user.lastName Nachname des Benutzers. * @apiSuccess {String} user.eMail E-Mail-Adresse des Benutzers. * @apiSuccess {String} user.adress Adresse des Benutzers (falls vorhanden, optional). * @apiSuccess {String} user.role Rolle des Benutzers (Standardwert: "user"). * * @apiSuccessExample Success-Response: * HTTP/1.1 201 OK * { * "message":"'Erfolgreich registriert und eingeloggt!'" * } * * @apiError {String} message Fehlermeldung. * * @apiErrorExample {json} 400 Fehler (Felder fehlen): * HTTP/1.1 400 Bad Request * { * "message": "Bitte alle Felder ausfüllen!" * } * * @apiErrorExample {json} 400 Fehler (Passwörter stimmen nicht überein): * HTTP/1.1 400 Bad Request * { * "message": "Passwörter stimmen nicht überein!" * } * * @apiErrorExample {json} 500 Fehler (Datenbankproblem): * HTTP/1.1 500 Internal Server Error * { * "message": "Datenbankanfrage fehlgeschlagen: [Fehlerdetails]" * } * * @apiErrorExample {json} 500 Fehler (Benutzer nicht abrufbar): * HTTP/1.1 500 Internal Server Error * { * "message": "Registrierung erfolgreich, aber der Benutzer konnte nicht abgerufen werden." * } */ //Benutzer registrieren (POST /user) app.post('/user', async (req: Request, res: Response): Promise<void> => { const firstName: string = req.body.firstName; //Variable firstName wird erstellt, mit req.body kann man auf Nutzdaten zugreifen, in dem Falle auf first name const lastName: string = req.body.lastName; const eMail: string = req.body.eMail; const password: string = req.body.password; const repeatPassword: string = req.body.repeatPassword; // Check, if any given value is empty if (!firstName || !lastName || !eMail || !password || !repeatPassword) { res.status(400).send({ message: "Bitte alle Felder ausfüllen!", }); return; } // Check if password and repeatPassword match if (password !== repeatPassword) { res.status(400).send({ message: 'Passwörter stimmen nicht überein!', }); return; } //aufrufen der namen, email, passwort const data: [string, string, string, string] = [ firstName, lastName, eMail, crypto.createHash("sha512").update(password).digest('hex') ]; //soll in user Tabelle eingepflegt werden const query: string = 'INSERT INTO user (firstName, lastName, eMail, password) VALUES (?, ?, ?, ?);'; try { const [result] = await database.query<ResultSetHeader>(query, data); //daten werden von dem neu registrierten Nutzer gespeichert // Hol den neu erstellten Benutzer aus der Datenbank const [userRows] = await database.query<RowDataPacket[]>( 'SELECT * FROM user WHERE user_id = ?;', [result.insertId] //=vom registrierten User id nehmen ); if (userRows.length === 1) { const user: User = { user_id: userRows[0].user_id, firstName: userRows[0].firstName, lastName: userRows[0].lastName, eMail: userRows[0].eMail, adress: userRows[0].adress || '', role: userRows[0].role || 'user', }; // Speichere den Benutzer in der Session req.session.user = user; res.status(201).send({ message: 'Erfolgreich registriert und eingeloggt!', user, }); } else { res.status(500).send({ message: 'Registrierung erfolgreich, aber der Benutzer konnte nicht abgerufen werden.', }); } } catch (error) { res.status(500).send({ message: 'Datenbankanfrage fehlgeschlagen: ' + error, }); } }); //Benutzer abrufen, aktualisieren, löschen /***************************************************************************** * HTTP ROUTES: USER, USERS * *****************************************************************************/ /** * * @api {get} /user/:user_id Benutzerinformationen abrufen * @apiName BenutzerInformationenAbrufen * @apiGroup User * * @apiDescription Ruft die Informationen eines Benutzers anhand der Benutzer-ID ab. * Die Benutzerdaten werden in der aktuellen Session gespeichert, wenn der Benutzer gefunden wird. * * @apiSuccess {String} message Bestätigungsnachricht. * @apiSuccess {Object} user Details des gefundenen Benutzers. * @apiSuccess {Number} user.user_id ID des Benutzers. * @apiSuccess {String} user.firstName Vorname des Benutzers. * @apiSuccess {String} user.lastName Nachname des Benutzers. * @apiSuccess {String} user.eMail E-Mail-Adresse des Benutzers. * @apiSuccess {String} user.adress Adresse des Benutzers (falls vorhanden, optional). * @apiSuccess {String} user.role Rolle des Benutzers. * * @apiSuccessExample Erfolgreiche Antwort: * HTTP/1.1 200 OK * { * "user": { * "user_id": 2, * "firstName": "Bob", * "lastName": "Hase", * "eMail": "bob.hase@outlook.com", * "adress": "Musterstraße 9, 12345 Musterstadt", * "role": "user" * } * } * * @apiError {String} message Fehlermeldung. * * @apiErrorExample {json} 401 Fehler (Benutzer nicht gefunden): * HTTP/1.1 401 Unauthorized * { * "message": "Benutzer nicht gefunden" * } * * @apiErrorExample {json} 500 Fehler (Datenbankproblem): * HTTP/1.1 500 Internal Server Error * { * "message": "Database request failed: [Fehlerdetails]" * } * * @apiPermission Authentifiziert */ //Gibt Benutzerinformationen zurück. app.get('/user/:user_id', isLoggedIn, async (req: Request, res: Response): Promise<void> => { // Read data from request parameters const data: [number] = [ parseInt(req.params.user_id) ]; // Search user in database const query: string = 'SELECT * FROM user WHERE user_id = ?;'; try { const [rows] = await database.query<RowDataPacket[]>(query, data); if (rows.length === 1) { const user: User = { user_id: rows[0].user_id, firstName: rows[0].firstName, lastName: rows[0].lastName, eMail: rows[0].eMail, adress: rows[0].adress, role: rows[0].role }; req.session.user = user; // Store user object in session for authentication res.status(200).send({ user: user, // Send user object to client for greeting message }); } else { // Login data is incorrect res.status(401).send({ message: 'Passwort ist falsch', }); } } catch (error: unknown) { // Unknown error res.status(500).send({ message: 'Database request failed: ' + error, }); } }); /** * @api {put} /user Aktualisiere Benutzerdaten * @apiName UpdateUser * @apiGroup User * @apiPermission loggedIn * * @apiDescription Diese Route ermöglicht es eingeloggten Benutzern, ihre persönlichen Daten wie Vorname, Nachname, E-Mail-Adresse, Passwort und Adresse zu aktualisieren. * Der Benutzer muss eingeloggt sein, um diese Route nutzen zu können. * * * @apiBody {String} firstName Vorname des Benutzers. (Pflichtfeld) * @apiBody {String} lastName Nachname des Benutzers. (Pflichtfeld) * @apiBody {String} eMail E-Mail-Adresse des Benutzers. (Pflichtfeld) * @apiBody {String} password Passwort des Benutzers. Wird als SHA-512-Hash gespeichert. (Pflichtfeld) * @apiBody {String} adress Adresse des Benutzers. (Optional) * * @apiSuccess {String} message Erfolgsmeldung, dass die Daten erfolgreich aktualisiert wurden. * * @apiError {Number} 400 Fehlende Pflichtfelder: Es wurden nicht alle erforderlichen Felder ausgefüllt. * @apiError {Number} 404 Benutzer nicht gefunden: Der Benutzer konnte in der Datenbank nicht gefunden werden. * @apiError {Number} 500 Datenbankfehler: Es gab ein Problem mit der Datenbankanfrage. * * @apiSuccessExample {json} Erfolgreiche Antwort: * HTTP/1.1 200 OK * { * "message": "Deine Daten wurden erfolgreich abgeändert" * } * * @apiErrorExample {json} Fehlende Pflichtfelder: * HTTP/1.1 400 Bad Request * { * "message": "Not all mandatory fields are filled in" * } * * @apiErrorExample {json} Benutzer nicht gefunden: * HTTP/1.1 404 Not Found * { * "message": "The user to update could not be found" * } * * @apiErrorExample {json} Datenbankfehler: * HTTP/1.1 500 Internal Server Error * { * "message": "Database request failed: [Fehlermeldung]" * } */ //Aktualisiert den Benutzer app.put('/user', isLoggedIn, async (req: Request, res: Response): Promise<void> => { // Read data from request console.log(req.body); const user_id: number = Number(req.session.user?.user_id); //user_id wird zu Number gemacht weil könnte "theoretisch" undefined sein,. aber eig wegen isloggedin nicht const firstName: string = req.body.firstName; const lastName: string = req.body.lastName; const eMail: string = req.body.eMail; const password: string = req.body.password; const adress: string = req.body.adress; // Create database query and data const data: [string, string] = [ eMail, crypto.createHash("sha512").update(password).digest('hex'), ]; console.log("login " + data) const query: string = 'SELECT * FROM user WHERE eMail = ? AND password = ?;'; try { const [result] = await database.query<RowDataPacket[]>(query, data); if (result.length != 1) { res.status(404).send({ }); } } catch (error) { res.status(500).send({ message: 'Database request failed: ' + error }); } // Check that all arguments are given if (firstName && lastName && eMail && password) { // Create database query and data const data: [string, string, string, string, string, number] = [ firstName, lastName, eMail, crypto.createHash("sha512").update(password).digest('hex'), adress, user_id ]; const query: string = 'UPDATE user SET firstName = ?, lastName = ?, eMail = ?, password = ?, adress = ? WHERE user_id = ?;'; console.log(query, data) // Execute database query try { const [result] = await database.query<ResultSetHeader>(query, data); console.log(result) if (result.affectedRows != 1) { res.status(404).send({ message: 'The user to update could not be found', }); } else { res.status(200).send({ message: `Deine Daten wurden erfolgreich abgeändert`, }); } } catch (error) { res.status(500).send({ message: 'Database request failed: ' + error }); } } else { res.status(400).send({ message: 'Not all mandatory fields are filled in', }); } }); // update route admin app.put('/user/:userId', isLoggedIn, async (req: Request, res: Response): Promise<void> => { // Read data from request const userId: number = parseInt(req.params.userId); const firstName: string = req.body.firstName; const lastName: string = req.body.lastName; const eMail: string = req.body.eMail; const adress: string = req.body.adress; const role: string = req.body.role; // Check that all arguments are given if (firstName && lastName && eMail && role) { // Create database query and data const data: [string, string, string, string, string, number] = [ firstName, lastName, eMail, adress, role, userId ]; const query: string = 'UPDATE userlist SET firstName = ?, lastName = ?, eMail = ?, adress = ?, role = ?, WHERE user_id = ?;'; // Execute database query try { const [result] = await database.query<ResultSetHeader>(query, data); if (result.affectedRows != 1) { res.status(404).send({ message: 'The user to update could not be found', }); } else { res.status(200).send({ message: `Successfully updated user ${firstName} ${lastName}`, }); } } catch (error) { res.status(500).send({ message: 'Database request failed: ' + error }); } } else { res.status(400).send({ message: 'Not all mandatory fields are filled in', }); } }); /** * @api {delete} /user Benutzerprofil löschen * @apiName BenutzerprofilLoeschen * @apiGroup User * @apiDescription Löscht das Benutzerprofil des aktuell eingeloggten Benutzers aus der Datenbank und beendet die Session. * * @apiSuccess {String} message Erfolgsnachricht * * @apiSuccessExample Sucsess-Response: * HTTP/1.1 200 OK * { * "message": "Profil erfolgreich gelöscht." * } * * @apiError {String} message Fehlermeldung. * * @apiErrorExample 404 Fehler (Benutzer nicht gefunden): * HTTP/1.1 404 Not Found * { * "message": "Profil konnte nicht gelöscht werden. Versuche es später erneut." * } * * @apiErrorExample 500 Fehler (Datenbankproblem): * HTTP/1.1 500 Internal Server Error * { * "message": "Datenbankanfrage fehlgeschlagen: [Fehlerdetails]" * } */ // Route zum Löschen des eigenen Benutzerprofils app.delete('/user', isLoggedIn, async (req: Request, res: Response): Promise<void> => { console.log("delete user called") // Read data from request const user_id: number = Number(req.session.user?.user_id); // Delete user const query: string = 'DELETE FROM user WHERE user_id = ?;'; try { // Führt die Löschoperation in der Datenbank aus const [result] = await database.query<ResultSetHeader>(query, [user_id]); if (result.affectedRows === 1) { // Löscht die Sitzung und sendet Erfolgsmeldung req.session.destroy((err) => { if (err) { console.error('Session destruction error:', err); } res.status(200).send({ message: 'Profil erfolgreich gelöscht.' }); }); } else { // Sendet Fehlermeldung, wenn kein Benutzer gefunden wurde res.status(404).send({ message: 'Profil konnte nicht gelöscht werden. Versuche es später erneut.', }); } } catch (error: unknown) { // Sendet Fehlermeldung bei Datenbankfehler res.status(500).send({ message: 'Datenbankanfrage fehlgeschlagen: ' + error, }); } }); //stellt sicher, dass nur eingeloggte Benutzer Zugriff auf die Benutzerliste haben. // Die Route ruft alle Benutzer aus der Tabelle user in der Datenbank ab. // Sie wandelt die Ergebnisse der Datenbank in ein standardisiertes Format (User-Objekte) um. app.get('/users', isLoggedIn, async (req: Request, res: Response): Promise<void> => { // Send user list to client const query: string = 'SELECT * FROM user;'; try { const [rows] = await database.query<RowDataPacket[]>(query); // Create local user list to parse users from database const userList: User[] = []; // Parse every entry for (const row of rows) { const user: User = { user_id: row.user_id, firstName: row.firstName, lastName: row.lastName, eMail: row.eMail, adress: row.adress, role: row.role }; userList.push(user); } // Send user list to client res.status(200).send({ userList: userList, message: 'hier ist die Nutzerliste' }); } catch (error) { // Database operation has failed res.status(500).send({ message: 'Database request failed: ' + error }); } }); /***************************************************************************** * STATIC ROUTES * *****************************************************************************/ app.use(express.static(path.join(__dirname, "..", "..", "client")));