From 79e6cf0502bdae08eb0949c864f9ea98d9bc3d33 Mon Sep 17 00:00:00 2001 From: Christian Rute Date: Tue, 29 Oct 2024 21:09:13 +0100 Subject: [PATCH] created basic structure --- .env.server.example | 2 + client/admin.html | 17 ++++ client/css/styles.css | 35 +++++++ client/index.html | 28 +++++ client/js/auth.js | 49 +++++++++ client/js/main.js | 147 +++++++++++++++++++++++++++ client/welcome.html | 20 ++++ docker-compose.yml | 44 ++++++++ nginx.conf | 21 ++++ server/Dockerfile | 20 ++++ server/app.js | 32 ++++++ server/config/db.js | 17 ++++ server/controllers/authController.js | 66 ++++++++++++ server/middleware/adminMiddleware.js | 12 +++ server/middleware/authMiddleware.js | 22 ++++ server/models/User.js | 30 ++++++ server/package.json | 22 ++++ server/routes/authRoutes.js | 80 +++++++++++++++ 18 files changed, 664 insertions(+) create mode 100644 .env.server.example create mode 100644 client/admin.html create mode 100644 client/css/styles.css create mode 100644 client/index.html create mode 100644 client/js/auth.js create mode 100644 client/js/main.js create mode 100644 client/welcome.html create mode 100644 docker-compose.yml create mode 100644 nginx.conf create mode 100644 server/Dockerfile create mode 100644 server/app.js create mode 100644 server/config/db.js create mode 100644 server/controllers/authController.js create mode 100644 server/middleware/adminMiddleware.js create mode 100644 server/middleware/authMiddleware.js create mode 100644 server/models/User.js create mode 100644 server/package.json create mode 100644 server/routes/authRoutes.js diff --git a/.env.server.example b/.env.server.example new file mode 100644 index 0000000..5965383 --- /dev/null +++ b/.env.server.example @@ -0,0 +1,2 @@ +MONGODB_URI=mongodb://172.18.0.3:27017/userdata +JWT_SECRET=??? \ No newline at end of file diff --git a/client/admin.html b/client/admin.html new file mode 100644 index 0000000..4ea4330 --- /dev/null +++ b/client/admin.html @@ -0,0 +1,17 @@ + + + + + + Admin-Bereich + + + +

Admin-Bereich

