From 7d268012ddf99ccdc0f5d39e1d3ae08300bc87e5 Mon Sep 17 00:00:00 2001 From: Git'Fellow <12234510+solracsf@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:24:06 +0100 Subject: [PATCH] fix(backend): Upgrade outdated password hashes on email logins checkPassword() accepts the user id or the email address, but handed the login name straight to setPassword(), which matches on uid_lower. When a guest with a hashed user id logged in with their email address the update therefore matched no row and the outdated hash stayed in place, so it was re-computed and dropped on every single login. Use the user id that the lookup already returned. Signed-off-by: Git'Fellow <12234510+solracsf@users.noreply.github.com> --- lib/UserBackend.php | 4 +++- tests/unit/UserBackendTest.php | 24 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/UserBackend.php b/lib/UserBackend.php index d3114b4b..f8975814 100644 --- a/lib/UserBackend.php +++ b/lib/UserBackend.php @@ -310,7 +310,9 @@ public function checkPassword(string $loginName, string $password) { $newHash = ''; if ($this->hasher->verify($password, $storedHash, $newHash)) { if ($newHash !== '') { - $this->setPassword($loginName, $password); + // $loginName can be the email address, while setPassword() + // matches on the user id. + $this->setPassword((string)$row['uid'], $password); } return (string)$row['uid']; diff --git a/tests/unit/UserBackendTest.php b/tests/unit/UserBackendTest.php index 6f7a6f72..4cdaa540 100644 --- a/tests/unit/UserBackendTest.php +++ b/tests/unit/UserBackendTest.php @@ -102,4 +102,28 @@ public function testCustomLoginNameUid(): void { $this->assertEquals($uid, $this->backend->checkPassword($email, 'bar')); $this->assertEquals('Karl Doe', $this->backend->getDisplayName($uid)); } + + /** + * An outdated hash has to be upgraded no matter whether the guest logged + * in with the user id or with the email address. + */ + public function testOutdatedPasswordHashIsUpgradedOnEmailLogin(): void { + $email = 'foo@example.tld'; + $uid = hash('sha256', $email); + $this->backend->createUser($uid, 'bar'); + $this->backend->setInitialEmail($uid, $email); + + // An unprefixed bcrypt hash is a legacy hash, so verifying it always + // hands back a replacement hash. + $legacyHash = password_hash('bar', PASSWORD_BCRYPT); + $query = Server::get(IDBConnection::class)->getQueryBuilder(); + $query->update('guests_users') + ->set('password', $query->createNamedParameter($legacyHash)) + ->where($query->expr()->eq('uid_lower', $query->createNamedParameter($uid))); + $query->executeStatement(); + + $this->assertEquals($uid, $this->backend->checkPassword($email, 'bar')); + + $this->assertNotEquals($legacyHash, $this->backend->getPasswordHash($uid)); + } }