You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
54 lines
2.5 KiB
TypeScript
54 lines
2.5 KiB
TypeScript
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<ClearResult> {
|
|
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
|
|
}
|