homeio
PublikFork dari doctor-io/homeio
secrets.ts
import { createCipheriv, createDecipheriv, createHash, randomBytes } from "node:crypto";
type EncryptedSecret = {
ciphertext: string;
iv: string;
tag: string;
};
const KEY_BYTES = 32;
const IV_BYTES = 12;
function decodeBase64Key(keyBase64: string) {
try {
return Buffer.from(keyBase64, "base64");
} catch {
return null;
}
}
function resolveSecretKey() {
const fromEnv = process.env.FILES_CREDENTIALS_KEY;
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
const decoded = decodeBase64Key(fromEnv.trim());
if (decoded && decoded.length === KEY_BYTES) {
return decoded;
}
throw new Error("FILES_CREDENTIALS_KEY must be a base64-encoded 32-byte key");
}
const authSecret = process.env.AUTH_SESSION_SECRET || "dev-session-secret-change-me";
return createHash("sha256")
.update(`files-credentials:${authSecret}`)
.digest();
}
export function encryptSecret(plaintext: string): EncryptedSecret {
const key = resolveSecretKey();
const iv = randomBytes(IV_BYTES);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const ciphertext = Buffer.concat([
cipher.update(Buffer.from(plaintext, "utf8")),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return {
ciphertext: ciphertext.toString("base64"),
iv: iv.toString("base64"),
tag: tag.toString("base64"),
};
}
export function decryptSecret(input: EncryptedSecret) {
const key = resolveSecretKey();
const iv = Buffer.from(input.iv, "base64");
const tag = Buffer.from(input.tag, "base64");
const ciphertext = Buffer.from(input.ciphertext, "base64");
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]);
return plaintext.toString("utf8");
}