import type { DB } from './db' import { isLegacyCiphertext } from './crypto' import { writeAudit } from './audit' /** * D32 keyless cleanup: HQ_SECRET_KEY was removed, so any credential still stored as a * pre-D31 AES-256-GCM payload is now unreadable dead weight. This NULLs those values so * the affected login shows as "not set" and is ready to be re-populated in plaintext * (re-import from APEX, or re-entered). Idempotent — plaintext rows are left untouched. * Writes ONE audit summary. No key required. */ export interface ClearResult { modulePasswords: number; moduleSecrets: number; clientDbPasswords: number; gmailTokens: number } export async function clearLegacyCiphertext(db: DB): Promise { const res: ClearResult = { modulePasswords: 0, moduleSecrets: 0, clientDbPasswords: 0, gmailTokens: 0 } for (const r of await db.all<{ id: string; password_enc: string }>( `SELECT id, password_enc FROM client_module WHERE password_enc IS NOT NULL`)) { if (!isLegacyCiphertext(r.password_enc)) continue await db.run(`UPDATE client_module SET password_enc=NULL WHERE id=?`, r.id) res.modulePasswords++ } // secrets_enc is `keylist|ciphertext`; if the ciphertext is legacy, the whole blob is dead. for (const r of await db.all<{ id: string; secrets_enc: string }>( `SELECT id, secrets_enc FROM client_module WHERE secrets_enc IS NOT NULL`)) { const sep = r.secrets_enc.indexOf('|') const cipher = sep < 0 ? r.secrets_enc : r.secrets_enc.slice(sep + 1) if (!isLegacyCiphertext(cipher)) continue await db.run(`UPDATE client_module SET secrets_enc=NULL WHERE id=?`, r.id) res.moduleSecrets++ } for (const r of await db.all<{ id: string; db_password_enc: string }>( `SELECT id, db_password_enc FROM client WHERE db_password_enc IS NOT NULL`)) { if (!isLegacyCiphertext(r.db_password_enc)) continue await db.run(`UPDATE client SET db_password_enc=NULL WHERE id=?`, r.id) res.clientDbPasswords++ } // A dead Gmail token means Gmail must be reconnected — remove the unreadable account row. for (const r of await db.all<{ id: string; refresh_token_enc: string }>( `SELECT id, refresh_token_enc FROM email_account WHERE refresh_token_enc IS NOT NULL`)) { if (!isLegacyCiphertext(r.refresh_token_enc)) continue await db.run(`DELETE FROM email_account WHERE id=?`, r.id) res.gmailTokens++ } await writeAudit(db, 'system', 'clear_legacy_credentials', 'system', 'crypto', undefined, { ...res }) return res }