import bcrypt from 'bcryptjs' import jwt from 'jsonwebtoken' import { promises as fs } from 'fs' import path from 'path' import { encryptObject, decryptObject } from './encryption.js' // Export migrateUserRoles für Verwendung in anderen Modulen export function migrateUserRoles(user) { if (!user) return user // Wenn bereits roles Array vorhanden, nichts tun if (Array.isArray(user.roles)) { return user } // Wenn role vorhanden, zu roles Array konvertieren if (user.role) { user.roles = [user.role] delete user.role } else { // Fallback: Standard-Rolle user.roles = ['mitglied'] } return user } const JWT_SECRET = process.env.JWT_SECRET || 'harheimertc-secret-key-change-in-production' // Handle both dev and production paths const getDataPath = (filename) => { const cwd = process.cwd() // In production (.output/server), working dir is .output if (cwd.endsWith('.output')) { return path.join(cwd, '../server/data', filename) } // In development, working dir is project root return path.join(cwd, 'server/data', filename) } const USERS_FILE = getDataPath('users.json') const SESSIONS_FILE = getDataPath('sessions.json') // Get encryption key from environment function getEncryptionKey() { return process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production' } // Check if data is encrypted by trying to parse as JSON first function isEncrypted(data) { try { const parsed = JSON.parse(data.trim()) if (Array.isArray(parsed)) { return false // Unencrypted array } if (typeof parsed === 'object' && parsed !== null && !parsed.encryptedData) { return false } return false } catch (e) { // JSON parsing failed - likely encrypted base64 return true } } // Read users from file (with encryption support and migration) export async function readUsers() { try { const data = await fs.readFile(USERS_FILE, 'utf-8') const encrypted = isEncrypted(data) let users = [] if (encrypted) { const encryptionKey = getEncryptionKey() try { users = decryptObject(data, encryptionKey) } catch (decryptError) { console.error('Fehler beim Entschlüsseln der Benutzerdaten:', decryptError) try { users = JSON.parse(data) console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen') } catch (parseError) { console.error('Konnte Benutzerdaten weder entschlüsseln noch als JSON lesen') return [] } } } else { // Plain JSON - migrate to encrypted format users = JSON.parse(data) console.log('Migriere unverschlüsselte Benutzerdaten zu verschlüsselter Speicherung...') } // Migriere Rollen von role zu roles Array let needsMigration = false users = users.map(user => { const migrated = migrateUserRoles(user) if (!Array.isArray(user.roles) && user.role) { needsMigration = true } return migrated }) // Wenn Migration nötig war, speichere zurück if (needsMigration) { console.log('Migriere Rollen von role zu roles Array...') await writeUsers(users) } else if (!encrypted) { // Write back encrypted wenn noch nicht verschlüsselt await writeUsers(users) } return users } catch (error) { if (error.code === 'ENOENT') { return [] } console.error('Fehler beim Lesen der Benutzerdaten:', error) return [] } } // Write users to file (always encrypted) export async function writeUsers(users) { try { const encryptionKey = getEncryptionKey() const encryptedData = encryptObject(users, encryptionKey) await fs.writeFile(USERS_FILE, encryptedData, 'utf-8') return true } catch (error) { console.error('Fehler beim Schreiben der Benutzerdaten:', error) return false } } // Prüft ob Sessions-Daten verschlüsselt sind function isSessionsEncrypted(data) { try { const parsed = JSON.parse(data.trim()) if (Array.isArray(parsed)) { return false } if (typeof parsed === 'object' && parsed !== null && !parsed.encryptedData) { return false } return false } catch (e) { return true } } // Read sessions from file (with encryption support) export async function readSessions() { try { const data = await fs.readFile(SESSIONS_FILE, 'utf-8') const encrypted = isSessionsEncrypted(data) if (encrypted) { const encryptionKey = getEncryptionKey() try { return decryptObject(data, encryptionKey) } catch (decryptError) { console.error('Fehler beim Entschlüsseln der Sessions:', decryptError) try { const plainData = JSON.parse(data) console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen') return plainData } catch (parseError) { console.error('Konnte Sessions weder entschlüsseln noch als JSON lesen') return [] } } } else { // Plain JSON - migriere zu verschlüsselter Speicherung const sessions = JSON.parse(data) console.log('Migriere unverschlüsselte Sessions zu verschlüsselter Speicherung...') await writeSessions(sessions) return sessions } } catch (error) { if (error.code === 'ENOENT') { return [] } console.error('Fehler beim Lesen der Sessions:', error) return [] } } // Write sessions to file (always encrypted) export async function writeSessions(sessions) { try { const encryptionKey = getEncryptionKey() const encryptedData = encryptObject(sessions, encryptionKey) await fs.writeFile(SESSIONS_FILE, encryptedData, 'utf-8') return true } catch (error) { console.error('Fehler beim Schreiben der Sessions:', error) return false } } // Hash password export async function hashPassword(password) { const salt = await bcrypt.genSalt(10) return await bcrypt.hash(password, salt) } // Verify password export async function verifyPassword(password, hash) { return await bcrypt.compare(password, hash) } // Generate JWT token export function generateToken(user) { // Stelle sicher, dass Rollen migriert sind const migratedUser = migrateUserRoles({ ...user }) const roles = Array.isArray(migratedUser.roles) ? migratedUser.roles : (migratedUser.role ? [migratedUser.role] : ['mitglied']) return jwt.sign( { id: user.id, email: user.email, roles: roles }, JWT_SECRET, { expiresIn: '7d' } ) } // Verify JWT token export function verifyToken(token) { try { return jwt.verify(token, JWT_SECRET) } catch (error) { return null } } // Get user by ID export async function getUserById(id) { const users = await readUsers() const user = users.find(u => u.id === id) return user ? migrateUserRoles(user) : null } // Get user by email export async function getUserByEmail(email) { const users = await readUsers() const user = users.find(u => u.email === email) return user ? migrateUserRoles(user) : null } // Prüft ob Benutzer eine bestimmte Rolle hat export function hasRole(user, role) { if (!user) return false const roles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []) return roles.includes(role) } // Prüft ob Benutzer eine der angegebenen Rollen hat export function hasAnyRole(user, ...roles) { if (!user) return false const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []) return roles.some(role => userRoles.includes(role)) } // Prüft ob Benutzer alle angegebenen Rollen hat export function hasAllRoles(user, ...roles) { if (!user) return false const userRoles = Array.isArray(user.roles) ? user.roles : (user.role ? [user.role] : []) return roles.every(role => userRoles.includes(role)) } // Get user from token export async function getUserFromToken(token) { const decoded = verifyToken(token) if (!decoded) return null const users = await readUsers() const user = users.find(u => u.id === decoded.id) // Migriere Rollen beim Laden if (user) { migrateUserRoles(user) } return user } // Create session export async function createSession(userId, token) { const sessions = await readSessions() const session = { id: Date.now().toString(), userId, token, createdAt: new Date().toISOString(), expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 7 days } sessions.push(session) await writeSessions(sessions) return session } // Delete session export async function deleteSession(token) { const sessions = await readSessions() const filtered = sessions.filter(s => s.token !== token) await writeSessions(filtered) } // Clean expired sessions export async function cleanExpiredSessions() { const sessions = await readSessions() const now = new Date() const valid = sessions.filter(s => new Date(s.expiresAt) > now) await writeSessions(valid) }