Files
pcrt-legal-return/app/app/lib/crypto.server.ts
tommaso 3bb7a30c1d SMTP per-shop configurabile dalle Impostazioni (password cifrata AES-256-GCM)
- Settings: smtpHost/Port/User/Pass(cifrata)/Secure/From. Vuoto -> provider
  default dell'app (env); compilato -> invio dal SMTP del merchant.
- crypto.server: encrypt/decrypt AES-256-GCM (chiave APP_ENCRYPTION_KEY).
- mailer: SmtpConfig + buildTransport(smtp) per-shop o env; mailFrom override.
  Sia ricevuta cliente sia notifica merchant usano lo SMTP del merchant.
- proxy: costruisce SmtpConfig (decifra pass), passa ai due invii.
- admin: nuovo tab 'Email (SMTP)'; password mai ri-mostrata (vuoto = invariata).
- Migrazione smtp_settings. APP_ENCRYPTION_KEY: dev in .env, prod = fly secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mv83a29B4eFv5ixoj6PoE1
2026-07-07 17:29:33 +02:00

48 lines
1.6 KiB
TypeScript

/**
* Cifratura simmetrica per segreti a riposo (es. password SMTP per-shop).
* AES-256-GCM. Chiave da env APP_ENCRYPTION_KEY (qualsiasi lunghezza: derivata
* a 32 byte via SHA-256). In prod = `fly secrets set APP_ENCRYPTION_KEY=...`.
*/
import {
createCipheriv,
createDecipheriv,
createHash,
randomBytes,
} from "node:crypto";
const PREFIX = "enc:v1:";
function key(): Buffer {
const raw = process.env.APP_ENCRYPTION_KEY;
if (!raw || raw.length < 16) {
throw new Error("APP_ENCRYPTION_KEY mancante o troppo corta (>=16 char)");
}
return createHash("sha256").update(raw).digest();
}
/** Cifra -> "enc:v1:<iv>:<tag>:<data>" (base64). */
export function encryptSecret(plain: string): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key(), iv);
const enc = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return `${PREFIX}${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
}
/** Decifra; se non e' nel formato cifrato, ritorna il valore invariato. */
export function decryptSecret(value: string): string {
if (!value.startsWith(PREFIX)) return value;
const parts = value.split(":");
if (parts.length !== 5) return "";
const iv = Buffer.from(parts[2]!, "base64");
const tag = Buffer.from(parts[3]!, "base64");
const data = Buffer.from(parts[4]!, "base64");
const decipher = createDecipheriv("aes-256-gcm", key(), iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(data), decipher.final()]).toString(
"utf8",
);
}
export const SECRET_PREFIX = PREFIX;