mirror of
https://github.com/donpat1to/Schichtenplaner.git
synced 2025-11-30 22:45:46 +01:00
fixed login
This commit is contained in:
@@ -3,9 +3,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node --loader ts-node/esm src/server.ts",
|
"dev": "npx tsx src/server.ts",
|
||||||
"simple": "node src/server.ts",
|
"build": "tsc",
|
||||||
"build": "tsc",
|
|
||||||
"start": "node dist/server.js"
|
"start": "node dist/server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -42,29 +42,38 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
try {
|
try {
|
||||||
const { email, password } = req.body as LoginRequest;
|
const { email, password } = req.body as LoginRequest;
|
||||||
|
|
||||||
|
console.log('🔐 Login attempt for email:', email);
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
|
console.log('❌ Missing email or password');
|
||||||
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
|
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user from database
|
// Get user from database
|
||||||
const user = await db.get<UserWithPassword>(
|
const user = await db.get<UserWithPassword>(
|
||||||
'SELECT id, email, password, name, role, phone, department FROM users WHERE email = ?',
|
'SELECT id, email, password, name, role, phone, department FROM users WHERE email = ? AND is_active = 1',
|
||||||
[email]
|
[email]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('🔍 User found:', user ? 'Yes' : 'No');
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
console.log('❌ No user found with email:', email);
|
||||||
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify password
|
// Verify password
|
||||||
const validPassword = await bcrypt.compare(password, user.password);
|
const validPassword = await bcrypt.compare(password, user.password);
|
||||||
|
console.log('🔑 Password valid:', validPassword);
|
||||||
|
|
||||||
if (!validPassword) {
|
if (!validPassword) {
|
||||||
|
console.log('❌ Invalid password for user:', email);
|
||||||
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create token payload
|
// Create token payload - ID als STRING verwenden
|
||||||
const tokenPayload: JWTPayload = {
|
const tokenPayload: JWTPayload = {
|
||||||
id: user.id.toString(), // ← Sicherstellen dass es string ist
|
id: user.id.toString(), // ← WICHTIG: Als string
|
||||||
email: user.email,
|
email: user.email,
|
||||||
role: user.role
|
role: user.role
|
||||||
};
|
};
|
||||||
@@ -79,6 +88,8 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
// Remove password from user object
|
// Remove password from user object
|
||||||
const { password: _, ...userWithoutPassword } = user;
|
const { password: _, ...userWithoutPassword } = user;
|
||||||
|
|
||||||
|
console.log('✅ Login successful for:', user.email);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
user: userWithoutPassword,
|
user: userWithoutPassword,
|
||||||
token
|
token
|
||||||
@@ -92,19 +103,26 @@ export const login = async (req: Request, res: Response) => {
|
|||||||
export const getCurrentUser = async (req: Request, res: Response) => {
|
export const getCurrentUser = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const jwtUser = (req as any).user as JWTPayload;
|
const jwtUser = (req as any).user as JWTPayload;
|
||||||
|
console.log('🔍 Getting current user for ID:', jwtUser?.id);
|
||||||
|
|
||||||
if (!jwtUser?.id) {
|
if (!jwtUser?.id) {
|
||||||
|
console.log('❌ No user ID in JWT');
|
||||||
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
return res.status(401).json({ error: 'Nicht authentifiziert' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await db.get<User>(
|
const user = await db.get<User>(
|
||||||
'SELECT id, email, name, role, phone, department FROM users WHERE id = ?',
|
'SELECT id, email, name, role, phone, department FROM users WHERE id = ? AND is_active = 1',
|
||||||
[jwtUser.id]
|
[jwtUser.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('🔍 User found in database:', user ? 'Yes' : 'No');
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
console.log('❌ User not found in database for ID:', jwtUser.id);
|
||||||
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('✅ Returning user:', user.email);
|
||||||
res.json({ user });
|
res.json({ user });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Get current user error:', error);
|
console.error('Get current user error:', error);
|
||||||
|
|||||||
@@ -5,6 +5,25 @@ import bcrypt from 'bcryptjs';
|
|||||||
import { db } from '../services/databaseService.js';
|
import { db } from '../services/databaseService.js';
|
||||||
import { AuthRequest } from '../middleware/auth.js';
|
import { AuthRequest } from '../middleware/auth.js';
|
||||||
|
|
||||||
|
export const getEmployees = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const employees = await db.all<any>(`
|
||||||
|
SELECT
|
||||||
|
id, email, name, role, is_active as isActive,
|
||||||
|
phone, department, created_at as createdAt,
|
||||||
|
last_login as lastLogin
|
||||||
|
FROM users
|
||||||
|
WHERE is_active = 1
|
||||||
|
ORDER BY name
|
||||||
|
`);
|
||||||
|
|
||||||
|
res.json(employees);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching employees:', error);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
export const getEmployee = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|||||||
@@ -1,18 +1,24 @@
|
|||||||
// backend/src/controllers/setupController.ts
|
// backend/src/controllers/setupController.ts
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
import { db } from '../services/databaseService.js';
|
import { db } from '../services/databaseService.js';
|
||||||
|
|
||||||
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
export const checkSetupStatus = async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
||||||
'SELECT COUNT(*) FROM users WHERE role = ?',
|
'SELECT COUNT(*) FROM users WHERE role = ? AND is_active = 1',
|
||||||
['admin']
|
['admin']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('Admin exists check:', adminExists);
|
||||||
|
|
||||||
|
// Korrekte Rückgabe - needsSetup sollte true sein wenn KEIN Admin existiert
|
||||||
|
const needsSetup = !adminExists || adminExists['COUNT(*)'] === 0;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
needsSetup: !adminExists || adminExists['COUNT(*)'] === 0
|
needsSetup: needsSetup
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking setup status:', error);
|
console.error('Error checking setup status:', error);
|
||||||
@@ -26,11 +32,14 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
try {
|
try {
|
||||||
// Check if admin already exists
|
// Check if admin already exists
|
||||||
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
const adminExists = await db.get<{ 'COUNT(*)': number }>(
|
||||||
'SELECT COUNT(*) FROM users WHERE role = ?',
|
'SELECT COUNT(*) FROM users WHERE role = ? AND is_active = 1',
|
||||||
['admin']
|
['admin']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('🔍 Admin exists check:', adminExists);
|
||||||
|
|
||||||
if (adminExists && adminExists['COUNT(*)'] > 0) {
|
if (adminExists && adminExists['COUNT(*)'] > 0) {
|
||||||
|
console.log('❌ Admin already exists');
|
||||||
res.status(400).json({ error: 'Admin existiert bereits' });
|
res.status(400).json({ error: 'Admin existiert bereits' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -38,6 +47,8 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
const { password, name, phone, department } = req.body;
|
const { password, name, phone, department } = req.body;
|
||||||
const email = 'admin@instandhaltung.de'; // Fixed admin email
|
const email = 'admin@instandhaltung.de'; // Fixed admin email
|
||||||
|
|
||||||
|
console.log('👤 Creating admin with data:', { name, email, phone, department });
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (!password || !name) {
|
if (!password || !name) {
|
||||||
res.status(400).json({ error: 'Passwort und Name sind erforderlich' });
|
res.status(400).json({ error: 'Passwort und Name sind erforderlich' });
|
||||||
@@ -52,29 +63,33 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
|
|||||||
|
|
||||||
// Hash password
|
// Hash password
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
const adminId = randomUUID();
|
const adminId = uuidv4();
|
||||||
|
|
||||||
|
console.log('📝 Inserting admin user with ID:', adminId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create admin user
|
// Create admin user
|
||||||
await db.run(
|
await db.run(
|
||||||
`INSERT INTO users (id, email, password, name, role, phone, department, is_active)
|
`INSERT INTO users (id, email, password, name, role, phone, department, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
[adminId, email, hashedPassword, name, 'admin', phone || null, department || null, true]
|
[adminId, email, hashedPassword, name, 'admin', phone || null, department || null, 1]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('✅ Admin user created successfully');
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Admin erfolgreich erstellt',
|
message: 'Admin erfolgreich erstellt',
|
||||||
email: email
|
email: email
|
||||||
});
|
});
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
console.error('Database error during admin creation:', dbError);
|
console.error('❌ Database error during admin creation:', dbError);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Fehler beim Erstellen des Admin-Accounts'
|
error: 'Fehler beim Erstellen des Admin-Accounts'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in setup:', error);
|
console.error('❌ Error in setup:', error);
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Ein unerwarteter Fehler ist aufgetreten'
|
error: 'Ein unerwarteter Fehler ist aufgetreten'
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// backend/src/middleware/auth.ts
|
// backend/src/middleware/auth.ts
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { JWTPayload } from '../controllers/authController.js';
|
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||||
|
|
||||||
@@ -14,15 +13,21 @@ export interface AuthRequest extends Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const authMiddleware = (req: AuthRequest, res: Response, next: NextFunction): void => {
|
export const authMiddleware = (req: AuthRequest, res: Response, next: NextFunction): void => {
|
||||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
const authHeader = req.header('Authorization');
|
||||||
|
console.log('🔐 Auth middleware - Authorization header:', authHeader);
|
||||||
|
|
||||||
|
const token = authHeader?.replace('Bearer ', '');
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
|
console.log('❌ No token provided');
|
||||||
res.status(401).json({ error: 'Access denied. No token provided.' });
|
res.status(401).json({ error: 'Access denied. No token provided.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
|
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||||
|
console.log('✅ Token valid for user:', decoded.email);
|
||||||
|
|
||||||
req.user = {
|
req.user = {
|
||||||
userId: decoded.id,
|
userId: decoded.id,
|
||||||
email: decoded.email,
|
email: decoded.email,
|
||||||
@@ -30,6 +35,7 @@ export const authMiddleware = (req: AuthRequest, res: Response, next: NextFuncti
|
|||||||
};
|
};
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('❌ Invalid token:', error);
|
||||||
res.status(400).json({ error: 'Invalid token.' });
|
res.status(400).json({ error: 'Invalid token.' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -37,6 +43,7 @@ export const authMiddleware = (req: AuthRequest, res: Response, next: NextFuncti
|
|||||||
export const requireRole = (roles: string[]) => {
|
export const requireRole = (roles: string[]) => {
|
||||||
return (req: AuthRequest, res: Response, next: NextFunction): void => {
|
return (req: AuthRequest, res: Response, next: NextFunction): void => {
|
||||||
if (!req.user || !roles.includes(req.user.role)) {
|
if (!req.user || !roles.includes(req.user.role)) {
|
||||||
|
console.log('❌ Insufficient permissions for user:', req.user?.email);
|
||||||
res.status(403).json({ error: 'Access denied. Insufficient permissions.' });
|
res.status(403).json({ error: 'Access denied. Insufficient permissions.' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import Settings from './pages/Settings/Settings';
|
|||||||
import Help from './pages/Help/Help';
|
import Help from './pages/Help/Help';
|
||||||
import Setup from './pages/Setup/Setup';
|
import Setup from './pages/Setup/Setup';
|
||||||
|
|
||||||
// Protected Route Component direkt in App.tsx
|
// Protected Route Component
|
||||||
const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }> = ({
|
const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }> = ({
|
||||||
children,
|
children,
|
||||||
roles = ['admin', 'instandhalter', 'user']
|
roles = ['admin', 'instandhalter', 'user']
|
||||||
@@ -47,6 +47,89 @@ const ProtectedRoute: React.FC<{ children: React.ReactNode; roles?: string[] }>
|
|||||||
return <Layout>{children}</Layout>;
|
return <Layout>{children}</Layout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SetupWrapper Component
|
||||||
|
const SetupWrapper: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Setup />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// LoginWrapper Component
|
||||||
|
const LoginWrapper: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<Login />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main App Content
|
||||||
|
const AppContent: React.FC = () => {
|
||||||
|
const { loading, needsSetup, user } = useAuth();
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||||
|
<div>⏳ Lade Anwendung...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('AppContent - needsSetup:', needsSetup, 'user:', user);
|
||||||
|
|
||||||
|
// Wenn Setup benötigt wird → Setup zeigen (mit Router)
|
||||||
|
if (needsSetup) {
|
||||||
|
return <SetupWrapper />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn kein User eingeloggt ist → Login zeigen (mit Router)
|
||||||
|
if (!user) {
|
||||||
|
return <LoginWrapper />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn User eingeloggt ist → Geschützte Routen zeigen
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<NotificationContainer />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Dashboard />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/shift-plans" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<ShiftPlanList />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/shift-plans/new" element={
|
||||||
|
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
||||||
|
<ShiftPlanCreate />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/employees" element={
|
||||||
|
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
||||||
|
<EmployeeManagement />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/settings" element={
|
||||||
|
<ProtectedRoute roles={['admin']}>
|
||||||
|
<Settings />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/help" element={
|
||||||
|
<ProtectedRoute>
|
||||||
|
<Help />
|
||||||
|
</ProtectedRoute>
|
||||||
|
} />
|
||||||
|
<Route path="/login" element={<Login />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
@@ -57,61 +140,4 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AppContent() {
|
|
||||||
const { loading, needsSetup } = useAuth();
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
|
||||||
<div>⏳ Lade Anwendung...</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<NotificationContainer />
|
|
||||||
<Routes>
|
|
||||||
{needsSetup ? (
|
|
||||||
<Route path="*" element={<Setup />} />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Route path="/login" element={<Login />} />
|
|
||||||
<Route path="/" element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<Dashboard />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/shift-plans" element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<ShiftPlanList />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/shift-plans/new" element={
|
|
||||||
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
|
||||||
<ShiftPlanCreate />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/employees" element={
|
|
||||||
<ProtectedRoute roles={['admin', 'instandhalter']}>
|
|
||||||
<EmployeeManagement />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/settings" element={
|
|
||||||
<ProtectedRoute roles={['admin']}>
|
|
||||||
<Settings />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
<Route path="/help" element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<Help />
|
|
||||||
</ProtectedRoute>
|
|
||||||
} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Routes>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
// frontend/src/contexts/AuthContext.tsx
|
// frontend/src/contexts/AuthContext.tsx
|
||||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||||
import { authService, User, LoginRequest } from '../services/authService';
|
import { Employee } from '../types/employee';
|
||||||
|
|
||||||
|
interface LoginRequest {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
user: User | null;
|
user: Employee | null;
|
||||||
login: (credentials: LoginRequest) => Promise<void>;
|
login: (credentials: LoginRequest) => Promise<void>;
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
hasRole: (roles: string[]) => boolean;
|
hasRole: (roles: string[]) => boolean;
|
||||||
@@ -15,102 +20,130 @@ interface AuthContextType {
|
|||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
interface AuthProviderProps {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||||
|
const [user, setUser] = useState<Employee | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [needsSetup, setNeedsSetup] = useState(false);
|
const [needsSetup, setNeedsSetup] = useState(false);
|
||||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
||||||
|
|
||||||
const checkSetupStatus = async () => {
|
// Token aus localStorage laden
|
||||||
|
const getStoredToken = (): string | null => {
|
||||||
|
return localStorage.getItem('token');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Token in localStorage speichern
|
||||||
|
const setStoredToken = (token: string) => {
|
||||||
|
localStorage.setItem('token', token);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Token aus localStorage entfernen
|
||||||
|
const removeStoredToken = () => {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkSetupStatus = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/setup/status');
|
const response = await fetch('http://localhost:3002/api/setup/status');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Failed to check setup status');
|
throw new Error('Setup status check failed');
|
||||||
}
|
}
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setNeedsSetup(data.needsSetup);
|
console.log('Setup status response:', data);
|
||||||
|
setNeedsSetup(data.needsSetup === true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking setup status:', error);
|
console.error('Error checking setup status:', error);
|
||||||
// If we can't reach the server, assume setup is needed
|
setNeedsSetup(false);
|
||||||
setNeedsSetup(true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check setup status and load user on mount
|
const refreshUser = async () => {
|
||||||
useEffect(() => {
|
|
||||||
const initializeApp = async () => {
|
|
||||||
await checkSetupStatus();
|
|
||||||
|
|
||||||
// Only try to load user if setup is not needed
|
|
||||||
if (!needsSetup) {
|
|
||||||
const savedUser = authService.getCurrentUser();
|
|
||||||
if (savedUser) {
|
|
||||||
setUser(savedUser);
|
|
||||||
console.log('✅ User from localStorage:', savedUser.email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
initializeApp();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Update needsSetup when it changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!needsSetup && !user) {
|
|
||||||
// If setup is complete but no user is loaded, try to load from localStorage
|
|
||||||
const savedUser = authService.getCurrentUser();
|
|
||||||
if (savedUser) {
|
|
||||||
setUser(savedUser);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [needsSetup, user]);
|
|
||||||
|
|
||||||
// User vom Server laden wenn nötig
|
|
||||||
useEffect(() => {
|
|
||||||
if (refreshTrigger > 0 && !needsSetup) {
|
|
||||||
const loadUserFromServer = async () => {
|
|
||||||
const serverUser = await authService.fetchCurrentUser();
|
|
||||||
if (serverUser) {
|
|
||||||
setUser(serverUser);
|
|
||||||
console.log('✅ User from server:', serverUser.email);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
loadUserFromServer();
|
|
||||||
}
|
|
||||||
}, [refreshTrigger, needsSetup]);
|
|
||||||
|
|
||||||
const login = async (credentials: LoginRequest) => {
|
|
||||||
try {
|
try {
|
||||||
console.log('🔄 Attempting login...');
|
const token = getStoredToken();
|
||||||
const response = await authService.login(credentials);
|
console.log('🔄 Refreshing user, token exists:', !!token);
|
||||||
setUser(response.user);
|
|
||||||
console.log('✅ Login successful, user set:', response.user.email);
|
|
||||||
|
|
||||||
// Force refresh der App
|
|
||||||
setRefreshTrigger(prev => prev + 1);
|
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('http://localhost:3002/api/auth/me', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('✅ User refreshed:', data.user);
|
||||||
|
setUser(data.user);
|
||||||
|
} else {
|
||||||
|
console.log('❌ Token invalid, removing from storage');
|
||||||
|
removeStoredToken();
|
||||||
|
setUser(null);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Login failed:', error);
|
console.error('Error refreshing user:', error);
|
||||||
|
removeStoredToken();
|
||||||
|
setUser(null);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const login = async (credentials: LoginRequest): Promise<void> => {
|
||||||
|
try {
|
||||||
|
console.log('🔐 Attempting login for:', credentials.email);
|
||||||
|
|
||||||
|
const response = await fetch('http://localhost:3002/api/auth/login', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(credentials),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('✅ Login successful, storing token');
|
||||||
|
|
||||||
|
// Token persistent speichern
|
||||||
|
setStoredToken(data.token);
|
||||||
|
setUser(data.user);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
authService.logout();
|
console.log('🚪 Logging out user');
|
||||||
|
removeStoredToken();
|
||||||
setUser(null);
|
setUser(null);
|
||||||
console.log('✅ Logout completed');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshUser = () => {
|
const hasRole = (roles: string[]): boolean => {
|
||||||
setRefreshTrigger(prev => prev + 1);
|
if (!user) return false;
|
||||||
|
return roles.includes(user.role);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasRole = (roles: string[]) => {
|
useEffect(() => {
|
||||||
return user ? roles.includes(user.role) : false;
|
const initializeAuth = async () => {
|
||||||
};
|
console.log('🚀 Initializing authentication...');
|
||||||
|
await checkSetupStatus();
|
||||||
|
await refreshUser();
|
||||||
|
};
|
||||||
|
|
||||||
const value = {
|
initializeAuth();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const value: AuthContextType = {
|
||||||
user,
|
user,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
@@ -118,9 +151,18 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
loading,
|
loading,
|
||||||
refreshUser,
|
refreshUser,
|
||||||
needsSetup,
|
needsSetup,
|
||||||
checkSetupStatus
|
checkSetupStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('🔄 Auth state changed - user:', user, 'loading:', loading);
|
||||||
|
}, [user, loading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const token = getStoredToken();
|
||||||
|
console.log('💾 Stored token on mount:', token ? 'Exists' : 'None');
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
@@ -128,7 +170,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAuth = () => {
|
export const useAuth = (): AuthContextType => {
|
||||||
const context = useContext(AuthContext);
|
const context = useContext(AuthContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('useAuth must be used within an AuthProvider');
|
throw new Error('useAuth must be used within an AuthProvider');
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ const Setup: React.FC = () => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
|
|
||||||
// Create the request payload
|
|
||||||
const payload = {
|
const payload = {
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
@@ -72,7 +71,7 @@ const Setup: React.FC = () => {
|
|||||||
...(formData.department ? { department: formData.department } : {})
|
...(formData.department ? { department: formData.department } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Sending setup request with payload:', payload);
|
console.log('🚀 Sending setup request with payload:', payload);
|
||||||
|
|
||||||
const response = await fetch('http://localhost:3002/api/setup/admin', {
|
const response = await fetch('http://localhost:3002/api/setup/admin', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -82,41 +81,38 @@ const Setup: React.FC = () => {
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const responseText = await response.text();
|
||||||
|
console.log('📨 Setup response:', responseText);
|
||||||
|
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = JSON.parse(responseText);
|
||||||
|
} catch (parseError) {
|
||||||
|
console.error('❌ Failed to parse response as JSON:', responseText);
|
||||||
|
throw new Error('Invalid server response');
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
throw new Error(result.error || 'Setup fehlgeschlagen');
|
||||||
throw new Error(data.error || 'Setup fehlgeschlagen');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check response format
|
console.log('✅ Setup successful:', result);
|
||||||
const contentType = response.headers.get('content-type');
|
|
||||||
if (!contentType || !contentType.includes('application/json')) {
|
|
||||||
const text = await response.text();
|
|
||||||
console.error('Non-JSON response:', text);
|
|
||||||
throw new Error('Server returned non-JSON response');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
// WICHTIG: Setup Status neu prüfen und dann zu Login navigieren
|
||||||
console.log('Setup response:', result);
|
|
||||||
|
|
||||||
// Re-check setup status after successful setup
|
|
||||||
await checkSetupStatus();
|
await checkSetupStatus();
|
||||||
|
|
||||||
// Automatically log in after setup
|
// Kurze Verzögerung damit der State aktualisiert werden kann
|
||||||
await login({
|
setTimeout(() => {
|
||||||
email: 'admin@instandhaltung.de',
|
navigate('/login');
|
||||||
password: formData.password
|
}, 100);
|
||||||
});
|
|
||||||
|
|
||||||
navigate('/');
|
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Setup error:', err);
|
console.error('❌ Setup error:', err);
|
||||||
setError(typeof err === 'string' ? err : err.message || 'Ein unerwarteter Fehler ist aufgetreten');
|
setError(typeof err === 'string' ? err : err.message || 'Ein unerwarteter Fehler ist aufgetreten');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||||
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8">
|
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8">
|
||||||
|
|||||||
@@ -1,127 +1,110 @@
|
|||||||
// frontend/src/services/employeeService.ts
|
// frontend/src/services/employeeService.ts
|
||||||
import { Employee, Availability, CreateEmployeeRequest, UpdateEmployeeRequest } from '../types/employee';
|
import { Employee, CreateEmployeeRequest, UpdateEmployeeRequest, Availability } from '../types/employee';
|
||||||
import { authService } from './authService';
|
|
||||||
|
|
||||||
const API_BASE = 'http://localhost:3002/api/employees';
|
const API_BASE_URL = 'http://localhost:3002/api';
|
||||||
|
|
||||||
|
const getAuthHeaders = () => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
return {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export class EmployeeService {
|
export class EmployeeService {
|
||||||
// Alle Mitarbeiter abrufen
|
|
||||||
async getEmployees(): Promise<Employee[]> {
|
async getEmployees(): Promise<Employee[]> {
|
||||||
const response = await fetch(`${API_BASE}?_=${Date.now()}`, {
|
const response = await fetch(`${API_BASE_URL}/employees`, {
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Fehler beim Laden der Mitarbeiter');
|
throw new Error('Failed to fetch employees');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Einzelnen Mitarbeiter abrufen
|
|
||||||
async getEmployee(id: string): Promise<Employee> {
|
async getEmployee(id: string): Promise<Employee> {
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Mitarbeiter nicht gefunden');
|
throw new Error('Failed to fetch employee');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neuen Mitarbeiter erstellen
|
async createEmployee(employee: CreateEmployeeRequest): Promise<Employee> {
|
||||||
async createEmployee(employeeData: CreateEmployeeRequest): Promise<Employee> {
|
const response = await fetch(`${API_BASE_URL}/employees`, {
|
||||||
const response = await fetch(API_BASE, {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify(employee),
|
||||||
...authService.getAuthHeaders()
|
|
||||||
},
|
|
||||||
body: JSON.stringify(employeeData)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json();
|
const error = await response.json();
|
||||||
throw new Error(error.error || 'Fehler beim Erstellen des Mitarbeiters');
|
throw new Error(error.error || 'Failed to create employee');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mitarbeiter aktualisieren
|
async updateEmployee(id: string, employee: UpdateEmployeeRequest): Promise<Employee> {
|
||||||
async updateEmployee(id: string, updates: UpdateEmployeeRequest): Promise<Employee> {
|
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify(employee),
|
||||||
...authService.getAuthHeaders()
|
|
||||||
},
|
|
||||||
body: JSON.stringify(updates)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Fehler beim Aktualisieren des Mitarbeiters');
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to update employee');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mitarbeiter permanent löschen
|
|
||||||
async deleteEmployee(id: string): Promise<void> {
|
async deleteEmployee(id: string): Promise<void> {
|
||||||
const response = await fetch(`${API_BASE}/${id}`, {
|
const response = await fetch(`${API_BASE_URL}/employees/${id}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json().catch(() => ({ error: 'Fehler beim Löschen des Mitarbeiters' }));
|
const error = await response.json();
|
||||||
throw new Error(error.error || 'Fehler beim Löschen des Mitarbeiters');
|
throw new Error(error.error || 'Failed to delete employee');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verfügbarkeiten abrufen
|
|
||||||
async getAvailabilities(employeeId: string): Promise<Availability[]> {
|
async getAvailabilities(employeeId: string): Promise<Availability[]> {
|
||||||
const response = await fetch(`${API_BASE}/${employeeId}/availabilities`, {
|
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...authService.getAuthHeaders()
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Fehler beim Laden der Verfügbarkeiten');
|
throw new Error('Failed to fetch availabilities');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verfügbarkeiten aktualisieren
|
|
||||||
async updateAvailabilities(employeeId: string, availabilities: Availability[]): Promise<Availability[]> {
|
async updateAvailabilities(employeeId: string, availabilities: Availability[]): Promise<Availability[]> {
|
||||||
const response = await fetch(`${API_BASE}/${employeeId}/availabilities`, {
|
const response = await fetch(`${API_BASE_URL}/employees/${employeeId}/availabilities`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: getAuthHeaders(),
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify(availabilities),
|
||||||
...authService.getAuthHeaders()
|
|
||||||
},
|
|
||||||
body: JSON.stringify(availabilities)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error('Fehler beim Aktualisieren der Verfügbarkeiten');
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to update availabilities');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
// ✅ Exportiere eine Instanz der Klasse
|
||||||
|
export const employeeService = new EmployeeService();
|
||||||
15
frontend/src/types/user.ts
Normal file
15
frontend/src/types/user.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// frontend/src/types/user.ts
|
||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: 'admin' | 'instandhalter' | 'user';
|
||||||
|
phone?: string;
|
||||||
|
department?: string;
|
||||||
|
lastLogin?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginRequest {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user