From e334a3e0ac11678bfaa3672542853b8b4839490b Mon Sep 17 00:00:00 2001 From: medievalshell Date: Tue, 19 May 2026 00:46:58 +0200 Subject: [PATCH] feat(auth): backward-compatible TTL check on SSO auth_ticket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pairs with the CMS-side change introducing auth_ticket_expires_at (60s expiry written on every ticket issuance). Without an emulator-side verification the column was advisory only — this commit gates every SELECT that resolves a user by auth_ticket on auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) The NULL branch preserves backward-compatibility: CMS deployments that do not yet populate the column keep working exactly like before (every ticket passes the WHERE clause as soon as auth_ticket matches), and the TTL takes effect automatically the moment a CMS starts writing the expiry value. Five SELECTs touched: - SessionEndpoints.java (cms-issued SSO + remember-token flow) - HabboManager.loadHabbo (game client login by ticket) - SecureLoginEvent (legacy handshake path) DB schema delivered both ways: - Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql: idempotent ALTER, skips if column already present (information_schema guard so re-running the bundle is safe). - Default Database/FullDatabase.sql: column added to the `users` table definition for fresh installs. Bumps the emulator version to 4.2.7. --- .../020_auth_ticket_ttl.sql | 36 +++++++++++++++++++ Default Database/FullDatabase.sql | 1 + Emulator/pom.xml | 2 +- .../habbo/habbohotel/users/HabboManager.java | 4 +-- .../incoming/handshake/SecureLoginEvent.java | 2 +- .../gameserver/auth/SessionEndpoints.java | 4 +-- 6 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql diff --git a/Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql b/Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql new file mode 100644 index 00000000..ac4d1fd8 --- /dev/null +++ b/Database Updates/Own_Database_RunFirst/020_auth_ticket_ttl.sql @@ -0,0 +1,36 @@ +-- ============================================================================ +-- 020_auth_ticket_ttl.sql +-- +-- Adds an explicit expiry timestamp to the SSO auth_ticket on `users`. +-- +-- The CMS issuing the ticket is expected to populate auth_ticket_expires_at +-- (e.g. NOW() + INTERVAL 60 SECOND) on every login redirect. The emulator- +-- side SELECT queries that look up a user by auth_ticket have been changed to +-- +-- WHERE auth_ticket = ? +-- AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) +-- +-- The NULL branch keeps backward-compatibility with CMS deployments that do +-- not populate the column yet: existing rows continue to authenticate the +-- same way they always did, and the TTL kicks in only once the CMS starts +-- writing the expiry value. +-- +-- Idempotent: skips the ALTER if the column already exists. +-- ============================================================================ + +SET @col_exists = ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'users' + AND COLUMN_NAME = 'auth_ticket_expires_at' +); + +SET @ddl = IF(@col_exists = 0, + 'ALTER TABLE `users` ADD COLUMN `auth_ticket_expires_at` TIMESTAMP NULL DEFAULT NULL AFTER `auth_ticket`', + 'SELECT ''auth_ticket_expires_at already present, skipping'' AS info' +); + +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; diff --git a/Default Database/FullDatabase.sql b/Default Database/FullDatabase.sql index 2351adde..75a3b75b 100644 --- a/Default Database/FullDatabase.sql +++ b/Default Database/FullDatabase.sql @@ -30682,6 +30682,7 @@ CREATE TABLE IF NOT EXISTS `users` ( `points` int(11) NOT NULL DEFAULT 10, `online` enum('0','1','2') CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '0', `auth_ticket` varchar(256) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL DEFAULT '', + `auth_ticket_expires_at` timestamp NULL DEFAULT NULL, `remember_token_hash` varchar(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT '', `remember_token_expires_at` int(11) unsigned NOT NULL DEFAULT 0, `ip_register` varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, diff --git a/Emulator/pom.xml b/Emulator/pom.xml index b3729627..c17786aa 100644 --- a/Emulator/pom.xml +++ b/Emulator/pom.xml @@ -6,7 +6,7 @@ com.eu.habbo Habbo - 4.2.6 + 4.2.7 UTF-8 diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java index 14fd99bf..4ca99732 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java @@ -95,7 +95,7 @@ public class HabboManager { int userId = 0; try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) { + PreparedStatement statement = connection.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) LIMIT 1")) { statement.setString(1, sso); try (ResultSet s = statement.executeQuery()) { if (s.next()) { @@ -121,7 +121,7 @@ public class HabboManager { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE auth_ticket = ? LIMIT 1")) { + PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) LIMIT 1")) { statement.setString(1, sso); try (ResultSet set = statement.executeQuery()) { if (set.next()) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java index 13d47c28..076a6795 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java @@ -104,7 +104,7 @@ public class SecureLoginEvent extends MessageHandler { // First, look up the user ID to check for ghost sessions int lookupUserId = 0; try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); - java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) { + java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) LIMIT 1")) { stmt.setString(1, sso); try (java.sql.ResultSet rs = stmt.executeQuery()) { if (rs.next()) { diff --git a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/SessionEndpoints.java b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/SessionEndpoints.java index 931b5b89..32f355be 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/SessionEndpoints.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/auth/SessionEndpoints.java @@ -50,7 +50,7 @@ final class SessionEndpoints { if (ssoTicket != null && !ssoTicket.isEmpty()) { try (PreparedStatement lookup = conn.prepareStatement( - "SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) { + "SELECT id FROM users WHERE auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) LIMIT 1")) { lookup.setString(1, ssoTicket); try (ResultSet rs = lookup.executeQuery()) { if (rs.next()) userId = rs.getInt("id"); @@ -134,7 +134,7 @@ final class SessionEndpoints { try (Connection conn = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement lookup = conn.prepareStatement( - "SELECT id, username FROM users WHERE auth_ticket = ? LIMIT 1")) { + "SELECT id, username FROM users WHERE auth_ticket = ? AND (auth_ticket_expires_at IS NULL OR auth_ticket_expires_at >= NOW()) LIMIT 1")) { lookup.setString(1, ssoTicket); try (ResultSet rs = lookup.executeQuery()) { if (!rs.next()) {