Protected profiles: recovery email and Discord contacts with reset codes (Gmail SMTP, Discord relay)

This commit is contained in:
Jonathan Sykes
2026-10-15 17:28:10 +08:00
parent dbcee5d5f8
commit ca47528581
8 changed files with 477 additions and 6 deletions

View File

@@ -316,7 +316,7 @@ export async function initDb() {
)`);
// Optional profile protection: a password or generated key, stored only as a
// slow salted hash (Bun.password / argon2id). NULL = the name alone opens it.
for (const col of ['secret_hash TEXT', 'secret_kind TEXT']) {
for (const col of ['secret_hash TEXT', 'secret_kind TEXT', 'recovery_email TEXT', 'recovery_discord TEXT']) {
try { await db.execute(`ALTER TABLE profiles ADD COLUMN ${col}`); } catch { /* already there */ }
}
}
@@ -763,12 +763,13 @@ export async function createProfile(name, dataJson) {
export async function getProfile(name) {
const r = await db.execute({
sql: 'SELECT data, updated_at, secret_hash, secret_kind FROM profiles WHERE name = ?',
sql: 'SELECT data, updated_at, secret_hash, secret_kind, recovery_email, recovery_discord FROM profiles WHERE name = ?',
args: [name],
});
const row = r.rows[0];
if (!row) return null;
return { data: row.data, updatedAt: Number(row.updated_at), secretHash: row.secret_hash || null, secretKind: row.secret_kind || null };
return { data: row.data, updatedAt: Number(row.updated_at), secretHash: row.secret_hash || null, secretKind: row.secret_kind || null,
recoveryEmail: row.recovery_email || null, recoveryDiscord: row.recovery_discord || null };
}
export async function setProfileSecret(name, hash, kind) {