created basic structure

This commit is contained in:
Christian Rute 2024-10-29 21:09:13 +01:00
commit 79e6cf0502
18 changed files with 664 additions and 0 deletions

2
.env.server.example Normal file
View File

@ -0,0 +1,2 @@
MONGODB_URI=mongodb://172.18.0.3:27017/userdata
JWT_SECRET=???

17
client/admin.html Normal file
View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin-Bereich</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Admin-Bereich</h1>
<button id="loadUserListBtn">Benutzerliste laden</button>
<div id="user-list"></div>
<button id="logoutBtn">Abmelden</button>
<button onclick="toWelcome()">Zur Willkommensseite</button>
<script src="js/main.js"></script>
</body>
</html>

35
client/css/styles.css Normal file
View File

@ -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;
}

28
client/index.html Normal file
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login & Registrierung</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Willkommen! Bitte melden Sie sich an oder registrieren Sie sich.</h1>
<form id="registerForm">
<h2>Registrierung</h2>
<input type="text" id="regUsername" placeholder="Benutzername" required>
<input type="email" id="regEmail" placeholder="E-Mail" required>
<input type="password" id="regPassword" placeholder="Passwort" required>
<button type="submit">Registrieren</button>
</form>
<form id="loginForm">
<h2>Login</h2>
<input type="email" id="loginEmail" placeholder="E-Mail" required>
<input type="password" id="loginPassword" placeholder="Passwort" required>
<button type="submit">Anmelden</button>
</form>
<script src="js/auth.js"></script>
</body>
</html>

49
client/js/auth.js Normal file
View File

@ -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);
}
});

147
client/js/main.js Normal file
View File

@ -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 = `
<p>Benutzername: ${user.username} - Email: ${user.email}</p>
<button onclick="deleteUser('${user._id}')">Benutzer löschen</button>
`;
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();
}

20
client/welcome.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Willkommen</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<h1>Willkommen auf Ihrer persönlichen Seite!</h1>
<div id="user-info">
<p><strong>Benutzername:</strong> <span id="username"></span></p>
<p><strong>Admin:</strong> <span id="isAdmin"></span></p>
</div>
<button id="adminPermissionsBtn" style="display: none;">Admin-Einstellungen</button>
<button id="logoutBtn">Abmelden</button>
<script src="js/main.js"></script>
</body>
</html>

44
docker-compose.yml Normal file
View File

@ -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

21
nginx.conf Normal file
View File

@ -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
}
}

20
server/Dockerfile Normal file
View File

@ -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"]

32
server/app.js Normal file
View File

@ -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}`);
});

17
server/config/db.js Normal file
View File

@ -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;

View File

@ -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 };

View File

@ -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;

View File

@ -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;

30
server/models/User.js Normal file
View File

@ -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);

22
server/package.json Normal file
View File

@ -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": {}
}

View File

@ -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;