Some checks failed
Build & Release / build (push) Failing after 35s
- 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>
35 lines
1.2 KiB
TypeScript
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
|
|
}
|