cms.c2sgmbh/src/lib/envValidation.ts
Martin Porwoll 19fcb4d837 feat: implement multi-tenant email system with logging
- Add Payload email adapter for system emails (auth, password reset)
- Add EmailLogs collection for tracking all sent emails
- Extend Tenants collection with SMTP configuration fields
- Implement tenant-specific email service with transporter caching
- Add /api/send-email endpoint with:
  - Authentication required
  - Tenant access control (users can only send for their tenants)
  - Rate limiting (10 emails/minute per user)
- Add form submission notification hook with email logging
- Add cache invalidation hook for tenant email config changes

Security:
- SMTP passwords are never returned in API responses
- Passwords are preserved when field is left empty on update
- Only super admins can delete email logs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 20:16:54 +00:00

96 lines
2.6 KiB
TypeScript

// src/lib/envValidation.ts
/**
* Zentrale Validierung aller erforderlichen Environment-Variablen.
* Wird beim Server-Start aufgerufen und beendet den Prozess bei fehlenden Werten.
*/
interface RequiredEnvVars {
PAYLOAD_SECRET: string
DATABASE_URI: string
CONSENT_LOGGING_API_KEY: string
IP_ANONYMIZATION_PEPPER: string
}
// Optionale SMTP-Konfiguration (Fallback für Tenants ohne eigene SMTP-Config)
export interface SmtpEnvVars {
SMTP_HOST?: string
SMTP_PORT?: string
SMTP_SECURE?: string
SMTP_USER?: string
SMTP_PASS?: string
SMTP_FROM_ADDRESS?: string
SMTP_FROM_NAME?: string
}
export function getSmtpConfig(): SmtpEnvVars {
return {
SMTP_HOST: process.env.SMTP_HOST,
SMTP_PORT: process.env.SMTP_PORT,
SMTP_SECURE: process.env.SMTP_SECURE,
SMTP_USER: process.env.SMTP_USER,
SMTP_PASS: process.env.SMTP_PASS,
SMTP_FROM_ADDRESS: process.env.SMTP_FROM_ADDRESS,
SMTP_FROM_NAME: process.env.SMTP_FROM_NAME,
}
}
const FORBIDDEN_VALUES = [
'',
'default-pepper-change-me',
'change-me',
'your-secret-here',
'xxx',
]
function validateEnvVar(name: string, value: string | undefined): string {
if (!value || value.trim() === '') {
throw new Error(
`FATAL: Environment variable ${name} is required but not set. ` +
`Server cannot start without this value.`,
)
}
if (FORBIDDEN_VALUES.includes(value.trim().toLowerCase())) {
throw new Error(
`FATAL: Environment variable ${name} has an insecure default value. ` +
`Please set a secure random value.`,
)
}
return value.trim()
}
/**
* Validiert alle erforderlichen Environment-Variablen.
* Wirft einen Fehler und beendet den Server-Start, wenn Variablen fehlen.
*/
export function validateRequiredEnvVars(): RequiredEnvVars {
return {
PAYLOAD_SECRET: validateEnvVar('PAYLOAD_SECRET', process.env.PAYLOAD_SECRET),
DATABASE_URI: validateEnvVar('DATABASE_URI', process.env.DATABASE_URI),
CONSENT_LOGGING_API_KEY: validateEnvVar(
'CONSENT_LOGGING_API_KEY',
process.env.CONSENT_LOGGING_API_KEY,
),
IP_ANONYMIZATION_PEPPER: validateEnvVar(
'IP_ANONYMIZATION_PEPPER',
process.env.IP_ANONYMIZATION_PEPPER,
),
}
}
/**
* Lazy-initialized Environment-Variablen.
* Wird erst beim ersten Zugriff validiert (vermeidet Build-Probleme).
*/
let _cachedEnv: RequiredEnvVars | null = null
export const env: RequiredEnvVars = new Proxy({} as RequiredEnvVars, {
get(_, prop: keyof RequiredEnvVars) {
if (!_cachedEnv) {
_cachedEnv = validateRequiredEnvVars()
}
return _cachedEnv[prop]
},
})