pdo->prepare( 'SELECT id, username, password_hash, role, site_scope, failed_attempts, locked_until FROM move_users WHERE username = ? LIMIT 1' ); $st->execute([$username]); $row = $st->fetch(); if ($row === false) { return null; } return [ 'id' => (int)$row['id'], 'username' => $row['username'], 'password_hash' => $row['password_hash'], 'role' => $row['role'], 'site_scope' => $row['site_scope'], 'failed_attempts' => (int)$row['failed_attempts'], 'locked_until' => (int)$row['locked_until'], ]; } public function recordFailure(int $userId, int $lockUntil): void { // Increment in SQL (not read-then-write in PHP) so concurrent failed // logins for the same account can't clobber each other's count. $st = $this->pdo->prepare( 'UPDATE move_users SET failed_attempts = failed_attempts + 1, locked_until = ? WHERE id = ?' ); $st->execute([$lockUntil, $userId]); } public function clearFailures(int $userId): void { $st = $this->pdo->prepare('UPDATE move_users SET failed_attempts = 0, locked_until = 0 WHERE id = ?'); $st->execute([$userId]); } }