fixed parsing admin setup

This commit is contained in:
2025-10-09 01:16:26 +02:00
parent 6052a600dd
commit 6e64343d62
7 changed files with 3084 additions and 203 deletions

2853
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,20 +9,22 @@
"start": "node dist/server.js" "start": "node dist/server.js"
}, },
"dependencies": { "dependencies": {
"express": "^4.18.2", "@types/bcrypt": "^6.0.0",
"cors": "^2.8.5", "bcrypt": "^6.0.0",
"sqlite3": "^5.1.6",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"express": "^4.18.2",
"jsonwebtoken": "^9.0.2",
"sqlite3": "^5.1.6",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.17",
"@types/cors": "^2.8.13",
"@types/jsonwebtoken": "^9.0.2",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.2",
"@types/uuid": "^9.0.2", "@types/uuid": "^9.0.2",
"typescript": "^5.0.0", "ts-node": "^10.9.0",
"ts-node": "^10.9.0" "typescript": "^5.0.0"
} }
} }

View File

@@ -1,168 +1,203 @@
// backend/src/controllers/authController.ts
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { v4 as uuidv4 } from 'uuid'; import bcrypt from 'bcrypt';
import { db } from '../services/databaseService.js'; import { db } from '../services/databaseService.js';
import { AuthRequest } from '../middleware/auth.js';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; export interface User {
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d'; id: number;
email: string;
export const login = async (req: Request, res: Response): Promise<void> => { name: string;
try { role: string;
const { email, password } = req.body; phone?: string;
department?: string;
if (!email || !password) {
res.status(400).json({ error: 'Email and password are required' });
return;
} }
// User aus Datenbank holen export interface UserWithPassword extends User {
const user = await db.get<any>( password: string;
'SELECT * FROM users WHERE email = ?', }
export interface LoginRequest {
email: string;
password: string;
}
export interface JWTPayload {
id: number;
email: string;
role: string;
iat?: number;
exp?: number;
}
export interface RegisterRequest {
email: string;
password: string;
name: string;
phone?: string;
department?: string;
role?: string;
}
export const login = async (req: Request, res: Response) => {
try {
const { email, password } = req.body as LoginRequest;
if (!email || !password) {
return res.status(400).json({ error: 'E-Mail und Passwort sind erforderlich' });
}
// Get user from database
const user = await db.get<UserWithPassword>(
'SELECT id, email, password, name, role, phone, department FROM users WHERE email = ?',
[email] [email]
); );
if (!user) { if (!user) {
res.status(401).json({ error: 'Invalid credentials' }); return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
return;
} }
// Passwort vergleichen // Verify password
const isPasswordValid = await bcrypt.compare(password, user.password); const validPassword = await bcrypt.compare(password, user.password);
if (!isPasswordValid) { if (!validPassword) {
res.status(401).json({ error: 'Invalid credentials' }); return res.status(401).json({ error: 'Ungültige Anmeldedaten' });
return;
} }
// JWT Token generieren // Create token payload
const token = jwt.sign( const tokenPayload: JWTPayload = {
{ id: user.id,
userId: user.id,
email: user.email, email: user.email,
role: user.role role: user.role
}, };
JWT_SECRET as jwt.Secret,
{ expiresIn: JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] } // Create token
const token = jwt.sign(
tokenPayload,
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '24h' }
); );
// User ohne Passwort zurückgeben // Remove password from user object
const { password: _, ...userWithoutPassword } = user; const { password: _, ...userWithoutPassword } = user;
res.json({ res.json({
user: userWithoutPassword, user: userWithoutPassword,
token, token
expiresIn: JWT_EXPIRES_IN
}); });
} catch (error) { } catch (error) {
console.error('Login error:', error); console.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({ error: 'Ein Fehler ist beim Login aufgetreten' });
} }
}; };
export const register = async (req: Request, res: Response): Promise<void> => { export const getCurrentUser = async (req: Request, res: Response) => {
try { try {
const { email, password, name, role = 'user' } = req.body; const jwtUser = (req as any).user as JWTPayload;
if (!jwtUser?.id) {
if (!email || !password || !name) { return res.status(401).json({ error: 'Nicht authentifiziert' });
res.status(400).json({ error: 'Email, password and name are required' });
return;
} }
// Check if user already exists const user = await db.get<User>(
const existingUser = await db.get<any>( 'SELECT id, email, name, role, phone, department FROM users WHERE id = ?',
[jwtUser.id]
);
if (!user) {
return res.status(404).json({ error: 'Benutzer nicht gefunden' });
}
res.json({ user });
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: 'Ein Fehler ist beim Abrufen des Benutzers aufgetreten' });
}
};
export const validateToken = async (req: Request, res: Response) => {
try {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Kein Token vorhanden' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key') as JWTPayload;
// Verify that the decoded token has the required fields
if (!decoded.id || !decoded.email || !decoded.role) {
throw new Error('Invalid token structure');
}
res.json({ valid: true, user: decoded });
} catch (jwtError) {
return res.status(401).json({ valid: false, error: 'Ungültiger Token' });
}
} catch (error) {
console.error('Token validation error:', error);
res.status(500).json({ valid: false, error: 'Fehler bei der Token-Validierung' });
}
};
export const register = async (req: Request, res: Response) => {
try {
const { email, password, name, phone, department, role = 'user' } = req.body as RegisterRequest;
// Validate required fields
if (!email || !password || !name) {
return res.status(400).json({
error: 'E-Mail, Passwort und Name sind erforderlich'
});
}
// Check if email already exists
const existingUser = await db.get<User>(
'SELECT id FROM users WHERE email = ?', 'SELECT id FROM users WHERE email = ?',
[email] [email]
); );
if (existingUser) { if (existingUser) {
res.status(409).json({ error: 'User already exists' }); return res.status(400).json({
return; error: 'Ein Benutzer mit dieser E-Mail existiert bereits'
} });
// Validate role
const validRoles = ['admin', 'instandhalter', 'user'];
if (!validRoles.includes(role)) {
res.status(400).json({ error: 'Invalid role' });
return;
} }
// Hash password // Hash password
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10);
const userId = uuidv4();
// Create user // Insert user
await db.run( const result = await db.run(
'INSERT INTO users (id, email, password, name, role) VALUES (?, ?, ?, ?, ?)', `INSERT INTO users (email, password, name, role, phone, department)
[userId, email, hashedPassword, name, role] VALUES (?, ?, ?, ?, ?, ?)`,
[email, hashedPassword, name, role, phone, department]
); );
// Generate token if (!result.lastID) {
const token = jwt.sign( throw new Error('Benutzer konnte nicht erstellt werden');
{ }
userId,
email, // Get created user
role const newUser = await db.get<User>(
}, 'SELECT id, email, name, role, phone, department FROM users WHERE id = ?',
JWT_SECRET as jwt.Secret, [result.lastID]
{ expiresIn: JWT_EXPIRES_IN as jwt.SignOptions['expiresIn'] }
); );
// Return user without password res.status(201).json({ user: newUser });
const user = {
id: userId,
email,
name,
role,
createdAt: new Date().toISOString()
};
res.status(201).json({
user,
token,
expiresIn: JWT_EXPIRES_IN
});
} catch (error) { } catch (error) {
console.error('Registration error:', error); console.error('Registration error:', error);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
error: 'Fehler bei der Registrierung'
});
} }
}; };
export const logout = async (req: AuthRequest, res: Response): Promise<void> => { export const logout = async (req: Request, res: Response) => {
try { try {
// Bei JWT gibt es keinen Server-side logout, aber wir können den Token client-seitig entfernen // Note: Since we're using JWTs, we don't need to do anything server-side
res.json({ message: 'Logged out successfully' }); // The client should remove the token from storage
res.json({ message: 'Erfolgreich abgemeldet' });
} catch (error) { } catch (error) {
console.error('Logout error:', error); console.error('Logout error:', error);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({
} error: 'Fehler beim Abmelden'
}; });
export const getCurrentUser = async (req: AuthRequest, res: Response): Promise<void> => {
try {
const userId = req.user?.userId;
if (!userId) {
res.status(401).json({ error: 'Not authenticated' });
return;
}
const user = await db.get<any>(
'SELECT id, email, name, role, created_at FROM users WHERE id = ?',
[userId]
);
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.json(user);
} catch (error) {
console.error('Get current user error:', error);
res.status(500).json({ error: 'Internal server error' });
} }
}; };

