Update
This commit is contained in:
1 parent
312dd3b3ad
commit
4f4fb0baf2
11 files changed
+181
-38
No files matched your search
@@ -0,0 +1,72 @@
|
||||
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");
|
||||
}
|
||||
Reference in new issue
Block a user