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()) {