Add dotenv package for environment variable management and refactor SMTP credential handling in email services. Enhance error handling for missing SMTP credentials across various API endpoints to improve reliability and maintainability.
This commit is contained in:
1
package-lock.json
generated
1
package-lock.json
generated
@@ -24,6 +24,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.11.0",
|
"@nuxtjs/tailwindcss": "^6.11.0",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"lucide-vue-next": "^0.344.0",
|
"lucide-vue-next": "^0.344.0",
|
||||||
"postcss": "^8.4.0",
|
"postcss": "^8.4.0",
|
||||||
"supertest": "^7.1.0",
|
"supertest": "^7.1.0",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxtjs/tailwindcss": "^6.11.0",
|
"@nuxtjs/tailwindcss": "^6.11.0",
|
||||||
"autoprefixer": "^10.4.0",
|
"autoprefixer": "^10.4.0",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
"lucide-vue-next": "^0.344.0",
|
"lucide-vue-next": "^0.344.0",
|
||||||
"postcss": "^8.4.0",
|
"postcss": "^8.4.0",
|
||||||
"supertest": "^7.1.0",
|
"supertest": "^7.1.0",
|
||||||
|
|||||||
66
scripts/README-re-encrypt.md
Normal file
66
scripts/README-re-encrypt.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Daten-Neuverschlüsselung
|
||||||
|
|
||||||
|
Dieses Script verschlüsselt alle verschlüsselten Daten mit einem neuen Schlüssel neu.
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
1. Stelle sicher, dass `ENCRYPTION_KEY` in der `.env` Datei gesetzt ist:
|
||||||
|
```bash
|
||||||
|
ENCRYPTION_KEY=dein-neuer-sicherer-schlüssel-hier
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Optional: Wenn du einen spezifischen alten Schlüssel verwendest (nicht den Standard), kannst du ihn angeben:
|
||||||
|
```bash
|
||||||
|
node scripts/re-encrypt-data.js --old-key="dein-alter-schlüssel"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ausführung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node scripts/re-encrypt-data.js
|
||||||
|
```
|
||||||
|
|
||||||
|
## Was wird neu verschlüsselt?
|
||||||
|
|
||||||
|
Das Script verarbeitet folgende Dateien:
|
||||||
|
|
||||||
|
1. **`server/data/users.json`** - Benutzerdaten
|
||||||
|
2. **`server/data/members.json`** - Mitgliederdaten
|
||||||
|
3. **`server/data/membership-applications/*.json`** - Mitgliedschaftsanträge (wenn sie ein `encryptedData` Feld enthalten)
|
||||||
|
4. **`server/data/membership-applications/*.data`** - Verschlüsselte Antragsdaten
|
||||||
|
|
||||||
|
## Sicherheit
|
||||||
|
|
||||||
|
- **Automatische Backups**: Vor jeder Änderung werden Backups im Verzeichnis `backups/re-encrypt-<timestamp>/` erstellt
|
||||||
|
- **Mehrere Schlüssel**: Das Script versucht automatisch verschiedene alte Standard-Schlüssel:
|
||||||
|
- `default-key-change-in-production`
|
||||||
|
- `local_development_encryption_key_change_in_production`
|
||||||
|
- Ein optional angegebener `--old-key` Parameter
|
||||||
|
|
||||||
|
## Beispiel
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Standard-Ausführung (verwendet Standard-Entwicklungsschlüssel)
|
||||||
|
node scripts/re-encrypt-data.js
|
||||||
|
|
||||||
|
# Mit spezifischem altem Schlüssel
|
||||||
|
node scripts/re-encrypt-data.js --old-key="mein-alter-produktions-schlüssel"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
|
||||||
|
Falls die Entschlüsselung mit allen verfügbaren Schlüsseln fehlschlägt:
|
||||||
|
- Das Script bricht ab
|
||||||
|
- Alle Backups bleiben erhalten
|
||||||
|
- Du kannst die Dateien manuell aus den Backups wiederherstellen
|
||||||
|
|
||||||
|
## Wichtig
|
||||||
|
|
||||||
|
⚠️ **WICHTIG**: Stelle sicher, dass:
|
||||||
|
- Die `.env` Datei den neuen `ENCRYPTION_KEY` enthält
|
||||||
|
- Du ein Backup der Daten hast (wird automatisch erstellt)
|
||||||
|
- Der Server während der Ausführung nicht läuft
|
||||||
|
- Du nach der Ausführung testest, ob alles funktioniert
|
||||||
|
|
||||||
282
scripts/re-encrypt-data.js
Executable file
282
scripts/re-encrypt-data.js
Executable file
@@ -0,0 +1,282 @@
|
|||||||
|
#!/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)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -53,47 +53,56 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
// Send notification email to admin
|
// Send notification email to admin
|
||||||
try {
|
try {
|
||||||
const transporter = nodemailer.createTransport({
|
const smtpUser = process.env.SMTP_USER
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
const smtpPass = process.env.SMTP_PASS
|
||||||
port: process.env.SMTP_PORT || 587,
|
|
||||||
secure: false,
|
|
||||||
auth: {
|
|
||||||
user: process.env.SMTP_USER,
|
|
||||||
pass: process.env.SMTP_PASS
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Email to admin
|
if (!smtpUser || !smtpPass) {
|
||||||
await transporter.sendMail({
|
console.warn('SMTP-Credentials fehlen! E-Mail-Versand wird übersprungen.')
|
||||||
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
console.warn(`SMTP_USER=${smtpUser ? 'gesetzt' : 'FEHLT'}, SMTP_PASS=${smtpPass ? 'gesetzt' : 'FEHLT'}`)
|
||||||
to: process.env.SMTP_ADMIN || 'j.dichmann@gmx.de',
|
// Continue without sending email
|
||||||
subject: 'Neue Registrierung - Harheimer TC',
|
} else {
|
||||||
html: `
|
const transporter = nodemailer.createTransport({
|
||||||
<h2>Neue Registrierung</h2>
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
<p>Ein neuer Benutzer hat sich registriert und wartet auf Freigabe:</p>
|
port: process.env.SMTP_PORT || 587,
|
||||||
<ul>
|
secure: false,
|
||||||
<li><strong>Name:</strong> ${name}</li>
|
auth: {
|
||||||
<li><strong>E-Mail:</strong> ${email}</li>
|
user: smtpUser,
|
||||||
<li><strong>Telefon:</strong> ${phone || 'Nicht angegeben'}</li>
|
pass: smtpPass
|
||||||
</ul>
|
}
|
||||||
<p>Bitte prüfen Sie die Registrierung im CMS.</p>
|
})
|
||||||
`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Email to user
|
// Email to admin
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
||||||
to: email,
|
to: process.env.SMTP_ADMIN || 'j.dichmann@gmx.de',
|
||||||
subject: 'Registrierung erhalten - Harheimer TC',
|
subject: 'Neue Registrierung - Harheimer TC',
|
||||||
html: `
|
html: `
|
||||||
<h2>Registrierung erhalten</h2>
|
<h2>Neue Registrierung</h2>
|
||||||
<p>Hallo ${name},</p>
|
<p>Ein neuer Benutzer hat sich registriert und wartet auf Freigabe:</p>
|
||||||
<p>vielen Dank für Ihre Registrierung beim Harheimer TC!</p>
|
<ul>
|
||||||
<p>Ihre Anfrage wird vom Vorstand geprüft. Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.</p>
|
<li><strong>Name:</strong> ${name}</li>
|
||||||
<br>
|
<li><strong>E-Mail:</strong> ${email}</li>
|
||||||
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
<li><strong>Telefon:</strong> ${phone || 'Nicht angegeben'}</li>
|
||||||
`
|
</ul>
|
||||||
})
|
<p>Bitte prüfen Sie die Registrierung im CMS.</p>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Email to user
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
||||||
|
to: email,
|
||||||
|
subject: 'Registrierung erhalten - Harheimer TC',
|
||||||
|
html: `
|
||||||
|
<h2>Registrierung erhalten</h2>
|
||||||
|
<p>Hallo ${name},</p>
|
||||||
|
<p>vielen Dank für Ihre Registrierung beim Harheimer TC!</p>
|
||||||
|
<p>Ihre Anfrage wird vom Vorstand geprüft. Sie erhalten eine E-Mail, sobald Ihr Zugang freigeschaltet wurde.</p>
|
||||||
|
<br>
|
||||||
|
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('E-Mail-Versand fehlgeschlagen:', emailError)
|
console.error('E-Mail-Versand fehlgeschlagen:', emailError)
|
||||||
// Continue anyway - user is registered
|
// Continue anyway - user is registered
|
||||||
|
|||||||
@@ -37,35 +37,44 @@ export default defineEventHandler(async (event) => {
|
|||||||
await writeUsers(updatedUsers)
|
await writeUsers(updatedUsers)
|
||||||
|
|
||||||
// Send email with temporary password
|
// Send email with temporary password
|
||||||
const transporter = nodemailer.createTransport({
|
const smtpUser = process.env.SMTP_USER
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
const smtpPass = process.env.SMTP_PASS
|
||||||
port: process.env.SMTP_PORT || 587,
|
|
||||||
secure: false,
|
if (!smtpUser || !smtpPass) {
|
||||||
auth: {
|
console.warn('SMTP-Credentials fehlen! E-Mail-Versand wird übersprungen.')
|
||||||
user: process.env.SMTP_USER,
|
console.warn(`SMTP_USER=${smtpUser ? 'gesetzt' : 'FEHLT'}, SMTP_PASS=${smtpPass ? 'gesetzt' : 'FEHLT'}`)
|
||||||
pass: process.env.SMTP_PASS
|
// Continue without sending email - security: don't reveal if email exists
|
||||||
|
} else {
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
|
port: process.env.SMTP_PORT || 587,
|
||||||
|
secure: false,
|
||||||
|
auth: {
|
||||||
|
user: smtpUser,
|
||||||
|
pass: smtpPass
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mailOptions = {
|
||||||
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
||||||
|
to: user.email,
|
||||||
|
subject: 'Passwort zurücksetzen - Harheimer TC',
|
||||||
|
html: `
|
||||||
|
<h2>Passwort zurücksetzen</h2>
|
||||||
|
<p>Hallo ${user.name},</p>
|
||||||
|
<p>Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.</p>
|
||||||
|
<p>Ihr temporäres Passwort lautet: <strong>${tempPassword}</strong></p>
|
||||||
|
<p>Bitte melden Sie sich damit an und ändern Sie Ihr Passwort im Mitgliederbereich.</p>
|
||||||
|
<br>
|
||||||
|
<p>Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.</p>
|
||||||
|
<br>
|
||||||
|
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
||||||
|
`
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
const mailOptions = {
|
await transporter.sendMail(mailOptions)
|
||||||
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
|
||||||
to: user.email,
|
|
||||||
subject: 'Passwort zurücksetzen - Harheimer TC',
|
|
||||||
html: `
|
|
||||||
<h2>Passwort zurücksetzen</h2>
|
|
||||||
<p>Hallo ${user.name},</p>
|
|
||||||
<p>Sie haben eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.</p>
|
|
||||||
<p>Ihr temporäres Passwort lautet: <strong>${tempPassword}</strong></p>
|
|
||||||
<p>Bitte melden Sie sich damit an und ändern Sie Ihr Passwort im Mitgliederbereich.</p>
|
|
||||||
<br>
|
|
||||||
<p>Falls Sie diese Anfrage nicht gestellt haben, ignorieren Sie diese E-Mail.</p>
|
|
||||||
<br>
|
|
||||||
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
|
||||||
`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await transporter.sendMail(mailOptions)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.'
|
message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.'
|
||||||
|
|||||||
@@ -35,30 +35,39 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
// Send approval email
|
// Send approval email
|
||||||
try {
|
try {
|
||||||
const transporter = nodemailer.createTransport({
|
const smtpUser = process.env.SMTP_USER
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
const smtpPass = process.env.SMTP_PASS
|
||||||
port: process.env.SMTP_PORT || 587,
|
|
||||||
secure: false,
|
|
||||||
auth: {
|
|
||||||
user: process.env.SMTP_USER,
|
|
||||||
pass: process.env.SMTP_PASS
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
await transporter.sendMail({
|
if (!smtpUser || !smtpPass) {
|
||||||
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
console.warn('SMTP-Credentials fehlen! E-Mail-Versand wird übersprungen.')
|
||||||
to: user.email,
|
console.warn(`SMTP_USER=${smtpUser ? 'gesetzt' : 'FEHLT'}, SMTP_PASS=${smtpPass ? 'gesetzt' : 'FEHLT'}`)
|
||||||
subject: 'Zugang freigeschaltet - Harheimer TC',
|
// Continue without sending email
|
||||||
html: `
|
} else {
|
||||||
<h2>Zugang freigeschaltet</h2>
|
const transporter = nodemailer.createTransport({
|
||||||
<p>Hallo ${user.name},</p>
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
<p>Ihr Zugang zum Mitgliederbereich wurde freigeschaltet!</p>
|
port: process.env.SMTP_PORT || 587,
|
||||||
<p>Sie können sich jetzt mit Ihrer E-Mail-Adresse und Ihrem Passwort anmelden.</p>
|
secure: false,
|
||||||
<p><a href="https://harheimertc.tsschulz.de/login">Zum Login</a></p>
|
auth: {
|
||||||
<br>
|
user: smtpUser,
|
||||||
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
pass: smtpPass
|
||||||
`
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await transporter.sendMail({
|
||||||
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
||||||
|
to: user.email,
|
||||||
|
subject: 'Zugang freigeschaltet - Harheimer TC',
|
||||||
|
html: `
|
||||||
|
<h2>Zugang freigeschaltet</h2>
|
||||||
|
<p>Hallo ${user.name},</p>
|
||||||
|
<p>Ihr Zugang zum Mitgliederbereich wurde freigeschaltet!</p>
|
||||||
|
<p>Sie können sich jetzt mit Ihrer E-Mail-Adresse und Ihrem Passwort anmelden.</p>
|
||||||
|
<p><a href="https://harheimertc.tsschulz.de/login">Zum Login</a></p>
|
||||||
|
<br>
|
||||||
|
<p>Mit sportlichen Grüßen,<br>Ihr Harheimer TC</p>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('E-Mail-Versand fehlgeschlagen:', emailError)
|
console.error('E-Mail-Versand fehlgeschlagen:', emailError)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,23 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SMTP-Konfiguration (hier können Sie Ihre SMTP-Daten eintragen)
|
// SMTP-Konfiguration (hier können Sie Ihre SMTP-Daten eintragen)
|
||||||
|
const smtpUser = process.env.SMTP_USER || 'j.dichmann@gmx.de'
|
||||||
|
const smtpPass = process.env.SMTP_PASS || process.env.EMAIL_PASSWORD
|
||||||
|
|
||||||
|
if (!smtpUser || !smtpPass) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
statusMessage: 'SMTP-Credentials fehlen! Bitte setzen Sie SMTP_USER und SMTP_PASS in der .env Datei.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
const transporter = nodemailer.createTransport({
|
||||||
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
||||||
port: process.env.SMTP_PORT || 587,
|
port: process.env.SMTP_PORT || 587,
|
||||||
secure: false, // true für 465, false für andere Ports
|
secure: false, // true für 465, false für andere Ports
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.SMTP_USER || 'j.dichmann@gmx.de',
|
user: smtpUser,
|
||||||
pass: process.env.SMTP_PASS || process.env.EMAIL_PASSWORD
|
pass: smtpPass
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
// E-Mail senden
|
// E-Mail senden
|
||||||
emailResult = await sendMembershipEmail(data, filename, event)
|
emailResult = await sendMembershipEmail(data, filename, event)
|
||||||
// Antragsdaten verschlüsselt speichern
|
// Antragsdaten verschlüsselt speichern
|
||||||
const encryptionKey = process.env.ENCRYPTION_KEY || 'default-key-change-in-production'
|
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
||||||
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
|
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
|
||||||
const dataPath = path.join(uploadsDir, `${filename}.data`)
|
const dataPath = path.join(uploadsDir, `${filename}.data`)
|
||||||
await fs.writeFile(dataPath, encryptedData, 'utf8')
|
await fs.writeFile(dataPath, encryptedData, 'utf8')
|
||||||
@@ -730,7 +730,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
emailResult = await sendMembershipEmail(data, filename, event)
|
emailResult = await sendMembershipEmail(data, filename, event)
|
||||||
|
|
||||||
// Antragsdaten verschlüsselt speichern
|
// Antragsdaten verschlüsselt speichern
|
||||||
const encryptionKey = process.env.ENCRYPTION_KEY || 'default-key-change-in-production'
|
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
|
||||||
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
|
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
|
||||||
const dataPath = path.join(uploadsDir, `${filename}.data`)
|
const dataPath = path.join(uploadsDir, `${filename}.data`)
|
||||||
await fs.writeFile(dataPath, encryptedData, 'utf8')
|
await fs.writeFile(dataPath, encryptedData, 'utf8')
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const SESSIONS_FILE = getDataPath('sessions.json')
|
|||||||
|
|
||||||
// Get encryption key from environment
|
// Get encryption key from environment
|
||||||
function getEncryptionKey() {
|
function getEncryptionKey() {
|
||||||
return process.env.ENCRYPTION_KEY || 'default-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
|
// Check if data is encrypted by trying to parse as JSON first
|
||||||
|
|||||||
@@ -80,13 +80,23 @@ function getEmailRecipients(data, config) {
|
|||||||
* @returns {Object} Nodemailer transporter
|
* @returns {Object} Nodemailer transporter
|
||||||
*/
|
*/
|
||||||
function createTransporter() {
|
function createTransporter() {
|
||||||
|
const smtpUser = process.env.SMTP_USER
|
||||||
|
const smtpPass = process.env.SMTP_PASS
|
||||||
|
|
||||||
|
if (!smtpUser || !smtpPass) {
|
||||||
|
throw new Error(
|
||||||
|
'SMTP-Credentials fehlen! Bitte setzen Sie SMTP_USER und SMTP_PASS in der .env Datei.\n' +
|
||||||
|
`Aktuell: SMTP_USER=${smtpUser ? 'gesetzt' : 'FEHLT'}, SMTP_PASS=${smtpPass ? 'gesetzt' : 'FEHLT'}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return nodemailer.createTransporter({
|
return nodemailer.createTransporter({
|
||||||
host: process.env.SMTP_HOST || 'localhost',
|
host: process.env.SMTP_HOST || 'localhost',
|
||||||
port: parseInt(process.env.SMTP_PORT) || 587,
|
port: parseInt(process.env.SMTP_PORT) || 587,
|
||||||
secure: process.env.SMTP_SECURE === 'true',
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.SMTP_USER,
|
user: smtpUser,
|
||||||
pass: process.env.SMTP_PASS
|
pass: smtpPass
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const MEMBERS_FILE = getDataPath('members.json')
|
|||||||
|
|
||||||
// Get encryption key from environment or config
|
// Get encryption key from environment or config
|
||||||
function getEncryptionKey() {
|
function getEncryptionKey() {
|
||||||
return process.env.ENCRYPTION_KEY || 'default-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
|
// Check if data is encrypted by trying to parse as JSON first
|
||||||
|
|||||||
Reference in New Issue
Block a user