Files
property-management-network/lib/crypto.ts
T
Leon SerfatyandClaude Opus 4.8 c9968531e4 Consolidate audit-fixes branch: webhooks, integrations, and deploy hardening
Batch commit of the pending working tree on security/audit-fixes-2026-07.
Major areas:
- Outbound webhooks / Zapier: schema + signed delivery with retries, public
  v1 API (REST-hook subscribe/unsubscribe), settings UI, cron drain.
- Deploy hardening: email via SMTP2GO (Resend fully removed), verified DB TLS
  (DATABASE_SSL=require + DATABASE_CA), storage fails loud in production when
  Spaces is unconfigured instead of silently using ephemeral disk.
- Integrations & features (concurrent work): accounting (QuickBooks/Xero),
  e-signature (DocuSign/Dropbox Sign), PayPal, geocoding/maps, onboarding,
  expanded legal pages.
- DB migrations 0006–0009.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:42:34 -04:00

28 lines
1.3 KiB
TypeScript

import crypto from "crypto"
// AES-256-GCM encryption for secrets at rest (OAuth tokens). The key is derived
// from ACCOUNTING_ENCRYPTION_KEY, falling back to BETTER_AUTH_SECRET, via SHA-256
// so no additional configuration is required.
function getKey(): Buffer {
const secret = process.env.ACCOUNTING_ENCRYPTION_KEY || process.env.BETTER_AUTH_SECRET
if (!secret) throw new Error("No encryption key configured (BETTER_AUTH_SECRET)")
return crypto.createHash("sha256").update(secret).digest()
}
/** Encrypt a UTF-8 string → "iv:tag:ciphertext" (all base64). */
export function encrypt(plaintext: string): string {
const iv = crypto.randomBytes(12)
const cipher = crypto.createCipheriv("aes-256-gcm", getKey(), iv)
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()])
const tag = cipher.getAuthTag()
return [iv.toString("base64"), tag.toString("base64"), enc.toString("base64")].join(":")
}
/** Decrypt a value produced by encrypt(). */
export function decrypt(payload: string): string {
const [ivB64, tagB64, dataB64] = payload.split(":")
const decipher = crypto.createDecipheriv("aes-256-gcm", getKey(), Buffer.from(ivB64, "base64"))
decipher.setAuthTag(Buffer.from(tagB64, "base64"))
return Buffer.concat([decipher.update(Buffer.from(dataB64, "base64")), decipher.final()]).toString("utf8")
}