Files
lunarfront-app/packages/backend/src/utils/encryption.ts
Ryan Moon b8e39369f1
Some checks failed
Build & Release / build (push) Failing after 35s
feat: add app settings table, encryption utility, and generic email service
- app_settings table with encrypted field support (AES-256-GCM, key from ENCRYPTION_KEY env)
- SettingsService for transparent encrypt/decrypt on get/set
- EmailService factory with Resend and SendGrid providers (SMTP stub) — provider config lives in app_settings
- Seeds initial admin user and email settings from env vars on first startup if not already present
- Migration 0039_app_settings.sql

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 10:27:20 -05:00

35 lines
1.2 KiB
TypeScript

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'
const ALGORITHM = 'aes-256-gcm'
function getKey(): Buffer {
const hex = process.env.ENCRYPTION_KEY
if (!hex) throw new Error('ENCRYPTION_KEY env var is required for encrypted settings')
const key = Buffer.from(hex, 'hex')
if (key.length !== 32) throw new Error('ENCRYPTION_KEY must be a 64-character hex string (32 bytes)')
return key
}
export function encrypt(plaintext: string): { ciphertext: string; iv: string } {
const key = getKey()
const iv = randomBytes(12)
const cipher = createCipheriv(ALGORITHM, key, iv)
let encrypted = cipher.update(plaintext, 'utf8', 'hex')
encrypted += cipher.final('hex')
const authTag = cipher.getAuthTag().toString('hex')
return {
ciphertext: `${encrypted}:${authTag}`,
iv: iv.toString('hex'),
}
}
export function decrypt(ciphertext: string, iv: string): string {
const key = getKey()
const [encrypted, authTag] = ciphertext.split(':')
const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(iv, 'hex'))
decipher.setAuthTag(Buffer.from(authTag, 'hex'))
let decrypted = decipher.update(encrypted, 'hex', 'utf8')
decrypted += decipher.final('utf8')
return decrypted
}