From 1c63ce46241e8df6f35f33533b808184811863ca Mon Sep 17 00:00:00 2001 From: Petr Ilin Date: Sat, 2 Apr 2022 11:28:16 +0300 Subject: Better premium/cracked detection+caching (closes #15, closes #19) --- .../java/net/elytrium/limboauth/LimboAuth.java | 75 ++++++++++++++++++---- src/main/java/net/elytrium/limboauth/Settings.java | 8 +++ 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/elytrium/limboauth/LimboAuth.java b/src/main/java/net/elytrium/limboauth/LimboAuth.java index b92ec91..c1b63d1 100644 --- a/src/main/java/net/elytrium/limboauth/LimboAuth.java +++ b/src/main/java/net/elytrium/limboauth/LimboAuth.java @@ -114,7 +114,8 @@ import org.slf4j.Logger; ) public class LimboAuth { - private final Map cachedAuthChecks = new ConcurrentHashMap<>(); + private final Map cachedAuthChecks = new ConcurrentHashMap<>(); + private final Map premiumCache = new ConcurrentHashMap<>(); private final Map postLoginTasks = new ConcurrentHashMap<>(); private final Set unsafePasswords = new HashSet<>(); @@ -286,12 +287,19 @@ public class LimboAuth { this.server.getEventManager().register(this, new AuthListener(this, this.playerDao, this.floodgateApi)); Executors.newScheduledThreadPool(1, task -> new Thread(task, "purge-cache")).scheduleAtFixedRate(() -> - this.checkCache(this.cachedAuthChecks, Settings.IMP.MAIN.PURGE_CACHE_MILLIS), + this.checkCache(this.cachedAuthChecks, Settings.IMP.MAIN.PURGE_CACHE_MILLIS), Settings.IMP.MAIN.PURGE_CACHE_MILLIS, Settings.IMP.MAIN.PURGE_CACHE_MILLIS, TimeUnit.MILLISECONDS ); + Executors.newScheduledThreadPool(1, task -> new Thread(task, "purge-premium-cache")).scheduleAtFixedRate(() -> + this.checkCache(this.premiumCache, Settings.IMP.MAIN.PURGE_PREMIUM_CACHE_MILLIS), + Settings.IMP.MAIN.PURGE_PREMIUM_CACHE_MILLIS, + Settings.IMP.MAIN.PURGE_PREMIUM_CACHE_MILLIS, + TimeUnit.MILLISECONDS + ); + this.server.getEventManager().fireAndForget(new AuthPluginReloadEvent()); } @@ -355,7 +363,7 @@ public class LimboAuth { public void cacheAuthUser(Player player) { String username = player.getUsername().toLowerCase(Locale.ROOT); this.cachedAuthChecks.remove(username); - this.cachedAuthChecks.put(username, new CachedUser(player.getRemoteAddress().getAddress(), System.currentTimeMillis())); + this.cachedAuthChecks.put(username, new CachedSessionUser(player.getRemoteAddress().getAddress(), System.currentTimeMillis())); } public void removePlayerFromCache(String username) { @@ -465,16 +473,35 @@ public class LimboAuth { } public boolean isPremiumExternal(String nickname) { + String lowercaseNickname = nickname.toLowerCase(Locale.ROOT); + if (this.premiumCache.containsKey(lowercaseNickname)) { + return this.premiumCache.get(lowercaseNickname).isPremium(); + } + try { - return this.client.send( + int statusCode = this.client.send( HttpRequest.newBuilder() - .uri(URI.create(String.format(Settings.IMP.MAIN.ISPREMIUM_AUTH_URL, URLEncoder.encode(nickname, StandardCharsets.UTF_8)))) + .uri(URI.create( + String.format( + Settings.IMP.MAIN.ISPREMIUM_AUTH_URL, + URLEncoder.encode(lowercaseNickname, StandardCharsets.UTF_8)))) .build(), HttpResponse.BodyHandlers.ofString() - ).statusCode() == 200; + ).statusCode(); + + boolean isPremium = statusCode == 200; + + // 429 Too Many Requests + if (statusCode != 429) { + this.premiumCache.put(lowercaseNickname, new CachedPremiumUser(isPremium, System.currentTimeMillis())); + } else { + return Settings.IMP.MAIN.ON_RATE_LIMIT_PREMIUM; + } + + return isPremium; } catch (IOException | InterruptedException e) { this.getLogger().error("Unable to authenticate with Mojang", e); - return true; + return Settings.IMP.MAIN.ON_RATE_LIMIT_PREMIUM; } } @@ -513,7 +540,7 @@ public class LimboAuth { } } - private void checkCache(Map userMap, long time) { + private void checkCache(Map userMap, long time) { userMap.entrySet().stream() .filter(u -> u.getValue().getCheckTime() + time <= System.currentTimeMillis()) .map(Map.Entry::getKey) @@ -546,20 +573,42 @@ public class LimboAuth { private static class CachedUser { - private final InetAddress inetAddress; private final long checkTime; - public CachedUser(InetAddress inetAddress, long checkTime) { - this.inetAddress = inetAddress; + public CachedUser(long checkTime) { this.checkTime = checkTime; } + public long getCheckTime() { + return this.checkTime; + } + } + + private static class CachedSessionUser extends CachedUser { + + private final InetAddress inetAddress; + + public CachedSessionUser(InetAddress inetAddress, long checkTime) { + super(checkTime); + this.inetAddress = inetAddress; + } + public InetAddress getInetAddress() { return this.inetAddress; } + } - public long getCheckTime() { - return this.checkTime; + private static class CachedPremiumUser extends CachedUser { + + private final boolean isPremium; + + public CachedPremiumUser(boolean isPremium, long checkTime) { + super(checkTime); + this.isPremium = isPremium; + } + + public boolean isPremium() { + return this.isPremium; } } diff --git a/src/main/java/net/elytrium/limboauth/Settings.java b/src/main/java/net/elytrium/limboauth/Settings.java index 745a6ee..0ce9186 100644 --- a/src/main/java/net/elytrium/limboauth/Settings.java +++ b/src/main/java/net/elytrium/limboauth/Settings.java @@ -90,6 +90,7 @@ public class Settings extends Config { @Comment("Available dimensions: OVERWORLD, NETHER, THE_END") public String DIMENSION = "THE_END"; public long PURGE_CACHE_MILLIS = 3600000; + public long PURGE_PREMIUM_CACHE_MILLIS = 28800000; @Comment("QR Generator URL, set {data} placeholder") public String QR_GENERATOR_URL = "https://api.qrserver.com/v1/create-qr-code/?data={data}&size=200x200&ecc=M&margin=30"; public String TOTP_ISSUER = "LimboAuth by Elytrium"; @@ -120,6 +121,13 @@ public class Settings extends Config { }) public String ISPREMIUM_AUTH_URL = "https://api.mojang.com/users/profiles/minecraft/%s"; + @Comment({ + "If Mojang rate-limits your server, we cannot determine if the player is premium or not", + "This option allows you to choose whether every player will be defined as premium or as cracked while Mojang is rate-limiting the server", + "True - as premium; False - as cracked" + }) + public boolean ON_RATE_LIMIT_PREMIUM = true; + public List REGISTER_COMMAND = List.of("/r", "/reg", "/register"); public List LOGIN_COMMAND = List.of("/l", "/log", "/login"); public List TOTP_COMMAND = List.of("/2fa", "/totp"); -- cgit