feat: account deletion, username reuse, and real-time username availability

- Add account deletion with tombstone (isDeleted flag), password/username
  confirmation, owned-space guard, and full cleanup transaction
- Free deleted usernames by renaming to !deleted:<id> so they can be reused
- Add migration to retroactively free usernames from already-tombstoned users
- Add GET /api/auth/check-username endpoint with rate limiting for real-time
  availability checking during registration
- Add debounced username availability indicator on registration Step 1
- Add DeleteAccountModal with federation-aware remote account cleanup
- Add federation ops utility for remote instance management
- Update sanitizeUser to anonymize deleted user profiles
- Add instance store improvements and connected instances modal updates
This commit is contained in:
Jannis Braun
2026-03-11 16:29:25 +01:00
parent c8e2945c07
commit 8c8767ba2c
18 changed files with 1343 additions and 21 deletions
+24
View File
@@ -99,6 +99,12 @@ export function runMigrations(db: Database.Database): void {
columns: [
{ name: 'avatar_color', type: 'TEXT' },
]
},
{
name: 'users',
columns: [
{ name: 'is_deleted', type: 'INTEGER DEFAULT 0' },
]
}
];
@@ -192,6 +198,9 @@ export function runMigrations(db: Database.Database): void {
// ─── Clean up corrupted read_states (temp_ IDs leaked from optimistic messages) ─
migrateCorruptedReadStates(db);
// ─── Free usernames from already-tombstoned users ───────────────────────────
migrateDeletedUsernames(db);
console.log('Migrations complete.');
}
@@ -418,6 +427,21 @@ function migrateCorruptedReadStates(db: Database.Database): void {
}
}
/** Rename already-tombstoned users so their original username can be reused */
function migrateDeletedUsernames(db: Database.Database): void {
const rows = db.prepare(
"SELECT id, username FROM users WHERE is_deleted = 1 AND username NOT LIKE '!deleted:%'"
).all() as { id: string; username: string }[];
if (rows.length === 0) return;
const update = db.prepare('UPDATE users SET username = ? WHERE id = ?');
for (const row of rows) {
update.run(`!deleted:${row.id}`, row.id);
console.log(`Migrating: Freed username "${row.username}" from deleted user ${row.id}`);
}
}
/**
* Rename non-namespaced replicated users: e.g. "test" → "test@nova.ddns.net"
* Frees plain usernames for native user creation and makes all federated users