From 6b831a0f1b6f7e12612c530d9b9a49d67d576167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 26 May 2026 15:03:16 +0200 Subject: [PATCH 1/2] fix(twofactor_backupcodes): Add a clean helper to set code as used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../lib/Db/BackupCodeMapper.php | 13 +++++++++++++ .../lib/Service/BackupCodeStorage.php | 9 +-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php b/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php index fbbc3d0403c75..b6698dae0bf08 100644 --- a/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php +++ b/apps/twofactor_backupcodes/lib/Db/BackupCodeMapper.php @@ -54,4 +54,17 @@ public function deleteCodesByUserId(string $uid): void { ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($uid))); $qb->execute(); } + + /** + * Marks the backup code as used, if not already marked as used in DB. + * @return int number of affected rows + */ + public function markUsedIfUnused(BackupCode $code): int { + $qb = $this->db->getQueryBuilder(); + $qb->update($this->getTableName()) + ->set('used', $qb->createNamedParameter(1, IQueryBuilder::PARAM_INT)) + ->where($qb->expr()->eq('id', $qb->createNamedParameter($code->getId(), IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('used', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + return $qb->executeStatement(); + } } diff --git a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php index 7dd6b3949e2d2..4c51be84141af 100644 --- a/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php +++ b/apps/twofactor_backupcodes/lib/Service/BackupCodeStorage.php @@ -85,19 +85,12 @@ public function getBackupCodesState(IUser $user): array { ]; } - /** - * @param IUser $user - * @param string $code - * @return bool - */ public function validateCode(IUser $user, string $code): bool { $dbCodes = $this->mapper->getBackupCodes($user); foreach ($dbCodes as $dbCode) { if ((int)$dbCode->getUsed() === 0 && $this->hasher->verify($code, $dbCode->getCode())) { - $dbCode->setUsed(1); - $this->mapper->update($dbCode); - return true; + return ($this->mapper->markUsedIfUnused($dbCode) === 1); } } return false; From 8c3138d06e74f406ed37461f018b1d8eaf9a43c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 15 Jun 2026 14:47:25 +0200 Subject: [PATCH 2/2] chore(tests): Adapt BackupCodeStorageTest to code changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- .../tests/Unit/Service/BackupCodeStorageTest.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php b/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php index 67124a7a92811..10d5a333babdf 100644 --- a/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php +++ b/apps/twofactor_backupcodes/tests/Unit/Service/BackupCodeStorageTest.php @@ -166,12 +166,11 @@ public function testValidateCode(): void { ->with('CHALLENGE', 'HASHEDVALUE', $this->anything()) ->willReturn(true); $this->mapper->expects($this->once()) - ->method('update') - ->with($code); + ->method('markUsedIfUnused') + ->with($code) + ->willReturn(1); $this->assertTrue($this->storage->validateCode($user, 'CHALLENGE')); - - $this->assertEquals(1, $code->getUsed()); } public function testValidateUsedCode(): void {