Files
harheimertc/server/utils/members.js

142 lines
4.2 KiB
JavaScript

import { promises as fs } from 'fs'
import path from 'path'
import { randomUUID } from 'crypto'
import { encrypt, decrypt, encryptObject, decryptObject } from './encryption.js'
// 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 MEMBERS_FILE = getDataPath('members.json')
// Get encryption key from environment or config
function getEncryptionKey() {
return process.env.ENCRYPTION_KEY || 'default-key-change-in-production'
}
// Check if data is encrypted by trying to parse as JSON first
function isEncrypted(data) {
try {
// Try to parse as JSON - if successful and looks like member data, it's unencrypted
const parsed = JSON.parse(data.trim())
// If it's an array (members list) or object with member-like structure, it's unencrypted
if (Array.isArray(parsed)) {
return false // Unencrypted array
}
// If it's an object but not encrypted format, it's unencrypted
if (typeof parsed === 'object' && parsed !== null && !parsed.encryptedData) {
return false
}
return false
} catch (e) {
// JSON parsing failed - likely encrypted base64
return true
}
}
// Read manual members from file (with encryption support and migration)
export async function readMembers() {
try {
const data = await fs.readFile(MEMBERS_FILE, 'utf-8')
// Check if data is encrypted or plain JSON
const encrypted = isEncrypted(data)
if (encrypted) {
// Decrypt and parse
const encryptionKey = getEncryptionKey()
try {
return decryptObject(data, encryptionKey)
} catch (decryptError) {
console.error('Fehler beim Entschlüsseln der Mitgliederdaten:', decryptError)
// Fallback: try to read as plain JSON (migration scenario)
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 Mitgliederdaten weder entschlüsseln noch als JSON lesen')
return []
}
}
} else {
// Plain JSON - migrate to encrypted format
const members = JSON.parse(data)
console.log('Migriere unverschlüsselte Mitgliederdaten zu verschlüsselter Speicherung...')
// Write back encrypted
await writeMembers(members)
return members
}
} catch (error) {
if (error.code === 'ENOENT') {
return []
}
console.error('Fehler beim Lesen der Mitgliederdaten:', error)
return []
}
}
// Write manual members to file (always encrypted)
export async function writeMembers(members) {
try {
const encryptionKey = getEncryptionKey()
const encryptedData = encryptObject(members, encryptionKey)
await fs.writeFile(MEMBERS_FILE, encryptedData, 'utf-8')
return true
} catch (error) {
console.error('Fehler beim Schreiben der Mitgliederdaten:', error)
return false
}
}
// Get member by ID
export async function getMemberById(id) {
const members = await readMembers()
return members.find(m => m.id === id)
}
// Add or update manual member
export async function saveMember(memberData) {
const members = await readMembers()
if (memberData.id) {
// Update existing
const index = members.findIndex(m => m.id === memberData.id)
if (index !== -1) {
members[index] = { ...members[index], ...memberData }
} else {
throw new Error('Mitglied nicht gefunden')
}
} else {
// Add new - use UUID for guaranteed uniqueness
const newMember = {
...memberData,
id: randomUUID() // Cryptographically secure unique ID
}
members.push(newMember)
}
await writeMembers(members)
return true
}
// Delete manual member
export async function deleteMember(id) {
const members = await readMembers()
const filtered = members.filter(m => m.id !== id)
await writeMembers(filtered)
return true
}