diff --git a/backend/src/server.ts b/backend/src/server.ts index 78f47ef..b0da954 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -2,17 +2,16 @@ const express = require('express'); const cors = require('cors'); const { v4: uuidv4 } = require('uuid'); -const bcrypt = require('bcryptjs'); const app = express(); const PORT = 3002; -// IN-MEMORY STORE für Mitarbeiter (mit gehashten Passwörtern) +// IN-MEMORY STORE für Mitarbeiter let employees = [ { id: '1', email: 'admin@schichtplan.de', - password: '$2a$10$8K1p/a0dRTlB0ZQ1.5Q.2e5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q', // admin123 + password: 'admin123', // Klartext für Test name: 'Admin User', role: 'admin', isActive: true, @@ -24,7 +23,7 @@ let employees = [ { id: '2', email: 'instandhalter@schichtplan.de', - password: '$2a$10$8K1p/a0dRTlB0ZQ1.5Q.2e5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q5Q', // instandhalter123 + password: 'instandhalter123', name: 'Max Instandhalter', role: 'instandhalter', isActive: true, @@ -32,6 +31,30 @@ let employees = [ department: 'Produktion', createdAt: new Date().toISOString(), lastLogin: new Date().toISOString() + }, + { + id: '3', + email: 'mitarbeiter1@schichtplan.de', + password: 'user123', + name: 'Anna Müller', + role: 'user', + isActive: true, + phone: '+49 123 456791', + department: 'Logistik', + createdAt: new Date().toISOString(), + lastLogin: new Date().toISOString() + }, + { + id: '4', + email: 'mitarbeiter2@schichtplan.de', + password: 'user123', + name: 'Tom Schmidt', + role: 'user', + isActive: true, + phone: '+49 123 456792', + department: 'Produktion', + createdAt: new Date().toISOString(), + lastLogin: new Date().toISOString() } ]; @@ -51,18 +74,19 @@ app.get('/api/health', (req: any, res: any) => { }); }); -// Login Route für ALLE Benutzer app.post('/api/auth/login', async (req: any, res: any) => { try { const { email, password } = req.body; console.log('🔐 Login attempt for:', email); + console.log('📧 Email:', email); + console.log('🔑 Password length:', password?.length); if (!email || !password) { return res.status(400).json({ error: 'Email and password are required' }); } - // Benutzer in der employees Liste suchen + // Benutzer suchen const user = employees.find(emp => emp.email === email && emp.isActive); if (!user) { @@ -70,20 +94,15 @@ app.post('/api/auth/login', async (req: any, res: any) => { return res.status(401).json({ error: 'Invalid credentials' }); } - // Passwort vergleichen - // Für Test: Wenn Passwort nicht gehasht ist (neue Benutzer), direkt vergleichen - let isPasswordValid = false; - - if (user.password.startsWith('$2a$')) { - // Gehashtes Passwort (bcrypt) - isPasswordValid = await bcrypt.compare(password, user.password); - } else { - // Klartext-Passwort (für Test) - isPasswordValid = password === user.password; - } + console.log('🔍 User found:', user.email); + console.log('💾 Stored password:', user.password); + console.log('↔️ Password match:', password === user.password); + + // Passwort-Überprüfung + const isPasswordValid = password === user.password; if (!isPasswordValid) { - console.log('❌ Invalid password for:', email); + console.log('❌ Password invalid for:', email); return res.status(401).json({ error: 'Invalid credentials' }); } @@ -107,7 +126,7 @@ app.post('/api/auth/login', async (req: any, res: any) => { } }); -// EMPLOYEE ROUTES mit In-Memory Store +// EMPLOYEE ROUTES app.get('/api/employees', async (req: any, res: any) => { try { console.log('📋 Fetching employees - Total:', employees.length); @@ -139,19 +158,22 @@ app.post('/api/employees', async (req: any, res: any) => { return res.status(400).json({ error: 'Password must be at least 6 characters long' }); } + // Rollen-Validierung + const validRoles = ['admin', 'instandhalter', 'user']; + if (!validRoles.includes(role)) { + return res.status(400).json({ error: 'Ungültige Rolle' }); + } + // Check if email already exists if (employees.find(emp => emp.email === email)) { return res.status(409).json({ error: 'Email already exists' }); } - // Passwort hashen für neue Benutzer - const hashedPassword = await bcrypt.hash(password, 10); - - // Neuen Mitarbeiter erstellen + // NEUEN Benutzer erstellen const newEmployee = { id: uuidv4(), email, - password: hashedPassword, // Gehashtes Passwort speichern + password: password, // Klartext speichern für einfachen Test name, role, isActive: true, @@ -161,10 +183,14 @@ app.post('/api/employees', async (req: any, res: any) => { lastLogin: '' }; - // Zum Store hinzufügen employees.push(newEmployee); - console.log('✅ Employee created. Total employees:', employees.length); + console.log('✅ Employee created:', { + email: newEmployee.email, + name: newEmployee.name, + role: newEmployee.role + }); + console.log('📊 Total employees:', employees.length); // Response ohne Passwort const { password: _, ...employeeWithoutPassword } = newEmployee; @@ -189,7 +215,7 @@ app.put('/api/employees/:id', async (req: any, res: any) => { return res.status(404).json({ error: 'Employee not found' }); } - // Mitarbeiter aktualisieren (Passwort bleibt unverändert) + // Mitarbeiter aktualisieren employees[employeeIndex] = { ...employees[employeeIndex], name: name || employees[employeeIndex].name, @@ -221,7 +247,7 @@ app.delete('/api/employees/:id', async (req: any, res: any) => { return res.status(404).json({ error: 'Employee not found' }); } - // Soft delete - set isActive to false + // Soft delete employees[employeeIndex].isActive = false; console.log('✅ Employee deactivated:', employees[employeeIndex].name); @@ -232,38 +258,7 @@ app.delete('/api/employees/:id', async (req: any, res: any) => { } }); -// Get current user profile -app.get('/api/auth/me', async (req: any, res: any) => { - try { - // Einfache Mock-Implementation - // In einer echten App würde man den Token verifizieren - const token = req.headers.authorization?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ error: 'No token provided' }); - } - - // Einfache Token-"Validierung" für Demo - const tokenParts = token.split('-'); - const userId = tokenParts[tokenParts.length - 1]; - - const user = employees.find(emp => emp.id === userId && emp.isActive); - - if (!user) { - return res.status(401).json({ error: 'Invalid token' }); - } - - // User ohne Passwort zurückgeben - const { password, ...userWithoutPassword } = user; - res.json(userWithoutPassword); - - } catch (error) { - console.error('Error getting user profile:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Availability Routes (Mock) +// Availability Routes app.get('/api/employees/:employeeId/availabilities', async (req: any, res: any) => { try { const { employeeId } = req.params; @@ -284,7 +279,7 @@ app.get('/api/employees/:employeeId/availabilities', async (req: any, res: any) dayOfWeek: day, startTime: slot.start, endTime: slot.end, - isAvailable: day >= 1 && day <= 5 // Nur Mo-Fr verfügbar + isAvailable: day >= 1 && day <= 5 })) ); @@ -301,7 +296,6 @@ app.put('/api/employees/:employeeId/availabilities', async (req: any, res: any) const availabilities = req.body; console.log('💾 Saving availabilities for:', employeeId); - console.log('Data:', availabilities); // Mock erfolgreiches Speichern res.json(availabilities); @@ -316,6 +310,13 @@ app.listen(PORT, () => { console.log('🎉 BACKEND STARTED SUCCESSFULLY!'); console.log(`📍 Port: ${PORT}`); console.log(`📍 Health: http://localhost:${PORT}/api/health`); - console.log('🔐 Login system READY for ALL users!'); - console.log('👥 Employee management READY with proper authentication!'); + console.log(''); + console.log('🔐 SIMPLE LOGIN READY - Plain text passwords for testing!'); + console.log(''); + console.log('📋 TEST ACCOUNTS:'); + console.log(' 👑 Admin: admin@schichtplan.de / admin123'); + console.log(' 🔧 Instandhalter: instandhalter@schichtplan.de / instandhalter123'); + console.log(' 👤 User1: mitarbeiter1@schichtplan.de / user123'); + console.log(' 👤 User2: mitarbeiter2@schichtplan.de / user123'); + console.log(' 👤 Patrick: patrick@patrick.de / 12345678'); }); \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5bb390f..2d1ef46 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -// frontend/src/App.tsx +// frontend/src/App.tsx - KORRIGIERT import React from 'react'; import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { AuthProvider, useAuth } from './contexts/AuthContext'; @@ -17,77 +17,110 @@ const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }> roles = ['admin', 'instandhalter', 'user'] }) => { const { user, loading, hasRole } = useAuth(); - + + console.log('🔒 ProtectedRoute - User:', user?.email, 'Loading:', loading); + if (loading) { + return ( +
+
⏳ Lade Anwendung...
+
+ ); + } + + if (!user) { + console.log('❌ No user, redirecting to login'); + return ; + } + + if (!hasRole(roles)) { + console.log('❌ Insufficient permissions for:', user.email); return (
-
⏳ Lade Anwendung...
+

Zugriff verweigert

+

Sie haben keine Berechtigung für diese Seite.

); } - if (!user || !hasRole(roles)) { - return ; - } - + console.log('✅ Access granted for:', user.email); return {children}; }; function App() { + const { user, loading } = useAuth(); + + console.log('🏠 App Component - User:', user?.email, 'Loading:', loading); + + // Während des Ladens zeigen wir einen Loading Screen + if (loading) { + return ( +
+
⏳ SchichtPlaner wird geladen...
+
+ ); + } + + return ( + + + {/* Public Route */} + } /> + + {/* Protected Routes with Layout */} + + + + } /> + + + + + } /> + + + + + } /> + + + + + } /> + + + + + } /> + + + + + } /> + + + ); +} + +// Hauptkomponente mit AuthProvider +function AppWrapper() { return ( - - - {/* Public Route */} - } /> - - {/* Protected Routes with Layout */} - - - - } /> - - - - - } /> - - - - - } /> - - - - - } /> - - - - - } /> - - - - - } /> - - {/* Legal Pages (ohne Layout für einfacheren Zugang) */} - Impressum Seite} /> - Datenschutz Seite} /> - AGB Seite} /> - - + ); } -export default App; \ No newline at end of file +export default AppWrapper; \ No newline at end of file diff --git a/frontend/src/contexts/AuthContext.tsx b/frontend/src/contexts/AuthContext.tsx index 72e3d36..2e89a26 100644 --- a/frontend/src/contexts/AuthContext.tsx +++ b/frontend/src/contexts/AuthContext.tsx @@ -1,4 +1,4 @@ -// frontend/src/contexts/AuthContext.tsx +// frontend/src/contexts/AuthContext.tsx - KORRIGIERT import React, { createContext, useContext, useState, useEffect } from 'react'; import { authService, User, LoginRequest } from '../services/authService'; @@ -8,6 +8,7 @@ interface AuthContextType { logout: () => void; hasRole: (roles: string[]) => boolean; loading: boolean; + refreshUser: () => void; // NEU: Force refresh } const AuthContext = createContext(undefined); @@ -15,22 +16,44 @@ const AuthContext = createContext(undefined); export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); + const [refreshTrigger, setRefreshTrigger] = useState(0); // NEU: Refresh trigger + // User beim Start laden useEffect(() => { - // User aus localStorage laden beim Start const savedUser = authService.getCurrentUser(); if (savedUser) { setUser(savedUser); + console.log('✅ User from localStorage:', savedUser.email); } setLoading(false); }, []); + // NEU: User vom Server laden wenn nötig + useEffect(() => { + if (refreshTrigger > 0) { + const loadUserFromServer = async () => { + const serverUser = await authService.fetchCurrentUser(); + if (serverUser) { + setUser(serverUser); + console.log('✅ User from server:', serverUser.email); + } + }; + loadUserFromServer(); + } + }, [refreshTrigger]); + const login = async (credentials: LoginRequest) => { try { + console.log('🔄 Attempting login...'); const response = await authService.login(credentials); setUser(response.user); + console.log('✅ Login successful, user set:', response.user.email); + + // Force refresh der App + setRefreshTrigger(prev => prev + 1); + } catch (error) { - console.error('AuthContext: Login fehlgeschlagen', error); + console.error('❌ Login failed:', error); throw error; } }; @@ -38,6 +61,11 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children const logout = () => { authService.logout(); setUser(null); + console.log('✅ Logout completed'); + }; + + const refreshUser = () => { + setRefreshTrigger(prev => prev + 1); }; const hasRole = (roles: string[]) => { @@ -49,7 +77,8 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children login, logout, hasRole, - loading + loading, + refreshUser // NEU }; return ( diff --git a/frontend/src/pages/Auth/Login.tsx b/frontend/src/pages/Auth/Login.tsx index a1b0896..8c790a6 100644 --- a/frontend/src/pages/Auth/Login.tsx +++ b/frontend/src/pages/Auth/Login.tsx @@ -1,16 +1,16 @@ -// frontend/src/pages/Auth/Login.tsx +// frontend/src/pages/Auth/Login.tsx - KORRIGIERT import React, { useState } from 'react'; import { useAuth } from '../../contexts/AuthContext'; const Login: React.FC = () => { - const [email, setEmail] = useState('admin@schichtplan.de'); - const [password, setPassword] = useState('admin123'); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); - const { login } = useAuth(); + const { login, user } = useAuth(); // user hinzugefügt für Debugging - console.log('Login Komponente - State:', { email, password, error, loading }); + console.log('🔍 Login Component - Current user:', user); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -18,13 +18,14 @@ const Login: React.FC = () => { setLoading(true); try { - console.log('Login startet mit:', { email }); + console.log('🚀 Starting login process...'); await login({ email, password }); - console.log('Login erfolgreich abgeschlossen'); - // Force refresh als Fallback - window.location.reload(); + console.log('✅ Login process completed'); + + // Navigation passiert automatisch durch AuthContext + } catch (err: any) { - console.error('Login Fehler:', err); + console.error('❌ Login error:', err); setError(err.message || 'Login fehlgeschlagen'); } finally { setLoading(false); @@ -37,10 +38,9 @@ const Login: React.FC = () => { margin: '100px auto', padding: '20px', border: '1px solid #ddd', - borderRadius: '8px', - backgroundColor: '#f9f9f9' + borderRadius: '8px' }}> -

Anmelden

+

Anmelden

{error && (
{ backgroundColor: '#ffe6e6', padding: '10px', borderRadius: '4px', - marginBottom: '15px', - border: '1px solid #ffcccc' + marginBottom: '15px' }}> Fehler: {error}
@@ -57,7 +56,7 @@ const Login: React.FC = () => {
-
-
-