Git history was lost when the previous Gitea instance was wiped and reinstalled due to an unresolved corruption bug — this commit is the last known-good file content, exported before the reinstall. Prior commit history is not recoverable through this path.
63 lines
2.3 KiB
TypeScript
63 lines
2.3 KiB
TypeScript
/**
|
|
* Rotates the AES-256-GCM master key used to encrypt stored mailbox
|
|
* credentials. Decrypts every stored IMAP/SMTP/CalDAV password under the
|
|
* old key and re-encrypts under a newly generated one, in a single
|
|
* transaction, then prints the new key so it can be written into
|
|
* /etc/ai-chief-of-staff/secrets.env.
|
|
*
|
|
* Usage: OLD_KEY=<base64> npx tsx scripts/rotate-credential-key.ts
|
|
* (reads the current key from CREDENTIALS_ENCRYPTION_KEY if OLD_KEY is unset)
|
|
*/
|
|
import crypto from "node:crypto";
|
|
import Database from "better-sqlite3";
|
|
import path from "node:path";
|
|
import { rotateCredential } from "../src/lib/crypto/credentials";
|
|
|
|
const dataDir = process.env.DATA_DIR ?? path.join(process.cwd(), "data");
|
|
const oldKeyB64 = process.env.OLD_KEY ?? process.env.CREDENTIALS_ENCRYPTION_KEY;
|
|
|
|
if (!oldKeyB64) {
|
|
console.error("Set OLD_KEY (or CREDENTIALS_ENCRYPTION_KEY) to the current base64-encoded key before running.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const oldKey = Buffer.from(oldKeyB64, "base64");
|
|
if (oldKey.length !== 32) {
|
|
console.error("OLD_KEY must decode to exactly 32 bytes.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const newKey = crypto.randomBytes(32);
|
|
|
|
const sqlite = new Database(path.join(dataDir, "db.sqlite"));
|
|
|
|
const rows = sqlite
|
|
.prepare(
|
|
"SELECT id, imap_password_enc, smtp_password_enc, caldav_password_enc FROM mailbox_credentials",
|
|
)
|
|
.all() as Array<{
|
|
id: string;
|
|
imap_password_enc: string | null;
|
|
smtp_password_enc: string | null;
|
|
caldav_password_enc: string | null;
|
|
}>;
|
|
|
|
const update = sqlite.prepare(
|
|
"UPDATE mailbox_credentials SET imap_password_enc = ?, smtp_password_enc = ?, caldav_password_enc = ? WHERE id = ?",
|
|
);
|
|
|
|
const rotateAll = sqlite.transaction(() => {
|
|
for (const row of rows) {
|
|
const imap = row.imap_password_enc ? rotateCredential(row.imap_password_enc, oldKey, newKey) : null;
|
|
const smtp = row.smtp_password_enc ? rotateCredential(row.smtp_password_enc, oldKey, newKey) : null;
|
|
const caldav = row.caldav_password_enc ? rotateCredential(row.caldav_password_enc, oldKey, newKey) : null;
|
|
update.run(imap, smtp, caldav, row.id);
|
|
}
|
|
});
|
|
|
|
rotateAll();
|
|
|
|
console.log(`Rotated ${rows.length} mailbox_credentials row(s).`);
|
|
console.log("\nNew key (update CREDENTIALS_ENCRYPTION_KEY in /etc/ai-chief-of-staff/secrets.env, then restart the service):\n");
|
|
console.log(newKey.toString("base64"));
|