#!/usr/bin/env node /** * Script zum Neuverschlüsseln aller verschlüsselten Daten * * Verwendung: * node scripts/re-encrypt-data.js [--old-key="alter-schlüssel"] * * Wenn --old-key nicht angegeben wird, wird der Standard-Entwicklungsschlüssel verwendet. * Der neue Schlüssel wird aus der .env Datei gelesen (ENCRYPTION_KEY). */ import fs from 'fs/promises' import path from 'path' import { fileURLToPath } from 'url' import { decryptObject, encryptObject, decrypt, encrypt } from '../server/utils/encryption.js' import dotenv from 'dotenv' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) // Lade .env Datei dotenv.config({ path: path.join(__dirname, '..', '.env') }) // Alte Standard-Schlüssel (die möglicherweise verwendet wurden) const OLD_DEFAULT_KEYS = [ 'default-key-change-in-production', 'local_development_encryption_key_change_in_production' ] // Neuer Schlüssel aus .env const NEW_KEY = process.env.ENCRYPTION_KEY if (!NEW_KEY) { console.error('❌ FEHLER: ENCRYPTION_KEY ist nicht in der .env Datei gesetzt!') console.error('Bitte setzen Sie ENCRYPTION_KEY in der .env Datei.') process.exit(1) } // Alten Schlüssel aus Kommandozeilenargumenten oder Standard verwenden const args = process.argv.slice(2) let oldKeyArg = null for (const arg of args) { if (arg.startsWith('--old-key=')) { oldKeyArg = arg.split('=')[1] } } // Pfade bestimmen function getDataPath(filename) { const cwd = process.cwd() if (cwd.endsWith('.output')) { return path.join(cwd, '../server/data', filename) } return path.join(cwd, 'server/data', filename) } const USERS_FILE = getDataPath('users.json') const MEMBERS_FILE = getDataPath('members.json') const MEMBERSHIP_APPLICATIONS_DIR = getDataPath('membership-applications') // Backup-Verzeichnis erstellen async function createBackup() { const backupDir = path.join(__dirname, '..', 'backups', `re-encrypt-${Date.now()}`) await fs.mkdir(backupDir, { recursive: true }) console.log(`📦 Backup-Verzeichnis erstellt: ${backupDir}`) return backupDir } // Prüft ob Daten verschlüsselt sind function isEncrypted(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 } } // Versucht mit verschiedenen Schlüsseln zu entschlüsseln async function decryptWithFallback(encryptedData, keys) { const errors = [] // encryptedData sollte bereits ein String sein (entweder direkt verschlüsselt oder aus einem JSON-Objekt extrahiert) // Versuche mit jedem Schlüssel zu entschlüsseln for (const key of keys) { try { // Direkt verschlüsselte Base64-Daten return decryptObject(encryptedData, key) } catch (error) { errors.push({ key: key.length > 30 ? key.substring(0, 30) + '...' : key, error: error.message }) continue } } throw new Error(`Entschlüsselung fehlgeschlagen mit allen Schlüsseln:\n${errors.map(e => ` - ${e.key}: ${e.error}`).join('\n')}`) } // Neuverschlüsselt users.json async function reencryptUsers(backupDir, oldKeys) { try { const data = await fs.readFile(USERS_FILE, 'utf-8') // Backup erstellen await fs.copyFile(USERS_FILE, path.join(backupDir, 'users.json')) console.log('✅ Backup von users.json erstellt') if (!isEncrypted(data)) { console.log('ℹ️ users.json ist nicht verschlüsselt, überspringe...') return } console.log('🔄 Entschlüssele users.json...') const decrypted = await decryptWithFallback(data, oldKeys) console.log('🔐 Verschlüssele users.json mit neuem Schlüssel...') const reencrypted = encryptObject(decrypted, NEW_KEY) await fs.writeFile(USERS_FILE, reencrypted, 'utf-8') console.log('✅ users.json erfolgreich neu verschlüsselt') } catch (error) { if (error.code === 'ENOENT') { console.log('ℹ️ users.json existiert nicht, überspringe...') return } throw error } } // Neuverschlüsselt members.json async function reencryptMembers(backupDir, oldKeys) { try { const data = await fs.readFile(MEMBERS_FILE, 'utf-8') // Backup erstellen await fs.copyFile(MEMBERS_FILE, path.join(backupDir, 'members.json')) console.log('✅ Backup von members.json erstellt') if (!isEncrypted(data)) { console.log('ℹ️ members.json ist nicht verschlüsselt, überspringe...') return } console.log('🔄 Entschlüssele members.json...') const decrypted = await decryptWithFallback(data, oldKeys) console.log('🔐 Verschlüssele members.json mit neuem Schlüssel...') const reencrypted = encryptObject(decrypted, NEW_KEY) await fs.writeFile(MEMBERS_FILE, reencrypted, 'utf-8') console.log('✅ members.json erfolgreich neu verschlüsselt') } catch (error) { if (error.code === 'ENOENT') { console.log('ℹ️ members.json existiert nicht, überspringe...') return } throw error } } // Neuverschlüsselt Mitgliedschaftsanträge async function reencryptMembershipApplications(backupDir, oldKeys) { try { await fs.access(MEMBERSHIP_APPLICATIONS_DIR) } catch { console.log('ℹ️ membership-applications Verzeichnis existiert nicht, überspringe...') return } const files = await fs.readdir(MEMBERSHIP_APPLICATIONS_DIR) let processed = 0 let skipped = 0 for (const file of files) { const filePath = path.join(MEMBERSHIP_APPLICATIONS_DIR, file) const stat = await fs.stat(filePath) if (stat.isDirectory()) { continue } try { // Backup erstellen const backupPath = path.join(backupDir, 'membership-applications', file) await fs.mkdir(path.dirname(backupPath), { recursive: true }) await fs.copyFile(filePath, backupPath) const content = await fs.readFile(filePath, 'utf-8') const parsed = JSON.parse(content) // Prüfe ob encryptedData Feld vorhanden ist if (parsed.encryptedData) { console.log(`🔄 Entschlüssele ${file}...`) // Nur das encryptedData Feld entschlüsseln const decrypted = await decryptWithFallback(parsed.encryptedData, oldKeys) console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`) const reencrypted = encryptObject(decrypted, NEW_KEY) parsed.encryptedData = reencrypted await fs.writeFile(filePath, JSON.stringify(parsed, null, 2), 'utf-8') console.log(`✅ ${file} erfolgreich neu verschlüsselt`) processed++ } else if (file.endsWith('.data')) { // .data Dateien sind direkt verschlüsselt console.log(`🔄 Entschlüssele ${file}...`) const decrypted = await decryptWithFallback(content, oldKeys) console.log(`🔐 Verschlüssele ${file} mit neuem Schlüssel...`) const reencrypted = encrypt(JSON.stringify(decrypted), NEW_KEY) await fs.writeFile(filePath, reencrypted, 'utf-8') console.log(`✅ ${file} erfolgreich neu verschlüsselt`) processed++ } else { console.log(`ℹ️ ${file} enthält keine verschlüsselten Daten, überspringe...`) skipped++ } } catch (error) { console.error(`❌ Fehler beim Verarbeiten von ${file}:`, error.message) throw error } } console.log(`✅ Mitgliedschaftsanträge verarbeitet: ${processed} neu verschlüsselt, ${skipped} übersprungen`) } // Hauptfunktion async function main() { console.log('🔐 Daten-Neuverschlüsselung gestartet\n') // Alte Schlüssel zusammenstellen const oldKeys = oldKeyArg ? [oldKeyArg, ...OLD_DEFAULT_KEYS] : OLD_DEFAULT_KEYS console.log('Alte Schlüssel (werden nacheinander versucht):') oldKeys.forEach((key, i) => { const displayKey = key.length > 50 ? key.substring(0, 50) + '...' : key console.log(` ${i + 1}. ${displayKey}`) }) console.log(`\nNeuer Schlüssel: ${NEW_KEY.length > 50 ? NEW_KEY.substring(0, 50) + '...' : NEW_KEY}\n`) // Bestätigung console.log('⚠️ WICHTIG: Dieses Script wird alle verschlüsselten Daten neu verschlüsseln!') console.log('📦 Backups werden automatisch erstellt.\n') // Backup-Verzeichnis erstellen const backupDir = await createBackup() try { // Dateien neu verschlüsseln await reencryptUsers(backupDir, oldKeys) console.log('') await reencryptMembers(backupDir, oldKeys) console.log('') await reencryptMembershipApplications(backupDir, oldKeys) console.log('') console.log('✅ Alle Daten erfolgreich neu verschlüsselt!') console.log(`📦 Backups gespeichert in: ${backupDir}`) } catch (error) { console.error('\n❌ FEHLER beim Neuverschlüsseln:', error.message) console.error('\nDie Backups sind sicher gespeichert. Sie können die Dateien manuell wiederherstellen.') process.exit(1) } } // Script ausführen main().catch(error => { console.error('Unerwarteter Fehler:', error) process.exit(1) })