From fe3a57a9c9b044e662c0ec5b4eb9d7eb414342ff Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 16 Aug 2025 12:24:14 +0200 Subject: [PATCH] feat: Enhance player validation by using hashed passwords and update player routes to avoid storing plaintext passwords --- database/backend.log | 204 +++++++++++++++++++++++++ database/routes/playerRoutes-sqlite.js | 26 ++-- database/sqlite/deathwatch.db-shm | Bin 32768 -> 32768 bytes database/sqlite/deathwatch.db-wal | Bin 2455552 -> 2826352 bytes src/components/PlayerTab.jsx | 2 +- 5 files changed, 221 insertions(+), 11 deletions(-) diff --git a/database/backend.log b/database/backend.log index 820ee94..48ef207 100644 --- a/database/backend.log +++ b/database/backend.log @@ -6459,3 +6459,207 @@ Connected to MongoDB [2025-08-16T10:13:48.113Z] DB: getPlayerByName - start gm [2025-08-16T10:13:48.113Z] DB: getPlayerByName - found gm [2025-08-16T10:13:48.113Z] API: Fetch player gm +[2025-08-16T10:15:56.532Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:15:56.533Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:15:56.533Z] DB: deletePlayer - start gmtest +[2025-08-16T10:15:56.533Z] DB: deletePlayer - changes 0 gmtest +[2025-08-16T10:15:56.560Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:15:56.561Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:15:56.561Z] DB: getPlayerByName - not found gmtest +[2025-08-16T10:15:56.947Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:15:56.947Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:15:56.947Z] DB: deletePlayer - start testplayer +[2025-08-16T10:15:56.947Z] DB: deletePlayer - changes 0 testplayer +[2025-08-16T10:15:56.963Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:15:56.964Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:15:56.964Z] DB: getPlayerByName - not found testplayer +[2025-08-16T10:15:57.517Z] DB: getAllPlayers - start +[2025-08-16T10:15:57.519Z] DB: getAllPlayers - resultCount 7 +[2025-08-16T10:17:04.049Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:17:04.049Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:17:04.049Z] DB: deletePlayer - start gmtest +[2025-08-16T10:17:04.049Z] DB: deletePlayer - changes 0 gmtest +[2025-08-16T10:17:04.077Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:17:04.077Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:17:04.077Z] DB: getPlayerByName - not found gmtest +[2025-08-16T10:17:04.527Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:17:04.528Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:17:04.528Z] DB: deletePlayer - start testplayer +[2025-08-16T10:17:04.528Z] DB: deletePlayer - changes 0 testplayer +[2025-08-16T10:17:04.545Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:17:04.545Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:17:04.545Z] DB: getPlayerByName - not found testplayer +[2025-08-16T10:17:06.225Z] DB: getAllPlayers - start +[2025-08-16T10:17:06.227Z] DB: getAllPlayers - resultCount 7 +[2025-08-16T10:17:49.136Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:17:49.138Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:17:49.138Z] DB: deletePlayer - start gmtest +[2025-08-16T10:17:49.138Z] DB: deletePlayer - changes 0 gmtest +[2025-08-16T10:17:49.188Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:17:49.189Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:17:49.189Z] DB: getPlayerByName - not found gmtest +[2025-08-16T10:17:49.415Z] DB: createPlayer - start gmtest +[2025-08-16T10:17:49.415Z] DB: createPlayer - done gmtest rowid 79 +[2025-08-16T10:17:49.415Z] API: Created player gmtest +[2025-08-16T10:17:49.434Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:17:49.434Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:17:49.435Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:17:49.435Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:17:49.972Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:17:49.972Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:17:49.973Z] DB: deletePlayer - start testplayer +[2025-08-16T10:17:49.973Z] DB: deletePlayer - changes 0 testplayer +[2025-08-16T10:17:49.990Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:17:49.990Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:17:49.990Z] DB: getPlayerByName - not found testplayer +[2025-08-16T10:17:50.071Z] DB: createPlayer - start testplayer +[2025-08-16T10:17:50.071Z] DB: createPlayer - done testplayer rowid 80 +[2025-08-16T10:17:50.071Z] API: Created player testplayer +[2025-08-16T10:17:50.091Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:17:50.091Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:17:50.172Z] DB: createSession - start 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 expiresAt 2025-08-17T10:17:50.171Z +[2025-08-16T10:17:50.172Z] DB: createSession - done 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.172Z] API: Player login testplayer 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.190Z] DB: getSession - start 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.190Z] DB: getSession - ok 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.191Z] SESSION: Valid 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 testplayer PUT /api/players/testplayer +[2025-08-16T10:17:50.191Z] DB: getSession - start 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.191Z] DB: getSession - ok 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 +[2025-08-16T10:17:50.191Z] SESSION: Valid 267b28e8e3b19d0131443982238765b6270f5a2a02d2a1eb70ac4c365a1bada8 testplayer PUT /api/players/testplayer +[2025-08-16T10:17:50.191Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:17:50.191Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:17:51.897Z] DB: getAllPlayers - start +[2025-08-16T10:17:51.899Z] DB: getAllPlayers - resultCount 9 +[2025-08-16T10:18:30.643Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:18:30.645Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:18:30.645Z] DB: deletePlayer - start gmtest +[2025-08-16T10:18:30.646Z] DB: deletePlayer - changes 1 gmtest +[2025-08-16T10:18:30.646Z] API: Deleted player gmtest +[2025-08-16T10:18:30.690Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:18:30.691Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:18:30.691Z] DB: getPlayerByName - not found gmtest +[2025-08-16T10:18:30.873Z] DB: createPlayer - start gmtest +[2025-08-16T10:18:30.874Z] DB: createPlayer - done gmtest rowid 81 +[2025-08-16T10:18:30.874Z] API: Created player gmtest +[2025-08-16T10:18:30.893Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:18:30.893Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:18:30.894Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:18:30.894Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:18:31.377Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:18:31.377Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:18:31.377Z] DB: deletePlayer - start testplayer +[2025-08-16T10:18:31.377Z] DB: deletePlayer - changes 1 testplayer +[2025-08-16T10:18:31.377Z] API: Deleted player testplayer +[2025-08-16T10:18:31.395Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:18:31.395Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:18:31.396Z] DB: getPlayerByName - not found testplayer +[2025-08-16T10:18:31.476Z] DB: createPlayer - start testplayer +[2025-08-16T10:18:31.476Z] DB: createPlayer - done testplayer rowid 82 +[2025-08-16T10:18:31.476Z] API: Created player testplayer +[2025-08-16T10:18:31.494Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:18:31.495Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:18:31.574Z] DB: createSession - start 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c expiresAt 2025-08-17T10:18:31.574Z +[2025-08-16T10:18:31.575Z] DB: createSession - done 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.575Z] API: Player login testplayer 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.591Z] DB: getSession - start 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.591Z] DB: getSession - ok 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.591Z] SESSION: Valid 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c testplayer PUT /api/players/testplayer +[2025-08-16T10:18:31.591Z] DB: getSession - start 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.591Z] DB: getSession - ok 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c +[2025-08-16T10:18:31.591Z] SESSION: Valid 14d07a1fad788e7409765c6312532815adfe1de731038ef2176e2b6f306c435c testplayer PUT /api/players/testplayer +[2025-08-16T10:18:31.592Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:18:31.592Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:18:33.312Z] DB: getAllPlayers - start +[2025-08-16T10:18:33.314Z] DB: getAllPlayers - resultCount 9 +[2025-08-16T10:21:05.320Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:21:05.322Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:21:05.323Z] DB: deletePlayer - start gmtest +[2025-08-16T10:21:05.323Z] DB: deletePlayer - changes 1 gmtest +[2025-08-16T10:21:05.323Z] API: Deleted player gmtest +[2025-08-16T10:21:05.371Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:21:05.372Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.372Z] DB: getPlayerByName - not found gmtest +[2025-08-16T10:21:05.550Z] DB: createPlayer - start gmtest +[2025-08-16T10:21:05.551Z] DB: createPlayer - done gmtest rowid 83 +[2025-08-16T10:21:05.551Z] API: Created player gmtest +[2025-08-16T10:21:05.570Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:21:05.570Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:21:05.571Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.571Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:21:05.572Z] DB: updatePlayer - start gmtest +[2025-08-16T10:21:05.573Z] DB: updatePlayer - changes 1 gmtest +[2025-08-16T10:21:05.573Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.573Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:21:05.573Z] API: Updated player gmtest +[2025-08-16T10:21:05.591Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:21:05.591Z] SESSION: GM bypass accepted PUT /api/players/gmtest +[2025-08-16T10:21:05.591Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.591Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:21:05.673Z] DB: updatePlayer - start gmtest +[2025-08-16T10:21:05.673Z] DB: updatePlayer - changes 1 gmtest +[2025-08-16T10:21:05.673Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.673Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:21:05.673Z] API: Updated player gmtest +[2025-08-16T10:21:05.689Z] DB: getPlayerByName - start gmtest +[2025-08-16T10:21:05.690Z] DB: getPlayerByName - found gmtest +[2025-08-16T10:21:05.773Z] DB: createSession - start 50e6d69d13507dc9deeffe4323ab3fe955e2e9591c52f25fa9ab23320d53ea4f expiresAt 2025-08-17T10:21:05.773Z +[2025-08-16T10:21:05.773Z] DB: createSession - done 50e6d69d13507dc9deeffe4323ab3fe955e2e9591c52f25fa9ab23320d53ea4f +[2025-08-16T10:21:05.773Z] API: Player login gmtest 50e6d69d13507dc9deeffe4323ab3fe955e2e9591c52f25fa9ab23320d53ea4f +[2025-08-16T10:21:05.789Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:21:05.789Z] SESSION: GM bypass accepted DELETE /api/players/gmtest +[2025-08-16T10:21:05.789Z] DB: deletePlayer - start gmtest +[2025-08-16T10:21:05.790Z] DB: deletePlayer - changes 1 gmtest +[2025-08-16T10:21:05.790Z] API: Deleted player gmtest +[2025-08-16T10:21:05.805Z] SESSION: GM bypass accepted GET /api/players +[2025-08-16T10:21:05.805Z] DB: getAllPlayers - start +[2025-08-16T10:21:05.807Z] DB: getAllPlayers - resultCount 8 +[2025-08-16T10:21:05.807Z] API: Fetch all players (public) Found 8 players +[2025-08-16T10:21:06.265Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:21:06.265Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:21:06.265Z] DB: deletePlayer - start testplayer +[2025-08-16T10:21:06.265Z] DB: deletePlayer - changes 1 testplayer +[2025-08-16T10:21:06.265Z] API: Deleted player testplayer +[2025-08-16T10:21:06.282Z] SESSION: GM bypass accepted POST /api/players +[2025-08-16T10:21:06.282Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.282Z] DB: getPlayerByName - not found testplayer +[2025-08-16T10:21:06.364Z] DB: createPlayer - start testplayer +[2025-08-16T10:21:06.364Z] DB: createPlayer - done testplayer rowid 84 +[2025-08-16T10:21:06.364Z] API: Created player testplayer +[2025-08-16T10:21:06.382Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.383Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:21:06.466Z] DB: createSession - start d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d expiresAt 2025-08-17T10:21:06.466Z +[2025-08-16T10:21:06.466Z] DB: createSession - done d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.466Z] API: Player login testplayer d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.483Z] DB: getSession - start d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.483Z] DB: getSession - ok d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.483Z] SESSION: Valid d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d testplayer PUT /api/players/testplayer +[2025-08-16T10:21:06.483Z] DB: getSession - start d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.483Z] DB: getSession - ok d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.483Z] SESSION: Valid d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d testplayer PUT /api/players/testplayer +[2025-08-16T10:21:06.484Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.484Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:21:06.484Z] DB: updatePlayer - start testplayer +[2025-08-16T10:21:06.484Z] DB: updatePlayer - changes 1 testplayer +[2025-08-16T10:21:06.484Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.485Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:21:06.485Z] API: Updated player testplayer +[2025-08-16T10:21:06.501Z] DB: getSession - start d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.501Z] DB: getSession - ok d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.501Z] SESSION: Valid d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d testplayer PUT /api/players/testplayer +[2025-08-16T10:21:06.501Z] DB: getSession - start d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.501Z] DB: getSession - ok d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d +[2025-08-16T10:21:06.501Z] SESSION: Valid d0205542063185516ddc1feaa4b208785461eee22bf089bf416f8fef57782c3d testplayer PUT /api/players/testplayer +[2025-08-16T10:21:06.501Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.501Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:21:06.502Z] DB: updatePlayer - start testplayer +[2025-08-16T10:21:06.502Z] DB: updatePlayer - changes 1 testplayer +[2025-08-16T10:21:06.502Z] DB: getPlayerByName - start testplayer +[2025-08-16T10:21:06.502Z] DB: getPlayerByName - found testplayer +[2025-08-16T10:21:06.502Z] API: Updated player testplayer +[2025-08-16T10:21:06.518Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:21:06.519Z] SESSION: GM bypass accepted DELETE /api/players/testplayer +[2025-08-16T10:21:06.519Z] DB: deletePlayer - start testplayer +[2025-08-16T10:21:06.519Z] DB: deletePlayer - changes 1 testplayer +[2025-08-16T10:21:06.519Z] API: Deleted player testplayer +[2025-08-16T10:21:08.256Z] DB: getAllPlayers - start +[2025-08-16T10:21:08.258Z] DB: getAllPlayers - resultCount 7 diff --git a/database/routes/playerRoutes-sqlite.js b/database/routes/playerRoutes-sqlite.js index bf9db43..3e4f092 100644 --- a/database/routes/playerRoutes-sqlite.js +++ b/database/routes/playerRoutes-sqlite.js @@ -217,21 +217,22 @@ router.post('/', async (req, res) => { return res.status(409).json({ error: 'Player already exists' }); } - // Validate and normalize incoming player object - const { valid, errors, normalized } = validatePlayer({ name, pw, ...otherData }); - if (!valid) { - return res.status(400).json({ error: 'Validation failed', details: errors }); - } - - // Create password hash if password provided (use safeHash) + // If a plain password was provided, hash it first so validation won't reject plaintext let pwHash = ''; if (pw) { pwHash = await safeHash(pw); } + // Validate and normalize incoming player object. Pass pwHash instead of plaintext pw. + const { valid, errors, normalized } = validatePlayer({ name, pwHash, ...otherData }); + if (!valid) { + return res.status(400).json({ error: 'Validation failed', details: errors }); + } + const newPlayer = playerHelpers.create({ name: normalized.name, - pw: pw || '', + // Do NOT store plaintext pw to avoid validation rejecting records later + pw: '', pwHash, rollerInfo: normalized.rollerInfo || {}, shopInfo: normalized.shopInfo || {}, @@ -262,6 +263,9 @@ router.put('/:name', requireSession, async (req, res) => { // Handle password update if provided using safeHash if (updateData.pw) { updateData.pwHash = await safeHash(updateData.pw); + // Do not pass plaintext pw into validation; validation requires pwHash only + // We'll store an empty pw field (frontend may still use pw for temporary purposes) + updateData.pw = ''; } // Merge the data properly @@ -269,7 +273,8 @@ router.put('/:name', requireSession, async (req, res) => { rollerInfo: { ...existingPlayer.rollerInfo, ...updateData.rollerInfo }, shopInfo: { ...existingPlayer.shopInfo, ...updateData.shopInfo }, tabInfo: { ...existingPlayer.tabInfo, ...updateData.tabInfo }, - pw: updateData.pw || existingPlayer.pw, + // Never carry forward plaintext pw into validation; keep pw empty and use pwHash + pw: '', pwHash: updateData.pwHash || existingPlayer.pwHash }; @@ -378,7 +383,8 @@ router.post('/:name/avatar', requireSession, async (req, res) => { rollerInfo: { ...existingPlayer.rollerInfo }, shopInfo: { ...existingPlayer.shopInfo }, tabInfo: { ...(existingPlayer.tabInfo || {}), picture: avatarUrl }, - pw: existingPlayer.pw, + // Do not include plaintext pw when validating/updating avatar + pw: '', pwHash: existingPlayer.pwHash }; diff --git a/database/sqlite/deathwatch.db-shm b/database/sqlite/deathwatch.db-shm index 3ae2a2585860d21d807db85537d335e59f9bc3c1..feefc665107b5e7554cb6afd39fa61e6e345e687 100644 GIT binary patch delta 720 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 p.name === lookupName) || null; debug('PlayerTab', 'currentPlayer computed', { authedPlayer, playerName, lookupName, playerFound: !!player }); return player; - }, [players, authedPlayer]); + }, [players, authedPlayer, playerName, isGMLoggedIn]); useEffect(() => { debug('PlayerTab', 'Data loading effect triggered', { authedPlayer, hasCurrentPlayer: !!currentPlayer });