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 // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal // filename is always a hardcoded constant (e.g., 'users.json'), never user input const getDataPath = (filename) => { const cwd = process.cwd() // In production (.output/server), working dir is .output if (cwd.endsWith('.output')) { // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal return path.join(cwd, '../server/data', filename) } // In development, working dir is project root // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal 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' } // Liste möglicher alter Verschlüsselungsschlüssel (für Migration) function getPossibleEncryptionKeys() { const currentKey = getEncryptionKey() const oldKeys = [ 'default-key-change-in-production', 'local_development_encryption_key_change_in_production' ] // Aktueller Schlüssel zuerst, dann alte Schlüssel const keys = [currentKey] for (const oldKey of oldKeys) { if (oldKey !== currentKey) { keys.push(oldKey) } } // Optional: Alter Schlüssel aus Environment-Variable if (process.env.OLD_ENCRYPTION_KEY && process.env.OLD_ENCRYPTION_KEY !== currentKey) { keys.push(process.env.OLD_ENCRYPTION_KEY) } return keys } // 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 { // 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) { // Versuche mit verschiedenen Schlüsseln zu entschlüsseln (für Migration) const possibleKeys = getPossibleEncryptionKeys() const encryptionKey = getEncryptionKey() let lastError = null for (let i = 0; i < possibleKeys.length; i++) { const key = possibleKeys[i] try { users = decryptObject(data, key) // Wenn mit altem Schlüssel entschlüsselt wurde, warnen und neu verschlüsseln if (i > 0 && key !== encryptionKey) { console.warn(`⚠️ Benutzerdaten wurden mit altem Schlüssel entschlüsselt. Automatische Neuverschlüsselung...`) try { await writeUsers(users) console.log('✅ Benutzerdaten erfolgreich mit neuem Schlüssel neu verschlüsselt') } catch (writeError) { console.error('❌ Fehler beim Neuverschlüsseln:', writeError.message) console.error(' Bitte führen Sie manuell aus: node scripts/re-encrypt-data.js') } } break // Erfolgreich entschlüsselt } catch (decryptError) { lastError = decryptError // Versuche nächsten Schlüssel continue } } // Wenn alle Schlüssel fehlgeschlagen sind if (!users || users.length === 0) { console.error('Fehler beim Entschlüsseln der Benutzerdaten:') console.error(' Versuchte Schlüssel:', possibleKeys.length) console.error(' Letzter Fehler:', lastError?.message || 'Unbekannter Fehler') console.error('') console.error('💡 Lösung: Führen Sie das Re-Encrypt-Skript aus:') console.error(' node scripts/re-encrypt-data.js --old-key=""') console.error(' Oder setzen Sie OLD_ENCRYPTION_KEY als Environment-Variable') // Fallback: try to read as plain JSON 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) }