Files
tickettracker/backend/src/secrets.ts
T
2026-09-16 05:30:23 +00:00

73 lines
2.1 KiB
TypeScript

import crypto from "node:crypto";
const encryptedSecretPrefix = "ttenc:v1:";
let warnedAboutFallbackKey = false;
let cachedKey: Buffer | null = null;
function getEncryptionMaterial() {
const configuredKey = process.env.SECRET_ENCRYPTION_KEY ?? process.env.TICKETTRACKER_SECRET_KEY;
if (configuredKey) {
return configuredKey;
}
if (!process.env.DATABASE_URL) {
throw new Error("SECRET_ENCRYPTION_KEY is required to encrypt database secrets");
}
if (!warnedAboutFallbackKey) {
console.warn("SECRET_ENCRYPTION_KEY is not set; deriving database secret encryption from DATABASE_URL.");
warnedAboutFallbackKey = true;
}
return process.env.DATABASE_URL;
}
function getEncryptionKey() {
if (!cachedKey) {
cachedKey = crypto.createHash("sha256").update(getEncryptionMaterial()).digest();
}
return cachedKey;
}
export function isEncryptedSecret(value: string | null | undefined) {
return typeof value === "string" && value.startsWith(encryptedSecretPrefix);
}
export function encryptSecret(value: string) {
if (isEncryptedSecret(value)) {
return value;
}
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv("aes-256-gcm", getEncryptionKey(), iv);
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
const tag = cipher.getAuthTag();
return `${encryptedSecretPrefix}${iv.toString("base64url")}.${tag.toString("base64url")}.${encrypted.toString("base64url")}`;
}
export function decryptSecret(value: string | null | undefined) {
if (!value) {
return null;
}
if (!isEncryptedSecret(value)) {
return value;
}
const parts = value.slice(encryptedSecretPrefix.length).split(".");
if (parts.length !== 3) {
throw new Error("Encrypted database secret has an invalid format");
}
const [ivText, tagText, encryptedText] = parts;
const decipher = crypto.createDecipheriv("aes-256-gcm", getEncryptionKey(), Buffer.from(ivText, "base64url"));
decipher.setAuthTag(Buffer.from(tagText, "base64url"));
return Buffer.concat([decipher.update(Buffer.from(encryptedText, "base64url")), decipher.final()]).toString("utf8");
}