Compare commits

...

3 Commits

Author SHA1 Message Date
github-actions[bot] 03d37650a0 🆙 Bump version to 4.1.8 [skip ci] 2026-04-28 09:32:12 +00:00
DuckieTM f4e5449443 Merge pull request #92 from duckietm/dev
🆙 Added Ban to the API
2026-04-28 11:31:15 +02:00
duckietm 1ebc8314a8 🆙 Added Ban to the API 2026-04-28 11:30:54 +02:00
3 changed files with 140 additions and 52 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.1.7</version>
<version>4.1.8</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -364,8 +364,18 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
return;
}
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement stmt = conn.prepareStatement(
try (Connection conn = Emulator.getDatabase().getDataSource().getConnection()) {
if (ip != null && !ip.isEmpty()) {
BanInfo ipBan = lookupIpBan(conn, ip);
if (ipBan != null) {
LOGGER.info("[auth/login] ip ban hit ip={} type={} expires={}",
ip, ipBan.type, ipBan.expiresAt);
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, bannedPayload(ipBan));
return;
}
}
try (PreparedStatement stmt = conn.prepareStatement(
"SELECT id, username, password FROM users WHERE username = ? LIMIT 1")) {
stmt.setString(1, username);
try (ResultSet rs = stmt.executeQuery()) {
@@ -392,6 +402,15 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
return;
}
BanInfo accountBan = lookupAccountBan(conn, userId);
if (accountBan != null) {
LOGGER.info("[auth/login] account ban hit userId={} type={} expires={}",
userId, accountBan.type, accountBan.expiresAt);
AuthRateLimiter.recordSuccess(ip);
sendJson(ctx, req, HttpResponseStatus.FORBIDDEN, bannedPayload(accountBan));
return;
}
String ssoTicket = mintSsoTicket();
try (PreparedStatement upd = conn.prepareStatement(
@@ -421,6 +440,7 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
if (rememberToken != null) ok.addProperty("rememberToken", rememberToken);
sendJson(ctx, req, HttpResponseStatus.OK, ok);
}
}
} catch (Exception e) {
LOGGER.error("Login query failed for username=" + username, e);
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Server error."));
@@ -804,6 +824,74 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
sendJson(ctx, req, HttpResponseStatus.OK, ok);
}
private static final long PERMANENT_BAN_THRESHOLD_SECONDS = 30L * 365L * 24L * 60L * 60L;
private static final class BanInfo {
final String type;
final String reason;
final int expiresAt;
BanInfo(String type, String reason, int expiresAt) {
this.type = type == null ? "account" : type;
this.reason = reason == null ? "" : reason;
this.expiresAt = expiresAt;
}
boolean isPermanent() {
return (long) expiresAt - Emulator.getIntUnixTimestamp() > PERMANENT_BAN_THRESHOLD_SECONDS;
}
}
private static BanInfo lookupAccountBan(Connection conn, int userId) throws SQLException {
try (PreparedStatement stmt = conn.prepareStatement(
"SELECT ban_expire, ban_reason, type FROM bans " +
"WHERE user_id = ? AND ban_expire >= ? AND (type = 'account' OR type = 'super') " +
"ORDER BY ban_expire DESC LIMIT 1")) {
stmt.setInt(1, userId);
stmt.setInt(2, Emulator.getIntUnixTimestamp());
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return new BanInfo(rs.getString("type"), rs.getString("ban_reason"), rs.getInt("ban_expire"));
}
}
}
return null;
}
private static BanInfo lookupIpBan(Connection conn, String ip) throws SQLException {
try (PreparedStatement stmt = conn.prepareStatement(
"SELECT ban_expire, ban_reason, type FROM bans " +
"WHERE ip = ? AND ban_expire >= ? AND (type = 'ip' OR type = 'super') " +
"ORDER BY ban_expire DESC LIMIT 1")) {
stmt.setString(1, ip);
stmt.setInt(2, Emulator.getIntUnixTimestamp());
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return new BanInfo(rs.getString("type"), rs.getString("ban_reason"), rs.getInt("ban_expire"));
}
}
}
return null;
}
private static JsonObject bannedPayload(BanInfo ban) {
boolean permanent = ban.isPermanent();
String message = permanent
? "Your account has been permanently banned."
: "Your account is temporarily banned.";
JsonObject details = new JsonObject();
details.addProperty("type", ban.type);
details.addProperty("reason", ban.reason);
details.addProperty("permanent", permanent);
if (!permanent) details.addProperty("expiresAt", ban.expiresAt);
JsonObject obj = new JsonObject();
obj.addProperty("error", message);
obj.add("ban", details);
return obj;
}
private static boolean checkPassword(String plain, String stored) {
String compatible = stored.startsWith("$2y$") ? "$2a$" + stored.substring(4) : stored;
try {