feat: identity protection UI, duplicate guards, name-lock enforcement (v2.4.6)

- Add password set/change/remove with loading states and double-click prevention
- Add protected identity deletion flow (server-side password removal first)
- Add restore flow for password-protected UUIDs (verify password before saving)
- Add UUID duplicate checks in setUuidForUser (prevent accidental overwrites)
- Add name-locked error handling in launch flow (server enforces registered name)
- Sync shield icon across all identity mutation paths
- Refresh identity dropdown after all password/identity operations
- Propagate force flag through IPC for legitimate overwrites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sanasol
2026-02-28 18:22:40 +01:00
parent 0b861904ba
commit 7347910fe9
7 changed files with 558 additions and 73 deletions

21
main.js
View File

@@ -532,10 +532,10 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
// Check if UUID has password before launching
let launchOptions = {};
const { getAuthServerUrl, getUuidForUser } = require('./backend/core/config');
const launchUuid = getUuidForUser(playerName);
try {
const { getAuthServerUrl } = require('./backend/core/config');
const { getUuidForUser } = require('./backend/core/config');
const uuid = getUuidForUser(playerName);
const uuid = launchUuid;
const authServerUrl = getAuthServerUrl();
const statusResp = await fetch(`${authServerUrl}/player/password/status/${uuid}`);
if (statusResp.ok) {
@@ -574,7 +574,7 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
}
if (error.passwordRequired) {
return { success: false, passwordRequired: true, error: 'Password required' };
return { success: false, passwordRequired: true, uuid: launchUuid, error: 'Password required' };
}
if (error.lockedOut) {
return { success: false, error: 'Too many failed attempts. Try again in ' + Math.ceil((error.lockoutSeconds || 900) / 60) + ' minutes.' };
@@ -582,6 +582,9 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
if (error.usernameTaken) {
return { success: false, usernameTaken: true, error: errorMessage };
}
if (error.nameLocked) {
return { success: false, nameLocked: true, registeredName: error.registeredName, error: error.message };
}
return { success: false, error: errorMessage };
}
@@ -625,6 +628,9 @@ ipcMain.handle('launch-game-with-password', async (event, playerName, javaPath,
if (error.usernameTaken) {
return { success: false, usernameTaken: true, error: error.message || error.toString() };
}
if (error.nameLocked) {
return { success: false, nameLocked: true, registeredName: error.registeredName, error: error.message };
}
return { success: false, error: error.message || error.toString() };
}
});
@@ -1423,9 +1429,12 @@ ipcMain.handle('get-all-uuid-mappings', async () => {
}
});
ipcMain.handle('set-uuid-for-user', async (event, username, uuid) => {
ipcMain.handle('set-uuid-for-user', async (event, username, uuid, force) => {
try {
await setUuidForUser(username, uuid);
const result = setUuidForUser(username, uuid, { force: !!force });
if (result && result.success === false) {
return result; // { success: false, error: 'duplicate', existingUuid }
}
return { success: true };
} catch (error) {
console.error('Error setting UUID for user:', error);