View File

@@ -1,35 +1,23 @@
// backend/src/controllers/setupController.ts // backend/src/controllers/setupController.ts
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid'; 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 {
// First, ensure database is properly initialized const adminExists = await db.get<{ 'COUNT(*)': number }>(
try { 'SELECT COUNT(*) FROM users WHERE role = ?',
const adminExists = await db.get<{ count: number }>(
'SELECT COUNT(*) as count FROM users WHERE role = ?',
['admin'] ['admin']
); );
res.json({ res.json({
needsSetup: !adminExists || adminExists.count === 0, needsSetup: !adminExists || adminExists['COUNT(*)'] === 0
message: adminExists && adminExists.count > 0 ? 'Admin user exists' : 'No admin user found'
}); });
} catch (dbError) {
console.error('Database error in checkSetupStatus:', dbError);
// If there's a database error, assume setup is needed
res.json({
needsSetup: true,
message: 'Database not ready, setup required'
});
}
} catch (error) { } catch (error) {
console.error('Error checking setup status:', error); console.error('Error checking setup status:', error);
res.status(500).json({ res.status(500).json({
error: 'Internal server error', error: 'Internal server error during setup check'
needsSetup: true
}); });
} }
}; };
@@ -37,52 +25,36 @@ export const checkSetupStatus = async (req: Request, res: Response): Promise<voi
export const setupAdmin = async (req: Request, res: Response): Promise<void> => { 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(*) as count FROM users WHERE role = ?', 'SELECT COUNT(*) FROM users WHERE role = ?',
['admin'] ['admin']
); );
if (adminExists && adminExists.count > 0) { if (adminExists && adminExists['COUNT(*)'] > 0) {
res.status(400).json({ error: 'Admin user already exists' }); res.status(400).json({ error: 'Admin existiert bereits' });
return; return;
} }
const { email, password, name, phone, department } = req.body; const { password, name, phone, department } = req.body;
const email = 'admin@instandhaltung.de'; // Fixed admin email
// Validation // Validation
if (!email || !password || !name) { if (!password || !name) {
res.status(400).json({ error: 'Email, password, and name are required' }); res.status(400).json({ error: 'Passwort und Name sind erforderlich' });
return;
}
// Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
res.status(400).json({ error: 'Invalid email format' });
return; return;
} }
// Password length validation // Password length validation
if (password.length < 6) { if (password.length < 6) {
res.status(400).json({ error: 'Password must be at least 6 characters long' }); res.status(400).json({ error: 'Das Passwort muss mindestens 6 Zeichen lang sein' });
return;
}
// Check if email already exists
const existingUser = await db.get<{ id: string }>(
'SELECT id FROM users WHERE email = ?',
[email]
);
if (existingUser) {
res.status(409).json({ error: 'Email already exists' });
return; return;
} }
// Hash password // Hash password
const hashedPassword = await bcrypt.hash(password, 10); const hashedPassword = await bcrypt.hash(password, 10);
const adminId = uuidv4(); const adminId = randomUUID();
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)
@@ -91,12 +63,20 @@ export const setupAdmin = async (req: Request, res: Response): Promise<void> =>
); );
res.status(201).json({ res.status(201).json({
message: 'Admin user created successfully', success: true,
userId: adminId, message: 'Admin erfolgreich erstellt',
email: email email: email
}); });
} catch (dbError) {
console.error('Database error during admin creation:', dbError);
res.status(500).json({
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({ error: 'Internal server error' }); res.status(500).json({
error: 'Ein unerwarteter Fehler ist aufgetreten'
});
} }
}; };

