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
+29
View File
@@ -7,6 +7,7 @@ import { useSocialStore } from './socialStore';
import { useVoiceStore } from './voiceStore';
import { useInstanceStore } from './instanceStore';
import { syncProfileUpdateToRemotes } from '../utils/profileSync';
import { changePasswordOnRemotes, deleteAccountOnRemotes, type FederationOpResult } from '../utils/federationOps';
interface AuthState {
token: string | null;
@@ -18,6 +19,8 @@ interface AuthState {
logout: () => void;
loadUser: () => Promise<void>;
updateProfile: (data: { displayName?: string; avatar?: string; banner?: string; accentColor?: string; avatarColor?: string; bio?: string; customStatus?: string; status?: UserStatus }) => Promise<void>;
changePassword: (currentPassword: string, newPassword: string) => Promise<FederationOpResult[]>;
deleteAccount: (password: string, username: string) => Promise<void>;
setUser: (user: User) => void;
clearError: () => void;
}
@@ -103,6 +106,32 @@ export const useAuthStore = create<AuthState>((set, get) => ({
}
},
changePassword: async (currentPassword: string, newPassword: string) => {
// Change on home instance
const response = await api.users.changePassword({ currentPassword, newPassword });
// Update token in state and localStorage
localStorage.setItem('backspace_token', response.token);
set({ token: response.token });
// Propagate to remote instances (best-effort)
const remoteResults = await changePasswordOnRemotes(newPassword);
return remoteResults;
},
deleteAccount: async (password: string, username: string) => {
// Delete on all remote instances first (best-effort)
await deleteAccountOnRemotes();
// Delete on home instance
await api.users.deleteAccount({ password, username });
// Clear all state
localStorage.removeItem('backspace_token');
resetUserStores();
set({ token: null, user: null });
},
setUser: (user: User) => set({ user }),
clearError: () => set({ error: null }),