Add support for multiple encryption keys in data handling
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 1m1s
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 1m1s
This commit introduces a mechanism to handle multiple possible encryption keys for data decryption across various modules, including auth.js, members.js, newsletter.js, and encryption.js. It adds functions to retrieve potential old keys for migration purposes and updates the decryption logic to attempt decryption with these keys. Additionally, it includes warnings for users when old keys are used and provides guidance for re-encrypting data. This enhancement improves data migration capabilities and ensures backward compatibility with previously encrypted data.
This commit is contained in:
@@ -52,6 +52,30 @@ function getEncryptionKey() {
|
|||||||
return process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
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
|
// Check if data is encrypted by trying to parse as JSON first
|
||||||
function isEncrypted(data) {
|
function isEncrypted(data) {
|
||||||
try {
|
try {
|
||||||
@@ -78,11 +102,47 @@ export async function readUsers() {
|
|||||||
let users = []
|
let users = []
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
|
// Versuche mit verschiedenen Schlüsseln zu entschlüsseln (für Migration)
|
||||||
|
const possibleKeys = getPossibleEncryptionKeys()
|
||||||
const encryptionKey = getEncryptionKey()
|
const encryptionKey = getEncryptionKey()
|
||||||
|
|
||||||
|
let lastError = null
|
||||||
|
for (let i = 0; i < possibleKeys.length; i++) {
|
||||||
|
const key = possibleKeys[i]
|
||||||
try {
|
try {
|
||||||
users = decryptObject(data, encryptionKey)
|
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) {
|
} catch (decryptError) {
|
||||||
console.error('Fehler beim Entschlüsseln der Benutzerdaten:', 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="<alter-schlüssel>"')
|
||||||
|
console.error(' Oder setzen Sie OLD_ENCRYPTION_KEY als Environment-Variable')
|
||||||
|
|
||||||
|
// Fallback: try to read as plain JSON
|
||||||
try {
|
try {
|
||||||
users = JSON.parse(data)
|
users = JSON.parse(data)
|
||||||
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
||||||
|
|||||||
@@ -48,14 +48,25 @@ function encryptV2GCM(text, password) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function decryptLegacyCBC(encryptedData, password) {
|
function decryptLegacyCBC(encryptedData, password) {
|
||||||
|
try {
|
||||||
// Base64 dekodieren
|
// Base64 dekodieren
|
||||||
const combined = Buffer.from(encryptedData, 'base64')
|
const combined = Buffer.from(encryptedData, 'base64')
|
||||||
|
|
||||||
|
// Prüfe minimale Länge (salt + iv = 32 + 16 = 48 bytes)
|
||||||
|
if (combined.length < SALT_LENGTH + LEGACY_IV_LENGTH) {
|
||||||
|
throw new Error(`Ungültige Datenlänge: ${combined.length} bytes (erwartet mindestens ${SALT_LENGTH + LEGACY_IV_LENGTH})`)
|
||||||
|
}
|
||||||
|
|
||||||
// Komponenten extrahieren (v1: salt(32) + iv(16) + ciphertext)
|
// Komponenten extrahieren (v1: salt(32) + iv(16) + ciphertext)
|
||||||
const salt = combined.subarray(0, SALT_LENGTH)
|
const salt = combined.subarray(0, SALT_LENGTH)
|
||||||
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + LEGACY_IV_LENGTH)
|
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + LEGACY_IV_LENGTH)
|
||||||
const encrypted = combined.subarray(SALT_LENGTH + LEGACY_IV_LENGTH)
|
const encrypted = combined.subarray(SALT_LENGTH + LEGACY_IV_LENGTH)
|
||||||
|
|
||||||
|
// Prüfe ob verschlüsselte Daten vorhanden sind
|
||||||
|
if (encrypted.length === 0) {
|
||||||
|
throw new Error('Keine verschlüsselten Daten gefunden')
|
||||||
|
}
|
||||||
|
|
||||||
// Schlüssel ableiten
|
// Schlüssel ableiten
|
||||||
const key = deriveKey(password, salt)
|
const key = deriveKey(password, salt)
|
||||||
|
|
||||||
@@ -69,12 +80,23 @@ function decryptLegacyCBC(encryptedData, password) {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return decrypted.toString('utf8')
|
return decrypted.toString('utf8')
|
||||||
|
} catch (error) {
|
||||||
|
// Re-throw mit mehr Kontext
|
||||||
|
throw new Error(`Legacy CBC Entschlüsselung fehlgeschlagen: ${error.message}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function decryptV2GCM(encryptedData, password) {
|
function decryptV2GCM(encryptedData, password) {
|
||||||
|
try {
|
||||||
const b64 = encryptedData.slice(VERSION_PREFIX.length)
|
const b64 = encryptedData.slice(VERSION_PREFIX.length)
|
||||||
const combined = Buffer.from(b64, 'base64')
|
const combined = Buffer.from(b64, 'base64')
|
||||||
|
|
||||||
|
// Prüfe minimale Länge (salt + iv + tag = 32 + 12 + 16 = 60 bytes)
|
||||||
|
const minLength = SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH
|
||||||
|
if (combined.length < minLength) {
|
||||||
|
throw new Error(`Ungültige Datenlänge: ${combined.length} bytes (erwartet mindestens ${minLength})`)
|
||||||
|
}
|
||||||
|
|
||||||
// v2: salt(32) + iv(12) + tag(16) + ciphertext
|
// v2: salt(32) + iv(12) + tag(16) + ciphertext
|
||||||
const salt = combined.subarray(0, SALT_LENGTH)
|
const salt = combined.subarray(0, SALT_LENGTH)
|
||||||
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH)
|
const iv = combined.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH)
|
||||||
@@ -82,6 +104,11 @@ function decryptV2GCM(encryptedData, password) {
|
|||||||
const tag = combined.subarray(tagStart, tagStart + AUTH_TAG_LENGTH)
|
const tag = combined.subarray(tagStart, tagStart + AUTH_TAG_LENGTH)
|
||||||
const encrypted = combined.subarray(tagStart + AUTH_TAG_LENGTH)
|
const encrypted = combined.subarray(tagStart + AUTH_TAG_LENGTH)
|
||||||
|
|
||||||
|
// Prüfe ob verschlüsselte Daten vorhanden sind
|
||||||
|
if (encrypted.length === 0) {
|
||||||
|
throw new Error('Keine verschlüsselten Daten gefunden')
|
||||||
|
}
|
||||||
|
|
||||||
const key = deriveKey(password, salt)
|
const key = deriveKey(password, salt)
|
||||||
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
|
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
|
||||||
decipher.setAuthTag(tag)
|
decipher.setAuthTag(tag)
|
||||||
@@ -92,6 +119,10 @@ function decryptV2GCM(encryptedData, password) {
|
|||||||
])
|
])
|
||||||
|
|
||||||
return decrypted.toString('utf8')
|
return decrypted.toString('utf8')
|
||||||
|
} catch (error) {
|
||||||
|
// Re-throw mit mehr Kontext
|
||||||
|
throw new Error(`GCM v2 Entschlüsselung fehlgeschlagen: ${error.message}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,7 +142,16 @@ export function encrypt(text, password) {
|
|||||||
*/
|
*/
|
||||||
export function decrypt(encryptedData, password) {
|
export function decrypt(encryptedData, password) {
|
||||||
try {
|
try {
|
||||||
if (typeof encryptedData === 'string' && encryptedData.startsWith(VERSION_PREFIX)) {
|
if (!encryptedData || typeof encryptedData !== 'string') {
|
||||||
|
throw new Error('Ungültige verschlüsselte Daten: muss ein String sein')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password || typeof password !== 'string') {
|
||||||
|
throw new Error('Verschlüsselungsschlüssel fehlt oder ist ungültig')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encryptedData.startsWith(VERSION_PREFIX)) {
|
||||||
|
// v2 GCM Format
|
||||||
return decryptV2GCM(encryptedData, password)
|
return decryptV2GCM(encryptedData, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +159,11 @@ export function decrypt(encryptedData, password) {
|
|||||||
return decryptLegacyCBC(encryptedData, password)
|
return decryptLegacyCBC(encryptedData, password)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Entschlüsselungsfehler:', error)
|
console.error('Entschlüsselungsfehler:', error)
|
||||||
throw new Error('Fehler beim Entschlüsseln der Daten')
|
// Re-throw mit mehr Details für besseres Debugging
|
||||||
|
if (error.message.includes('Entschlüsselung fehlgeschlagen')) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
throw new Error(`Fehler beim Entschlüsseln der Daten: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,11 +27,51 @@ function getEncryptionKey() {
|
|||||||
return process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
return process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if data is encrypted by trying to parse as JSON first
|
// 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 checking format indicators
|
||||||
function isEncrypted(data) {
|
function isEncrypted(data) {
|
||||||
|
if (!data || typeof data !== 'string') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = data.trim()
|
||||||
|
|
||||||
|
// Empty or whitespace only
|
||||||
|
if (!trimmed) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for v2 prefix (v2:base64...)
|
||||||
|
if (trimmed.startsWith('v2:')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as JSON - if successful, it's likely unencrypted
|
||||||
try {
|
try {
|
||||||
// Try to parse as JSON - if successful and looks like member data, it's unencrypted
|
const parsed = JSON.parse(trimmed)
|
||||||
const parsed = JSON.parse(data.trim())
|
|
||||||
// If it's an array (members list) or object with member-like structure, it's unencrypted
|
// If it's an array (members list) or object with member-like structure, it's unencrypted
|
||||||
if (Array.isArray(parsed)) {
|
if (Array.isArray(parsed)) {
|
||||||
return false // Unencrypted array
|
return false // Unencrypted array
|
||||||
@@ -42,9 +82,16 @@ function isEncrypted(data) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
// JSON parsing failed - likely encrypted base64
|
// JSON parsing failed - check if it looks like base64 (encrypted)
|
||||||
|
// Base64 strings typically don't contain spaces and have specific character set
|
||||||
|
const base64Pattern = /^[A-Za-z0-9+/=]+$/
|
||||||
|
if (base64Pattern.test(trimmed) && trimmed.length > 50) {
|
||||||
|
// Looks like base64 and long enough to be encrypted data
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
// If it's not valid JSON and doesn't look like base64, assume unencrypted but malformed
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read manual members from file (with encryption support and migration)
|
// Read manual members from file (with encryption support and migration)
|
||||||
@@ -52,29 +99,78 @@ export async function readMembers() {
|
|||||||
try {
|
try {
|
||||||
const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
|
const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
|
||||||
|
|
||||||
|
// Check if file is empty or whitespace only
|
||||||
|
if (!data || !data.trim()) {
|
||||||
|
console.warn('Mitgliederdaten-Datei ist leer')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
// Check if data is encrypted or plain JSON
|
// Check if data is encrypted or plain JSON
|
||||||
const encrypted = isEncrypted(data)
|
const encrypted = isEncrypted(data)
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
// Decrypt and parse
|
// Versuche mit verschiedenen Schlüsseln zu entschlüsseln (für Migration)
|
||||||
|
const possibleKeys = getPossibleEncryptionKeys()
|
||||||
const encryptionKey = getEncryptionKey()
|
const encryptionKey = getEncryptionKey()
|
||||||
|
|
||||||
|
// Warn if using default encryption key in production
|
||||||
|
if (process.env.NODE_ENV === 'production' &&
|
||||||
|
encryptionKey === 'local_development_encryption_key_change_in_production') {
|
||||||
|
console.error('WARNUNG: Produktionsumgebung verwendet Standard-Verschlüsselungsschlüssel!')
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastError = null
|
||||||
|
for (let i = 0; i < possibleKeys.length; i++) {
|
||||||
|
const key = possibleKeys[i]
|
||||||
try {
|
try {
|
||||||
return decryptObject(data, encryptionKey)
|
const decrypted = decryptObject(data, key)
|
||||||
|
|
||||||
|
// Wenn mit altem Schlüssel entschlüsselt wurde, warnen und neu verschlüsseln
|
||||||
|
if (i > 0 && key !== encryptionKey) {
|
||||||
|
console.warn(`⚠️ Mitgliederdaten wurden mit altem Schlüssel entschlüsselt. Automatische Neuverschlüsselung...`)
|
||||||
|
try {
|
||||||
|
await writeMembers(decrypted)
|
||||||
|
console.log('✅ Mitgliederdaten 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypted
|
||||||
} catch (decryptError) {
|
} catch (decryptError) {
|
||||||
console.error('Fehler beim Entschlüsseln der Mitgliederdaten:', decryptError)
|
lastError = decryptError
|
||||||
|
// Versuche nächsten Schlüssel
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Schlüssel fehlgeschlagen
|
||||||
|
console.error('Fehler beim Entschlüsseln der Mitgliederdaten:')
|
||||||
|
console.error(' Versuchte Schlüssel:', possibleKeys.length)
|
||||||
|
console.error(' Letzter Fehler:', lastError?.message || 'Unbekannter Fehler')
|
||||||
|
console.error(' Daten-Länge:', data.length)
|
||||||
|
console.error(' Daten-Präfix (erste 50 Zeichen):', data.substring(0, 50))
|
||||||
|
console.error('')
|
||||||
|
console.error('💡 Lösung: Führen Sie das Re-Encrypt-Skript aus:')
|
||||||
|
console.error(' node scripts/re-encrypt-data.js --old-key="<alter-schlüssel>"')
|
||||||
|
console.error(' Oder setzen Sie OLD_ENCRYPTION_KEY als Environment-Variable')
|
||||||
|
|
||||||
// Fallback: try to read as plain JSON (migration scenario)
|
// Fallback: try to read as plain JSON (migration scenario)
|
||||||
try {
|
try {
|
||||||
const plainData = JSON.parse(data)
|
const plainData = JSON.parse(data.trim())
|
||||||
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
||||||
|
// Don't auto-migrate if decryption failed - might be wrong key
|
||||||
return plainData
|
return plainData
|
||||||
} catch (_parseError) {
|
} catch (parseError) {
|
||||||
console.error('Konnte Mitgliederdaten weder entschlüsseln noch als JSON lesen')
|
console.error('Konnte Mitgliederdaten weder entschlüsseln noch als JSON lesen')
|
||||||
|
console.error('JSON-Parse-Fehler:', parseError.message)
|
||||||
|
// Return empty array to prevent complete failure
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Plain JSON - migrate to encrypted format
|
// Plain JSON - migrate to encrypted format
|
||||||
const members = JSON.parse(data)
|
const members = JSON.parse(data.trim())
|
||||||
console.log('Migriere unverschlüsselte Mitgliederdaten zu verschlüsselter Speicherung...')
|
console.log('Migriere unverschlüsselte Mitgliederdaten zu verschlüsselter Speicherung...')
|
||||||
|
|
||||||
// Write back encrypted
|
// Write back encrypted
|
||||||
|
|||||||
@@ -23,6 +23,30 @@ function getEncryptionKey() {
|
|||||||
return process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Prüft ob Daten verschlüsselt sind
|
// Prüft ob Daten verschlüsselt sind
|
||||||
function isEncrypted(data) {
|
function isEncrypted(data) {
|
||||||
try {
|
try {
|
||||||
@@ -45,11 +69,46 @@ export async function readSubscribers() {
|
|||||||
const encrypted = isEncrypted(data)
|
const encrypted = isEncrypted(data)
|
||||||
|
|
||||||
if (encrypted) {
|
if (encrypted) {
|
||||||
|
// Versuche mit verschiedenen Schlüsseln zu entschlüsseln (für Migration)
|
||||||
|
const possibleKeys = getPossibleEncryptionKeys()
|
||||||
const encryptionKey = getEncryptionKey()
|
const encryptionKey = getEncryptionKey()
|
||||||
|
|
||||||
|
let lastError = null
|
||||||
|
for (let i = 0; i < possibleKeys.length; i++) {
|
||||||
|
const key = possibleKeys[i]
|
||||||
try {
|
try {
|
||||||
return decryptObject(data, encryptionKey)
|
const decrypted = decryptObject(data, key)
|
||||||
|
|
||||||
|
// Wenn mit altem Schlüssel entschlüsselt wurde, warnen und neu verschlüsseln
|
||||||
|
if (i > 0 && key !== encryptionKey) {
|
||||||
|
console.warn(`⚠️ Newsletter-Abonnenten wurden mit altem Schlüssel entschlüsselt. Automatische Neuverschlüsselung...`)
|
||||||
|
try {
|
||||||
|
await writeSubscribers(decrypted)
|
||||||
|
console.log('✅ Newsletter-Abonnenten 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decrypted
|
||||||
} catch (decryptError) {
|
} catch (decryptError) {
|
||||||
console.error('Fehler beim Entschlüsseln der Newsletter-Abonnenten:', decryptError)
|
lastError = decryptError
|
||||||
|
// Versuche nächsten Schlüssel
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle Schlüssel fehlgeschlagen
|
||||||
|
console.error('Fehler beim Entschlüsseln der Newsletter-Abonnenten:')
|
||||||
|
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="<alter-schlüssel>"')
|
||||||
|
console.error(' Oder setzen Sie OLD_ENCRYPTION_KEY als Environment-Variable')
|
||||||
|
|
||||||
|
// Fallback: try to read as plain JSON
|
||||||
try {
|
try {
|
||||||
const plainData = JSON.parse(data)
|
const plainData = JSON.parse(data)
|
||||||
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
console.warn('Entschlüsselung fehlgeschlagen, versuche als unverschlüsseltes Format zu lesen')
|
||||||
@@ -58,7 +117,6 @@ export async function readSubscribers() {
|
|||||||
console.error('Konnte Newsletter-Abonnenten weder entschlüsseln noch als JSON lesen')
|
console.error('Konnte Newsletter-Abonnenten weder entschlüsseln noch als JSON lesen')
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Plain JSON - migriere zu verschlüsselter Speicherung
|
// Plain JSON - migriere zu verschlüsselter Speicherung
|
||||||
const subscribers = JSON.parse(data)
|
const subscribers = JSON.parse(data)
|
||||||
|
|||||||
Reference in New Issue
Block a user