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:
@@ -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
|
||||
|
||||
@@ -16,6 +16,7 @@ export const users = sqliteTable('users', {
|
||||
accentColor: text('accent_color'),
|
||||
avatarColor: text('avatar_color'),
|
||||
bio: text('bio'),
|
||||
isDeleted: integer('is_deleted').default(0),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user