View File

@@ -1,12 +1,22 @@
// backend/src/routes/auth.ts // backend/src/routes/auth.ts
import express from 'express'; import express from 'express';
import { login, register, logout, getCurrentUser } from '../controllers/authController.js'; import {
login,
register,
logout,
getCurrentUser,
validateToken
} from '../controllers/authController.js';
import { authMiddleware } from '../middleware/auth.js'; import { authMiddleware } from '../middleware/auth.js';
const router = express.Router(); const router = express.Router();
// Public routes
router.post('/login', login); router.post('/login', login);
router.post('/register', register); router.post('/register', register);
router.get('/validate', validateToken);
// Protected routes (require authentication)
router.post('/logout', authMiddleware, logout); router.post('/logout', authMiddleware, logout);
router.get('/me', authMiddleware, getCurrentUser); router.get('/me', authMiddleware, getCurrentUser);

View File

@@ -42,18 +42,13 @@ app.get('/api/initial-setup', async (req: any, res: any) => {
try { try {
const { db } = await import('./services/databaseService.js'); const { db } = await import('./services/databaseService.js');
// Define proper interface for the result const adminExists = await db.get<{ 'COUNT(*)': number }>(
interface AdminCountResult { 'SELECT COUNT(*) FROM users WHERE role = ?',
count: number;
}
const adminExists = await db.get<AdminCountResult>(
'SELECT COUNT(*) as count FROM users WHERE role = ?',
['admin'] ['admin']
); );
res.json({ res.json({
needsInitialSetup: !adminExists || adminExists.count === 0 needsInitialSetup: !adminExists || adminExists['COUNT(*)'] === 0
}); });
} catch (error) { } catch (error) {
console.error('Error checking initial setup:', error); console.error('Error checking initial setup:', error);
@@ -61,23 +56,29 @@ app.get('/api/initial-setup', async (req: any, res: any) => {
} }
}); });
// Start server // Initialize the application
app.listen(PORT, async () => { const initializeApp = async () => {
console.log('🎉 BACKEND STARTED SUCCESSFULLY!');
console.log(`📍 Port: ${PORT}`);
console.log(`📍 Health: http://localhost:${PORT}/api/health`);
try { try {
await initializeDatabase(); await initializeDatabase();
console.log('✅ Database initialized successfully'); console.log('✅ Database initialized successfully');
await setupDefaultTemplate(); await setupDefaultTemplate();
console.log('✅ Default template checked/created'); console.log('✅ Default template checked/created');
} catch (error) {
console.error('❌ Error during initialization:', error);
}
// Start server only after successful initialization
app.listen(PORT, () => {
console.log('🎉 BACKEND STARTED SUCCESSFULLY!');
console.log(`📍 Port: ${PORT}`);
console.log(`📍 Health: http://localhost:${PORT}/api/health`);
console.log(''); console.log('');
console.log(`🔧 Setup ready at: http://localhost:${PORT}/api/setup/status`); console.log(`🔧 Setup ready at: http://localhost:${PORT}/api/setup/status`);
console.log('📝 Create your admin account on first launch'); console.log('📝 Create your admin account on first launch');
}); });
} catch (error) {
console.error('❌ Error during initialization:', error);
process.exit(1); // Exit if initialization fails
}
};
// Start the application
initializeApp();

View File

@@ -32,11 +32,11 @@ class Database {
}); });
} }
async run(sql: string, params: any[] = []): Promise<void> { async run(sql: string, params: any[] = []): Promise<{ lastID?: number }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.db.run(sql, params, (err) => { this.db.run(sql, params, function(err) {
if (err) reject(err); if (err) reject(err);
else resolve(); else resolve({ lastID: this.lastID });
}); });
}); });
} }