Modified by www.SourceFiles.app

This commit is contained in:
2026-08-07 04:13:04 +00:00
parent 4041390ea7
commit 6fc382dc77
-55
View File
@@ -1,55 +0,0 @@
// Sealing for mailbox passwords. Server-only: never import from a route or a
// *.functions.ts top level, both of which ship to the client bundle.
//
// AES-256-GCM so the stored value is both encrypted and tamper-evident — a
// modified ciphertext fails the auth tag rather than decrypting to garbage that
// then gets sent to an IMAP server.
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
export type Sealed = { ciphertext: string; iv: string; tag: string };
const KEY_ENV = "MAIL_CRED_KEY";
// 32 bytes, supplied base64 or hex. Generate with:
// openssl rand -base64 32
function key(): Buffer {
const raw = process.env[KEY_ENV];
if (!raw) {
throw new Error(
`${KEY_ENV} is not set. Add a 32-byte key to .env.secret (openssl rand -base64 32) before using mail.`,
);
}
const buf = /^[0-9a-fA-F]{64}$/.test(raw.trim())
? Buffer.from(raw.trim(), "hex")
: Buffer.from(raw.trim(), "base64");
if (buf.length !== 32) {
throw new Error(`${KEY_ENV} must decode to exactly 32 bytes (got ${buf.length}).`);
}
return buf;
}
export function seal(plaintext: string): Sealed {
const iv = randomBytes(12); // 96-bit nonce, the GCM standard
const c = createCipheriv("aes-256-gcm", key(), iv);
const ct = Buffer.concat([c.update(plaintext, "utf8"), c.final()]);
return {
ciphertext: ct.toString("base64"),
iv: iv.toString("base64"),
tag: c.getAuthTag().toString("base64"),
};
}
export function open(s: Sealed): string {
const d = createDecipheriv("aes-256-gcm", key(), Buffer.from(s.iv, "base64"));
d.setAuthTag(Buffer.from(s.tag, "base64"));
return Buffer.concat([d.update(Buffer.from(s.ciphertext, "base64")), d.final()]).toString("utf8");
}
export function mailKeyConfigured(): boolean {
try {
key();
return true;
} catch {
return false;
}
}