+ +
+ + + + + diff --git a/client/css/styles.css b/client/css/styles.css new file mode 100644 index 0000000..3cb820b --- /dev/null +++ b/client/css/styles.css @@ -0,0 +1,35 @@ +body { + font-family: Arial, sans-serif; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + margin: 0; + background-color: #f4f4f9; +} + +form { + border: 1px solid #ddd; + padding: 20px; + margin: 20px; + background: white; + width: 300px; + text-align: center; +} + +h1, h2 { + color: #333; +} + +input, button { + width: 100%; + padding: 10px; + margin: 10px 0; + border: 1px solid #ddd; +} + +button { + background-color: #007bff; + color: white; + cursor: pointer; +} diff --git a/client/index.html b/client/index.html new file mode 100644 index 0000000..97b880d --- /dev/null +++ b/client/index.html @@ -0,0 +1,28 @@ + + + + + + Login & Registrierung + + + +

Willkommen! Bitte melden Sie sich an oder registrieren Sie sich.

+
+

Registrierung

+ + + + +
+ +
+

Login

+ + + +
+ + + + diff --git a/client/js/auth.js b/client/js/auth.js new file mode 100644 index 0000000..d249032 --- /dev/null +++ b/client/js/auth.js @@ -0,0 +1,49 @@ +// Registrierungs-Event +document.getElementById('registerForm').addEventListener('submit', async (event) => { + event.preventDefault(); + const username = document.getElementById('regUsername').value; + const email = document.getElementById('regEmail').value; + const password = document.getElementById('regPassword').value; + + try { + const response = await fetch('http://localhost:8015/api/auth/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, email, password }) + }); + + const data = await response.json(); + if (response.ok) { + alert('Registrierung erfolgreich! Bitte loggen Sie sich ein.'); + } else { + alert(data.message || 'Registrierung fehlgeschlagen'); + } + } catch (error) { + console.error('Fehler bei der Registrierung:', error); + } +}); + +// Login-Event +document.getElementById('loginForm').addEventListener('submit', async (event) => { + event.preventDefault(); + const email = document.getElementById('loginEmail').value; + const password = document.getElementById('loginPassword').value; + + try { + const response = await fetch('http://localhost:8015/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }); + + const data = await response.json(); + if (response.ok) { + localStorage.setItem('token', data.token); // Token speichern + window.location.href = 'welcome.html'; // Weiterleitung zur Willkommensseite + } else { + alert(data.message || 'Login fehlgeschlagen'); + } + } catch (error) { + console.error('Fehler beim Login:', error); + } +}); diff --git a/client/js/main.js b/client/js/main.js new file mode 100644 index 0000000..5aaa32e --- /dev/null +++ b/client/js/main.js @@ -0,0 +1,147 @@ +// IP-Adresse +const baseUrl = 'http://localhost:8015'; + +// Funktion zum Laden der Benutzerdaten für die normale Webseite +async function loadUserData() { + const token = localStorage.getItem('token'); + if (!token) { + alert('Nicht autorisiert. Bitte einloggen.'); + window.location.href = 'index.html'; + return; + } + + try { + const response = await fetch(`${baseUrl}/api/auth/me`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + throw new Error('Fehler beim Abrufen der Benutzerdaten'); + } + + const data = await response.json(); + document.getElementById('username').textContent = data.user; + document.getElementById('isAdmin').textContent = data.isAdmin ? 'Ja' : 'Nein'; + + // Überprüfen, ob der Benutzer Admin ist + if (data.isAdmin) { + const adminBtn = document.getElementById('adminPermissionsBtn'); + adminBtn.style.display = 'inline-block'; // Button anzeigen + adminBtn.addEventListener('click', () => { + window.location.href = 'admin.html'; // Bei Klick weiterleiten + }); + } + + } catch (error) { + console.error(error); + alert('Sitzung abgelaufen. Bitte erneut einloggen.'); + localStorage.removeItem('token'); + window.location.href = 'index.html'; + } +} + +// Funktion zum Abmelden +document.getElementById('logoutBtn').addEventListener('click', () => { + localStorage.removeItem('token'); + window.location.href = 'index.html'; +}); + +// Funktion um auf die Willkommensseite zurückzukommen +function toWelcome() { + location.href = 'welcome.html'; +} + +async function loadAdminData() { + const token = localStorage.getItem('token'); + if (!token) { + alert('Nicht autorisiert. Bitte einloggen.'); + window.location.href = 'index.html'; + return; + } + + try { + const response = await fetch(`${baseUrl}/api/auth/admin`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); + + if (!response.ok) { + alert('Zugriff verweigert. Sie sind kein Admin.'); + window.location.href = 'welcome.html'; + return; + } + + const data = await response.json(); + console.log(data.message); // "Willkommen, Admin!" + } catch (error) { + console.error(error); + alert('Sitzung abgelaufen. Bitte erneut einloggen.'); + localStorage.removeItem('token'); + window.location.href = 'index.html'; + } +} + +async function loadUserList() { + const token = localStorage.getItem('token'); + const response = await fetch(`${baseUrl}/api/auth/users`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const users = await response.json(); + const userList = document.getElementById('user-list'); + userList.innerHTML = ''; // Liste leeren + + users.forEach(user => { + const userDiv = document.createElement('div'); + userDiv.innerHTML = ` +

Benutzername: ${user.username} - Email: ${user.email}

+ + `; + userList.appendChild(userDiv); + }); + } else { + alert('Fehler beim Laden der Benutzerliste'); + } +} + +// Überprüfen, ob wir uns auf der Admin-Seite befinden, und dann den Event-Listener hinzufügen +const loadUserListBtn = document.getElementById('loadUserListBtn'); +if (loadUserListBtn) { + loadUserListBtn.addEventListener('click', loadUserList); // Benutzerliste laden, wenn der Button geklickt wird +} + +// Funktion zum Löschen eines Benutzers +async function deleteUser(userId) { + const token = localStorage.getItem('token'); + try { + const response = await fetch(`${baseUrl}/api/auth/user/${userId}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + loadUserList(); // Liste aktualisieren + } else { + alert('Fehler beim Löschen des Benutzers'); + } + } catch (error) { + console.error('Fehler beim Löschen des Benutzers:', error); + } +} + +// Abhängig von der Seite entweder Admin- oder Benutzerdaten laden +if (window.location.pathname.includes('admin.html')) { + loadAdminData(); + loadUserList(); +} else if (window.location.pathname.includes('welcome.html')) { + loadUserData(); +} + diff --git a/client/welcome.html b/client/welcome.html new file mode 100644 index 0000000..aca775b --- /dev/null +++ b/client/welcome.html @@ -0,0 +1,20 @@ + + + + + + Willkommen + + + +

Willkommen auf Ihrer persönlichen Seite!

+
+

Benutzername:

+

Admin:

+
+ + + + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c3ba1d9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +services: + app: + container_name: logic + build: ./server + env_file: + - ./.env.server + depends_on: + - mongo + networks: + internal_network: + ipv4_address: 172.18.0.2 + + mongo: + image: mongo:5 + container_name: database + volumes: + - mongo-data:/data/db + ports: + - "27017:27017" + networks: + internal_network: + ipv4_address: 172.18.0.3 + + client: + image: nginx:alpine + container_name: webserver + volumes: + - ./client:/usr/share/nginx/html + - ./nginx.conf:/etc/nginx/conf.d/default.conf + ports: + - "8015:80" + networks: + internal_network: + ipv4_address: 172.18.0.4 + +volumes: + mongo-data: + +networks: + internal_network: + driver: bridge + ipam: + config: + - subnet: 172.18.0.0/16 diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..a4ceee9 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,21 @@ +server { + listen 80; + + # Root-Verzeichnis für statische Dateien + root /usr/share/nginx/html; + index index.html; + + # Alle Anfragen an den Node.js-Container weiterleiten + location /api/ { + proxy_pass http://app:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Anfragen für andere statische Dateien (HTML, CSS, JS, Bilder) im Root-Verzeichnis suchen + location / { + try_files $uri $uri/ /index.html; # Bei nicht gefundenen Dateien auf index.html zurückgreifen + } +} diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000..c59c9af --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,20 @@ +# Node.js Image +FROM node:latest + +# Arbeitsverzeichnis erstellen und setzen +WORKDIR /app + +# Kopiere package.json und package-lock.json +COPY package*.json ./ + +# Installiere Abhängigkeiten +RUN npm install + +# Kopiere den restlichen Server-Code +COPY . . + +# Exponiere den Port des Servers +EXPOSE 5000 + +# Startbefehl +CMD ["npm", "start"] diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..010d9dd --- /dev/null +++ b/server/app.js @@ -0,0 +1,32 @@ +require('dotenv').config(); // Muss ganz oben stehen +const mongoose = require('mongoose'); + +const express = require('express'); +const cors = require('cors'); +const dotenv = require('dotenv'); +const connectDB = require('./config/db'); +const authRoutes = require('./routes/authRoutes'); + +dotenv.config(); +// Verbindung zur Datenbank aufbauen +connectDB(); + +const app = express(); + +// Middleware +app.use(cors()); +app.use(express.json()); + +// Authentifizierungsrouten +app.use('/api/auth', authRoutes); + +// Beispiel-Route +app.get('/', (req, res) => { + res.send('API läuft'); +}); + +// Server starten +const PORT = process.env.PORT || 5000; +app.listen(PORT, () => { + console.log(`Server läuft auf Port ${PORT}`); +}); diff --git a/server/config/db.js b/server/config/db.js new file mode 100644 index 0000000..f18d08a --- /dev/null +++ b/server/config/db.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); +const dotenv = require('dotenv'); + +dotenv.config(); + +// Datenbankverbindung definieren +const connectDB = async () => { + try { + await mongoose.connect(process.env.MONGODB_URI); + console.log('MongoDB connected'); + } catch (error) { + console.error('MongoDB connection error:', error.message); + process.exit(1); + } +}; + +module.exports = connectDB; diff --git a/server/controllers/authController.js b/server/controllers/authController.js new file mode 100644 index 0000000..02f7937 --- /dev/null +++ b/server/controllers/authController.js @@ -0,0 +1,66 @@ +const User = require('../models/User'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); + +// Registrierungs-Controller +const register = async (req, res) => { + const { username, email, password } = req.body; + + try { + // Überprüfen, ob Benutzername oder E-Mail bereits existieren + let user = await User.findOne({ $or: [{ username }, { email }] }); + if (user) { + return res.status(400).json({ message: 'Benutzername oder E-Mail bereits vergeben' }); + } + + // Überprüfen, ob es sich um den ersten Benutzer handelt + const userCount = await User.countDocuments(); + const isAdmin = userCount === 0; // Erster Benutzer wird Admin + + // Passwort hashen + const hashedPassword = await bcrypt.hash(password, 10); + + // Neuen Benutzer erstellen + user = new User({ + username, + email, + password: hashedPassword, + isAdmin, + }); + + await user.save(); + + res.status(201).json({ message: 'Registrierung erfolgreich' }); + } catch (error) { + res.status(500).json({ message: 'Serverfehler' }); + } +}; + +// Login-Controller +const login = async (req, res) => { + const { email, password } = req.body; + + try { + // Benutzer anhand der E-Mail suchen + const user = await User.findOne({ email }); + if (!user) { + return res.status(400).json({ message: 'Ungültige Anmeldedaten' }); + } + + // Passwort überprüfen + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + return res.status(400).json({ message: 'Ungültige Anmeldedaten' }); + } + + // JWT-Token erstellen mit benutzerdefinierten Daten + const token = jwt.sign({ id: user._id, username: user.username, docAccess: user.docAccess, isAdmin: user.isAdmin }, process.env.JWT_SECRET, { expiresIn: '1h' }); + + + res.status(200).json({ token, message: 'Login erfolgreich' }); + } catch (error) { + res.status(500).json({ message: 'Serverfehler' }); + } +}; + +module.exports = { register, login }; diff --git a/server/middleware/adminMiddleware.js b/server/middleware/adminMiddleware.js new file mode 100644 index 0000000..b0dff44 --- /dev/null +++ b/server/middleware/adminMiddleware.js @@ -0,0 +1,12 @@ +const User = require('../models/User'); + +const adminMiddleware = (req, res, next) => { + // Überprüfen, ob der Benutzer ein Administrator ist + if (req.user && req.user.isAdmin) { + return next(); // Weiter zu der nächsten Middleware oder Route + } + + return res.status(403).json({ message: 'Zugriff verweigert: Administratorrechte erforderlich' }); +}; + +module.exports = adminMiddleware; diff --git a/server/middleware/authMiddleware.js b/server/middleware/authMiddleware.js new file mode 100644 index 0000000..1f51f84 --- /dev/null +++ b/server/middleware/authMiddleware.js @@ -0,0 +1,22 @@ +const jwt = require('jsonwebtoken'); + +// Identifiziert einen Nutzer und überprüft das JWT Token + +const authMiddleware = (req, res, next) => { + const token = req.header('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ message: 'Kein Token, Zugriff verweigert' }); + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); // Token verifizieren + + req.user = decoded; // Benutzerinformationen im Request speichern + next(); + } catch (error) { + return res.status(401).json({ message: 'Ungültiger Token' }); + } +}; + +module.exports = authMiddleware; diff --git a/server/models/User.js b/server/models/User.js new file mode 100644 index 0000000..19b3e97 --- /dev/null +++ b/server/models/User.js @@ -0,0 +1,30 @@ +const mongoose = require('mongoose'); + +const userSchema = new mongoose.Schema({ + username: { + type: String, + required: true, + unique: true, + trim: true + }, + email: { + type: String, + required: true, + unique: true, + trim: true + }, + password: { + type: String, // Wird davor zu einem Hash umgewandelt + required: true + }, + createdAt: { + type: Date, + default: Date.now + }, + isAdmin: { + type: Boolean, + default: false // Standardwert ist false + } +}); + +module.exports = mongoose.model('User', userSchema); diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..ed7c82f --- /dev/null +++ b/server/package.json @@ -0,0 +1,22 @@ +{ + "name": "webinterface", + "version": "1.0.0", + "description": "Abhängigkeiten installieren:\r ```shell\r npm install express mongoose bcryptjs jsonwebtoken dotenv cors\r ```", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node app.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.21.1", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.7.3" + }, + "devDependencies": {} +} diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js new file mode 100644 index 0000000..e8dd282 --- /dev/null +++ b/server/routes/authRoutes.js @@ -0,0 +1,80 @@ +const express = require('express'); +const { register, login } = require('../controllers/authController'); +const authMiddleware = require('../middleware/authMiddleware'); +const adminMiddleware = require('../middleware/adminMiddleware'); +const User = require('../models/User'); + +const router = express.Router(); + +// Registrierungsroute +router.post('/register', register); + +// Loginroute +router.post('/login', login); + +// Geschützte Route, die die Benutzerinformationen zurückgibt +router.get('/me', authMiddleware, (req, res) => { + res.status(200).json({ + id: req.user.id, + user: req.user.username, + docAccess: req.user.docAccess, + isAdmin: req.user.isAdmin, + message: 'Benutzerinformationen abgerufen', + }); +}); + +// Eine geschützte Admin-Route +router.get('/admin', authMiddleware, adminMiddleware, (req, res) => { + res.status(200).json({ message: 'Willkommen, Admin!' }); +}); + +// Admin-Route, die eine Liste aller Benutzer zurückgibt +router.get('/users', authMiddleware, adminMiddleware, async (req, res) => { + try { + // Suche alle Benutzer und gib nur die Felder 'username', 'email' und 'docAccess' zurück + const users = await User.find({}, 'username email docAccess'); + res.status(200).json(users); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Abrufen der Benutzerdaten' }); + } +}); + +router.put('/user/:id/docAccess', authMiddleware, adminMiddleware, async (req, res) => { + const { id } = req.params; + const { docAccess } = req.body; + + // Sicherheitsvalidierung: docAccess-Wert muss zwischen 0 und 4 liegen + if (![0, 1, 2, 3, 4].includes(docAccess)) { + return res.status(400).json({ message: 'Ungültiger docAccess-Wert' }); + } + + try { + const user = await User.findByIdAndUpdate( + id, + { docAccess }, + { new: true, fields: 'username email docAccess' } // nur ausgewählte Felder zurückgeben + ); + if (!user) return res.status(404).json({ message: 'Benutzer nicht gefunden' }); + res.status(200).json(user); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Aktualisieren von docAccess' }); + } +}); + +router.delete('/user/:id', authMiddleware, adminMiddleware, async (req, res) => { + const { id } = req.params; + + try { + const user = await User.findByIdAndDelete(id); + if (!user) return res.status(404).json({ message: 'Benutzer nicht gefunden' }); + res.status(200).json({ message: 'Benutzer erfolgreich gelöscht' }); + } catch (error) { + console.error(error); + res.status(500).json({ message: 'Fehler beim Löschen des Benutzers' }); + } +}); + + +module.exports = router;