334 lines
11 KiB
JavaScript
Executable File
334 lines
11 KiB
JavaScript
Executable File
#!/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) {
|
||
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal
|
||
// filename is always a hardcoded constant (e.g., 'users.json'), never user input
|
||
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
|
||
}
|
||
}
|
||
|
||
// Prüft ob Daten bereits mit dem neuen Schlüssel verschlüsselt sind
|
||
async function isEncryptedWithNewKey(encryptedData) {
|
||
try {
|
||
await decryptObject(encryptedData, NEW_KEY)
|
||
return true
|
||
} catch {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// Versucht mit verschiedenen Schlüsseln zu entschlüsseln
|
||
async function decryptWithFallback(encryptedData, keys) {
|
||
const errors = []
|
||
|
||
// Prüfe zuerst, ob die Daten bereits mit dem neuen Schlüssel verschlüsselt sind
|
||
if (await isEncryptedWithNewKey(encryptedData)) {
|
||
throw new Error('ALREADY_ENCRYPTED_WITH_NEW_KEY')
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|
||
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
|
||
if (await isEncryptedWithNewKey(data)) {
|
||
console.log('ℹ️ users.json ist bereits mit dem neuen Schlüssel 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
|
||
}
|
||
if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
|
||
console.log('ℹ️ users.json ist bereits mit dem neuen Schlüssel verschlüsselt, ü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
|
||
}
|
||
|
||
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
|
||
if (await isEncryptedWithNewKey(data)) {
|
||
console.log('ℹ️ members.json ist bereits mit dem neuen Schlüssel 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
|
||
}
|
||
if (error.message === 'ALREADY_ENCRYPTED_WITH_NEW_KEY') {
|
||
console.log('ℹ️ members.json ist bereits mit dem neuen Schlüssel verschlüsselt, ü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) {
|
||
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
|
||
if (await isEncryptedWithNewKey(parsed.encryptedData)) {
|
||
console.log(`ℹ️ ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
|
||
skipped++
|
||
} else {
|
||
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
|
||
// Prüfe ob bereits mit neuem Schlüssel verschlüsselt
|
||
if (await isEncryptedWithNewKey(content)) {
|
||
console.log(`ℹ️ ${file} ist bereits mit dem neuen Schlüssel verschlüsselt, überspringe...`)
|
||
skipped++
|
||
} else {
|
||
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) {
|
||
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring
|
||
// file is from readdir, not user input; error.message is safe
|
||
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)
|
||
})
|
||
|