mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-24 23:58:14 +01:00
Compare commits
26 Commits
2.0
...
1.12-kick-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
568ad7a621 | ||
|
|
1464ef2952 | ||
|
|
46239c9ffc | ||
|
|
083068b856 | ||
|
|
dd2aa922d7 | ||
|
|
b77ea285e5 | ||
|
|
f8f0e7ac7a | ||
|
|
927f09af67 | ||
|
|
9dee4e56a1 | ||
|
|
4dd6b9ade4 | ||
|
|
51efb9d62d | ||
|
|
1a56741112 | ||
|
|
7f488498cf | ||
|
|
9a40cf0afb | ||
|
|
2d177e3df5 | ||
|
|
bdd7af8290 | ||
|
|
29100b5376 | ||
|
|
bcb1893176 | ||
|
|
5fb6308130 | ||
|
|
a33f53e259 | ||
|
|
fb6209d26c | ||
|
|
829c70a51b | ||
|
|
2876448e0c | ||
|
|
2b60153f8a | ||
|
|
54b49eb6be | ||
|
|
64d291556a |
@@ -1,5 +1,7 @@
|
||||
# FastLogin
|
||||
|
||||

|
||||
|
||||
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
|
||||
So they don't need to enter passwords. This is also called auto login (auto-login).
|
||||
|
||||
@@ -64,7 +66,7 @@ Possible values: `Premium`, `Cracked`, `Unknown`
|
||||
* Server software in offlinemode:
|
||||
* Spigot (or a fork e.g. Paper) 1.8.8+
|
||||
* Protocol plugin:
|
||||
* [ProtocolLib 5.2+](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolLib 5.3+ with development build above 720](https://www.spigotmc.org/resources/protocollib.1997/) or
|
||||
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
* Latest BungeeCord (or a fork e.g. Waterfall) or Velocity proxy
|
||||
* An auth plugin.
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
@@ -119,16 +119,13 @@
|
||||
<!-- PaperSpigot API, PaperLib, datafixupper and bungeecord-chat -->
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
|
||||
<!-- ProtocolLib -->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
|
||||
@@ -211,7 +208,7 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.1.0</version>
|
||||
<version>5.3.0</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -282,7 +279,7 @@
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.5</version>
|
||||
<version>2.11.6</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
@@ -297,7 +294,7 @@
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.6.0-beta2</version>
|
||||
<version>5.6.0</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPremiumToggleEv
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason;
|
||||
@@ -57,6 +58,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) sender;
|
||||
if (forwardCrackedCommand(sender, sender.getName())) {
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +73,16 @@ public class CrackedCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_OTHER)
|
||||
);
|
||||
|
||||
plugin.getScheduler().getSyncExecutor().execute(() -> {
|
||||
if (plugin.getCore().getConfig().getBoolean("kick-toggle", true)) {
|
||||
player.kickPlayer(plugin.getCore().getMessage("remove-premium"));
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("not-premium", sender);
|
||||
@@ -104,7 +115,7 @@ public class CrackedCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,8 @@ public class PremiumCommand extends ToggleCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
UUID id = ((Player) sender).getUniqueId();
|
||||
Player player = (Player) sender;
|
||||
UUID id = player.getUniqueId();
|
||||
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getCore().getPendingConfirms().contains(id)) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
|
||||
plugin.getCore().getPendingConfirms().add(id);
|
||||
@@ -86,10 +87,17 @@ public class PremiumCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_SELF));
|
||||
});
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_SELF)
|
||||
);
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
plugin.getScheduler().getSyncExecutor().execute(() -> {
|
||||
if (plugin.getCore().getConfig().getBoolean("kick-toggle", true)) {
|
||||
player.kickPlayer(plugin.getCore().getMessage("remove-premium"));
|
||||
} else {
|
||||
plugin.getCore().sendLocaleMessage("add-premium", sender);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +125,8 @@ public class PremiumCommand extends ToggleCommand {
|
||||
plugin.getScheduler().runAsync(() -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
plugin.getServer().getPluginManager().callEvent(
|
||||
new BukkitFastLoginPremiumToggleEvent(profile, PremiumToggleReason.COMMAND_OTHER));
|
||||
new BukkitFastLoginPremiumToggleEvent(sender, profile, PremiumToggleReason.COMMAND_OTHER)
|
||||
);
|
||||
});
|
||||
|
||||
plugin.getCore().sendLocaleMessage("add-premium-other", sender);
|
||||
|
||||
@@ -27,6 +27,7 @@ package com.github.games647.fastlogin.bukkit.event;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent;
|
||||
import com.github.games647.fastlogin.core.storage.StoredProfile;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -34,11 +35,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLoginPremiumToggleEvent {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
private final CommandSender invoker;
|
||||
private final StoredProfile profile;
|
||||
private final PremiumToggleReason reason;
|
||||
|
||||
public BukkitFastLoginPremiumToggleEvent(StoredProfile profile, PremiumToggleReason reason) {
|
||||
public BukkitFastLoginPremiumToggleEvent(CommandSender invoker, StoredProfile profile, PremiumToggleReason reason) {
|
||||
super(true);
|
||||
|
||||
this.invoker = invoker;
|
||||
this.profile = profile;
|
||||
this.reason = reason;
|
||||
}
|
||||
@@ -48,6 +53,13 @@ public class BukkitFastLoginPremiumToggleEvent extends Event implements FastLogi
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return who triggered this change. This could be a Player for itself or others (Admin) or the console.
|
||||
*/
|
||||
public CommandSender getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PremiumToggleReason getReason() {
|
||||
return reason;
|
||||
|
||||
@@ -90,6 +90,7 @@ public class ConnectionListener implements Listener {
|
||||
if (floodgatePlayer != null) {
|
||||
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, floodgateAuthTask);
|
||||
plugin.getBungeeManager().markJoinEventFired(player);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.primitives.Longs;
|
||||
import lombok.val;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
@@ -41,6 +40,7 @@ import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
@@ -53,6 +53,7 @@ import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
@@ -198,9 +199,9 @@ final class EncryptionUtil {
|
||||
|
||||
private static PublicKey loadMojangSessionKey()
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
val keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
|
||||
val keyData = Resources.toByteArray(keyUrl);
|
||||
val keySpec = new X509EncodedKeySpec(keyData);
|
||||
URL keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
|
||||
byte[] keyData = Resources.toByteArray(keyUrl);
|
||||
KeySpec keySpec = new X509EncodedKeySpec(keyData);
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||
}
|
||||
|
||||
@@ -30,16 +30,16 @@ import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.PacketFilterManager;
|
||||
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
|
||||
import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.FieldAccessException;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.reflect.accessors.Accessors;
|
||||
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
|
||||
import com.comphenix.protocol.utility.MinecraftVersion;
|
||||
import com.comphenix.protocol.wrappers.BukkitConverters;
|
||||
import com.comphenix.protocol.wrappers.Converters;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
@@ -49,7 +49,6 @@ import com.mojang.datafixers.util.Either;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -75,7 +74,6 @@ import static com.comphenix.protocol.PacketType.Login.Client.START;
|
||||
public class ProtocolLibListener extends PacketAdapter {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PlayerInjectionHandler handler;
|
||||
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
@@ -94,7 +92,6 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
this.plugin = plugin;
|
||||
this.antiBotService = antiBotService;
|
||||
this.verifyClientKeys = verifyClientKeys;
|
||||
this.handler = getHandler();
|
||||
}
|
||||
|
||||
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
|
||||
@@ -117,13 +114,13 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
Player sender = packetEvent.getPlayer();
|
||||
PacketType packetType = getOverriddenType(packetEvent.getPacketType());
|
||||
|
||||
plugin.getLog().info("New packet {} from {}", packetType, sender);
|
||||
plugin.getLog().info("New incoming packet {} from {}", packetType, sender.getName());
|
||||
try {
|
||||
if (packetType == START) {
|
||||
if (plugin.getFloodgateService() != null) {
|
||||
boolean success = processFloodgateTasks(packetEvent);
|
||||
// don't continue execution if the player was kicked by Floodgate
|
||||
if (!success) {
|
||||
// don't continue execution if the player was kicked by Floodgate
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -248,8 +245,9 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
// public key is sent separate
|
||||
clientKey = Optional.empty();
|
||||
} else {
|
||||
val profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
|
||||
.optionRead(0);
|
||||
Optional<Optional<WrappedProfileKeyData>> profileKey = packet.getOptionals(
|
||||
BukkitConverters.getWrappedPublicKeyDataConverter()
|
||||
).optionRead(0);
|
||||
|
||||
clientKey = profileKey.flatMap(Function.identity()).flatMap(data -> {
|
||||
Instant expires = data.getExpireTime();
|
||||
@@ -297,18 +295,19 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
private static PlayerInjectionHandler getHandler() {
|
||||
PacketFilterManager manager = (PacketFilterManager) ProtocolLibrary.getProtocolManager();
|
||||
FieldAccessor accessor = Accessors.getFieldAccessor(manager.getClass(), PlayerInjectionHandler.class, true);
|
||||
return (PlayerInjectionHandler) accessor.get(manager);
|
||||
}
|
||||
|
||||
private FloodgatePlayer getFloodgatePlayer(Player player) {
|
||||
Channel channel = handler.getChannel(player);
|
||||
Channel channel = getChannel(player);
|
||||
AttributeKey<FloodgatePlayer> floodgateAttribute = AttributeKey.valueOf("floodgate-player");
|
||||
return channel.attr(floodgateAttribute).get();
|
||||
}
|
||||
|
||||
private static Channel getChannel(Player player) {
|
||||
NettyChannelInjector injector = (NettyChannelInjector) Accessors.getMethodAccessorOrNull(
|
||||
TemporaryPlayerFactory.class, "getInjectorFromPlayer", Player.class
|
||||
).invoke(null, player);
|
||||
return FuzzyReflection.getFieldValue(injector, Channel.class, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reimplementation of the tasks injected Floodgate in ProtocolLib that are not run due to a bug
|
||||
* @see <a href="https://github.com/GeyserMC/Floodgate/issues/143">Issue Floodgate#143</a>
|
||||
@@ -325,7 +324,7 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
// kick the player, if necessary
|
||||
Channel channel = handler.getChannel(packetEvent.getPlayer());
|
||||
Channel channel = getChannel(packetEvent.getPlayer());
|
||||
AttributeKey<String> kickMessageAttribute = AttributeKey.valueOf("floodgate-kick-message");
|
||||
String kickMessage = channel.attr(kickMessageAttribute).get();
|
||||
if (kickMessage != null) {
|
||||
|
||||
@@ -28,6 +28,7 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector;
|
||||
import com.comphenix.protocol.injector.packet.PacketRegistry;
|
||||
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.EquivalentConverter;
|
||||
@@ -49,7 +50,6 @@ import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.InetUtils;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
@@ -218,15 +218,14 @@ public class VerifyResponseTask implements Runnable {
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager() throws ClassNotFoundException {
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
NettyChannelInjector injectorContainer = (NettyChannelInjector) Accessors.getMethodAccessorOrNull(
|
||||
TemporaryPlayerFactory.class, "getInjectorFromPlayer", Player.class
|
||||
).invoke(null, player);
|
||||
|
||||
// ChannelInjector
|
||||
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
|
||||
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
|
||||
|
||||
Class<?> rawInjectorClass = rawInjector.getClass();
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(rawInjectorClass, "networkManager", Object.class);
|
||||
return accessor.get(rawInjector);
|
||||
FieldAccessor accessor = Accessors.getFieldAccessorOrNull(
|
||||
NettyChannelInjector.class, "networkManager", Object.class
|
||||
);
|
||||
return accessor.get(injectorContainer);
|
||||
}
|
||||
|
||||
private boolean enableEncryption(SecretKey loginKey) throws IllegalArgumentException {
|
||||
@@ -307,7 +306,7 @@ public class VerifyResponseTask implements Runnable {
|
||||
startPacket.getStrings().write(0, username);
|
||||
|
||||
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
|
||||
val wrappedKey = Optional.ofNullable(clientKey).map(key ->
|
||||
Optional<WrappedProfileKeyData> wrappedKey = Optional.ofNullable(clientKey).map(key ->
|
||||
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
|
||||
);
|
||||
|
||||
|
||||
@@ -25,20 +25,38 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Value(staticConstructor = "of")
|
||||
public class ClientPublicKey {
|
||||
Instant expiry;
|
||||
PublicKey key;
|
||||
byte[] signature;
|
||||
|
||||
private final Instant expiry;
|
||||
private final PublicKey key;
|
||||
private final byte[] signature;
|
||||
|
||||
public Instant expiry() {
|
||||
return expiry;
|
||||
}
|
||||
|
||||
public PublicKey key() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public byte[] signature() {
|
||||
return signature;
|
||||
}
|
||||
|
||||
public ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) {
|
||||
this.expiry = expiry;
|
||||
this.key = key;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public static ClientPublicKey of(Instant expiry, PublicKey key, byte[] signature) {
|
||||
return new ClientPublicKey(expiry, key, signature);
|
||||
}
|
||||
|
||||
public boolean isExpired(Instant verifyTimestamp) {
|
||||
return !verifyTimestamp.isBefore(expiry);
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import lombok.val;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -37,13 +37,13 @@ class FastLoginBukkitTest {
|
||||
|
||||
@Test
|
||||
void testRGB() {
|
||||
val message = "&x00002a00002b&lText";
|
||||
val msg = CommonUtil.translateColorCodes(message);
|
||||
String message = "&x00002a00002b&lText";
|
||||
String msg = CommonUtil.translateColorCodes(message);
|
||||
assertEquals(msg, "§x00002a00002b§lText");
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
val components = TextComponent.fromLegacyText(msg);
|
||||
val expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
|
||||
BaseComponent[] components = TextComponent.fromLegacyText(msg);
|
||||
String expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
|
||||
//noinspection deprecation
|
||||
assertEquals(ComponentSerializer.toString(components), expected);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import lombok.val;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
@@ -37,7 +36,7 @@ public class Base64Adapter extends TypeAdapter<byte[]> {
|
||||
|
||||
@Override
|
||||
public void write(JsonWriter out, byte[] value) throws IOException {
|
||||
val encoded = Base64.getEncoder().encodeToString(value);
|
||||
String encoded = Base64.getEncoder().encodeToString(value);
|
||||
out.value(encoded);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.hash.Hasher;
|
||||
import com.google.common.hash.Hashing;
|
||||
import lombok.val;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
@@ -51,6 +51,7 @@ import java.security.SignatureException;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@@ -60,7 +61,7 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testVerifyToken() {
|
||||
val random = ThreadLocalRandom.current();
|
||||
Random random = ThreadLocalRandom.current();
|
||||
byte[] token = EncryptionUtil.generateVerifyToken(random);
|
||||
|
||||
assertAll(
|
||||
@@ -88,10 +89,10 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testExpiredClientKey() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
|
||||
// Client expires at the exact second mentioned, so use it for verification
|
||||
val expiredTimestamp = clientKey.expiry();
|
||||
Instant expiredTimestamp = clientKey.expiry();
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp, null));
|
||||
}
|
||||
|
||||
@@ -105,7 +106,7 @@ class EncryptionUtilTest {
|
||||
"client_keys/invalid_wrong_signature.json"
|
||||
})
|
||||
void testInvalidClientKey(String clientKeySource) throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey(clientKeySource);
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey(clientKeySource);
|
||||
Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp, null));
|
||||
@@ -113,34 +114,34 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testValidClientKey() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValid191ClientKey() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
val ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
|
||||
UUID ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
|
||||
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncorrect191ClientOwner() throws Exception {
|
||||
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
|
||||
Instant verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
|
||||
|
||||
val ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
|
||||
UUID ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
|
||||
assertFalse(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecryptSharedSecret() throws Exception {
|
||||
KeyPair serverPair = EncryptionUtil.generateKeyPair();
|
||||
val serverPK = serverPair.getPublic();
|
||||
PublicKey serverPK = serverPair.getPublic();
|
||||
|
||||
SecretKey secretKey = generateSharedKey();
|
||||
byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded());
|
||||
@@ -152,7 +153,7 @@ class EncryptionUtilTest {
|
||||
private static byte[] encrypt(PublicKey receiverKey, byte... message)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
|
||||
IllegalBlockSizeException, BadPaddingException {
|
||||
val encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
|
||||
Cipher encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
|
||||
encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey);
|
||||
return encryptCipher.doFinal(message);
|
||||
}
|
||||
@@ -168,9 +169,9 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHash() throws Exception {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
|
||||
@@ -185,7 +186,7 @@ class EncryptionUtilTest {
|
||||
// sha1.update(server's encoded public key from Encryption Request)
|
||||
// hash := sha1.hexdigest() # String of hex characters
|
||||
@SuppressWarnings("deprecation")
|
||||
val hasher = Hashing.sha1().newHasher();
|
||||
Hasher hasher = Hashing.sha1().newHasher();
|
||||
hasher.putString(serverId, StandardCharsets.US_ASCII);
|
||||
hasher.putBytes(sharedSecret.getEncoded());
|
||||
hasher.putBytes(serverPK.getEncoded());
|
||||
@@ -198,9 +199,9 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongSecret() throws Exception {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), sessionHash);
|
||||
@@ -208,12 +209,12 @@ class EncryptionUtilTest {
|
||||
|
||||
@Test
|
||||
void testServerIdHashWrongServerKey() {
|
||||
val serverId = "";
|
||||
val sharedSecret = generateSharedKey();
|
||||
val serverPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
String serverId = "";
|
||||
SecretKeySpec sharedSecret = generateSharedKey();
|
||||
PublicKey serverPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
|
||||
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
|
||||
val wrongPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
PublicKey wrongPK = EncryptionUtil.generateKeyPair().getPublic();
|
||||
assertNotEquals(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), sessionHash);
|
||||
}
|
||||
|
||||
@@ -257,8 +258,8 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonce() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
val encryptedNonce = encrypt(serverKey.getPublic(), expected);
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
byte[] encryptedNonce = encrypt(serverKey.getPublic(), expected);
|
||||
|
||||
assertTrue(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
|
||||
}
|
||||
@@ -266,19 +267,19 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonceIncorrect() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
|
||||
// flipped first character
|
||||
val encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
|
||||
byte[] encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
|
||||
assertFalse(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonceFailedDecryption() throws Exception {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
// generate a new keypair that is different
|
||||
val encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
|
||||
byte[] encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
() -> EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce)
|
||||
@@ -288,7 +289,7 @@ class EncryptionUtilTest {
|
||||
@Test
|
||||
void testNonceIncorrectEmpty() {
|
||||
byte[] expected = {1, 2, 3, 4};
|
||||
val serverKey = EncryptionUtil.generateKeyPair();
|
||||
KeyPair serverKey = EncryptionUtil.generateKeyPair();
|
||||
byte[] encryptedNonce = {};
|
||||
|
||||
assertThrows(GeneralSecurityException.class,
|
||||
|
||||
@@ -28,16 +28,16 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import lombok.val;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class SignatureTestData {
|
||||
|
||||
public static SignatureTestData fromResource(String resourceName) throws IOException {
|
||||
val keyUrl = Resources.getResource(resourceName);
|
||||
val encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
URL keyUrl = Resources.getResource(resourceName);
|
||||
String encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
|
||||
return new Gson().fromJson(encodedSignature, SignatureTestData.class);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
package com.github.games647.fastlogin.bukkit.task;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import lombok.val;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -36,7 +35,7 @@ class DelayedAuthHookTest {
|
||||
|
||||
@Test
|
||||
void createNewReflectiveInstance() throws ReflectiveOperationException {
|
||||
val authHook = new DelayedAuthHook(null);
|
||||
DelayedAuthHook authHook = new DelayedAuthHook(null);
|
||||
assertNotNull(authHook.newInstance(DummyHook.class));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
|
||||
@@ -76,7 +76,12 @@ public class AsyncToggleMessage implements Runnable {
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getPluginManager().callEvent(
|
||||
new BungeeFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("remove-premium");
|
||||
|
||||
if (isPlayerSender && core.getConfig().getBoolean("kick-toggle", true)) {
|
||||
sender.disconnect(TextComponent.fromLegacyText(core.getMessage("remove-premium")));
|
||||
} else {
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
@@ -147,7 +147,7 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-config</artifactId>
|
||||
<version>1.20-R0.2-SNAPSHOT</version>
|
||||
<version>1.20-R0.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- This is optional in BungeeCord-config, so we include it here manually -->
|
||||
|
||||
@@ -63,7 +63,7 @@ public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public void readFrom(ByteArrayDataInput input) {
|
||||
this.type = Type.values()[input.readInt()];
|
||||
this.type = Type.values()[input.readByte()];
|
||||
|
||||
this.playerName = input.readUTF();
|
||||
|
||||
@@ -75,7 +75,7 @@ public class LoginActionMessage implements ChannelMessage {
|
||||
|
||||
@Override
|
||||
public void writeTo(ByteArrayDataOutput output) {
|
||||
output.writeInt(type.ordinal());
|
||||
output.writeByte(type.ordinal());
|
||||
|
||||
//Data is sent through a random player. We have to tell the Bukkit version of this plugin the target
|
||||
output.writeUTF(playerName);
|
||||
|
||||
@@ -42,7 +42,8 @@ public class AsyncScheduler extends AbstractAsyncScheduler {
|
||||
|
||||
public AsyncScheduler(Logger logger, Executor processingPool) {
|
||||
super(logger, processingPool);
|
||||
logger.info("Using legacy scheduler");
|
||||
logger.info("Using legacy platform scheduler for using an older Java version. "
|
||||
+ "Upgrade Java to 21+ for improved performance");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -62,9 +62,9 @@ public abstract class FloodgateManagement<P extends C, C, L extends LoginSession
|
||||
this.username = getName(player);
|
||||
|
||||
//load values from config.yml
|
||||
autoLoginFloodgate = core.getConfig().getString("autoLoginFloodgate").toLowerCase(Locale.ROOT);
|
||||
autoRegisterFloodgate = core.getConfig().getString("autoRegisterFloodgate").toLowerCase(Locale.ROOT);
|
||||
allowNameConflict = core.getConfig().getString("allowFloodgateNameConflict").toLowerCase(Locale.ROOT);
|
||||
autoLoginFloodgate = core.getConfig().get("autoLoginFloodgate").toString().toLowerCase(Locale.ROOT);
|
||||
autoRegisterFloodgate = core.getConfig().get("autoRegisterFloodgate").toString().toLowerCase(Locale.ROOT);
|
||||
allowNameConflict = core.getConfig().get("allowFloodgateNameConflict").toString().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -150,11 +150,14 @@ nameChangeCheck: false
|
||||
forwardSkin: true
|
||||
|
||||
# Displays a warning message that this message SHOULD only be invoked by
|
||||
# users who actually are the owner of this account. So not by cracked players
|
||||
# users who actually are the owner of this account (and not cracked players)
|
||||
#
|
||||
# If they still want to invoke the command, they have to invoke /premium again
|
||||
premium-warning: true
|
||||
|
||||
# Kick players after they confirmed the command from above.
|
||||
kick-toggle: true
|
||||
|
||||
# ======[[ Spigot+ProtocolLib users only ]]======
|
||||
# When set to true, enables the use of alternative session resolver which does not send the server IP
|
||||
# to mojang session servers. This setting might be useful when you are trying to run the server via a
|
||||
@@ -171,7 +174,7 @@ premium-warning: true
|
||||
# This option is considered highly experimental. While it is highly unlikely this will break your server,
|
||||
# more tests need to be conducted in order to verify its effectiveness. Brief tests seemed promising, but
|
||||
# every environment is different, and so it might not work for you as it did for me.
|
||||
useProxyAgnosticResolver: false
|
||||
useProxyAgnosticResolver: true
|
||||
|
||||
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
|
||||
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
|
||||
|
||||
24
pom.xml
24
pom.xml
@@ -50,7 +50,7 @@
|
||||
|
||||
<maven.compiler.release>8</maven.compiler.release>
|
||||
|
||||
<lombook.version>1.18.32</lombook.version>
|
||||
<lombook.version>1.18.34</lombook.version>
|
||||
|
||||
<floodgate.version>2.2.3-SNAPSHOT</floodgate.version>
|
||||
<geyser.version>2.2.1-SNAPSHOT</geyser.version>
|
||||
@@ -106,7 +106,7 @@
|
||||
<plugin>
|
||||
<groupId>com.mycila</groupId>
|
||||
<artifactId>license-maven-plugin</artifactId>
|
||||
<version>4.3</version>
|
||||
<version>4.5</version>
|
||||
<configuration>
|
||||
<licenseSets>
|
||||
<licenseSet>
|
||||
@@ -134,7 +134,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||
<version>3.3.1</version>
|
||||
<version>3.4.0</version>
|
||||
<configuration>
|
||||
<configLocation>checkstyle.xml</configLocation>
|
||||
<consoleOutput>true</consoleOutput>
|
||||
@@ -145,7 +145,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>10.16.0</version>
|
||||
<version>10.17.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@@ -162,7 +162,7 @@
|
||||
<!-- Require newer versions for Junit5 support -->
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<!-- Work-around to make multi-release classes discoverable
|
||||
https://issues.apache.org/jira/browse/SUREFIRE-1731 -->
|
||||
@@ -179,7 +179,7 @@
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.4.2</version>
|
||||
<!-- Explicitly enable multi-release for the scheduler,
|
||||
because detection from class shading doesn't work -->
|
||||
<executions>
|
||||
@@ -218,18 +218,10 @@
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<!-- Use lombok to use some newer Java syntax features in Java 8 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombook.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.10.2</version>
|
||||
<version>5.10.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -237,7 +229,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.11.0</version>
|
||||
<version>5.17.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.5.3</version>
|
||||
<version>3.6.0</version>
|
||||
<configuration>
|
||||
<minimizeJar>true</minimizeJar>
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>3.3.3</version>
|
||||
<version>3.4.0</version>
|
||||
<exclusions>
|
||||
<!-- Exclude JNA implementation for WAFFLE - Windows Authentication Framework (mariadb)-->
|
||||
<exclusion>
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
|
||||
@@ -57,7 +58,6 @@ import org.geysermc.geyser.GeyserImpl;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -75,7 +75,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
private final ProxyServer server;
|
||||
private final Path dataDirectory;
|
||||
private final Logger logger;
|
||||
private final ConcurrentMap<InetSocketAddress, VelocityLoginSession> session = new MapMaker().weakKeys().makeMap();
|
||||
private final ConcurrentMap<InboundConnection, VelocityLoginSession> session = new MapMaker().weakKeys().makeMap();
|
||||
private static final String PROXY_ID_FILE = "proxyId.txt";
|
||||
|
||||
private FastLoginCore<Player, CommandSource, FastLoginVelocity> core;
|
||||
@@ -175,7 +175,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
return core;
|
||||
}
|
||||
|
||||
public ConcurrentMap<InetSocketAddress, VelocityLoginSession> getSession() {
|
||||
public ConcurrentMap<InboundConnection, VelocityLoginSession> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -230,4 +230,8 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
|
||||
public UUID getProxyId() {
|
||||
return proxyId;
|
||||
}
|
||||
|
||||
public ProxyServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,4 +66,8 @@ public class VelocityLoginSource implements LoginSource {
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getRemoteAddress();
|
||||
}
|
||||
|
||||
public InboundConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ import com.github.games647.fastlogin.velocity.VelocityLoginSession;
|
||||
import com.github.games647.fastlogin.velocity.task.AsyncPremiumCheck;
|
||||
import com.github.games647.fastlogin.velocity.task.FloodgateAuthTask;
|
||||
import com.github.games647.fastlogin.velocity.task.ForceLoginTask;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.velocitypowered.api.event.EventManager;
|
||||
import com.velocitypowered.api.event.EventTask;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
@@ -43,6 +46,7 @@ import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
@@ -52,6 +56,7 @@ import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
@@ -61,6 +66,8 @@ import java.util.UUID;
|
||||
|
||||
public class ConnectListener {
|
||||
|
||||
private static final String FLOODGATE_PLUGIN_NAME = "org.geysermc.floodgate.VelocityPlugin";
|
||||
|
||||
private final FastLoginVelocity plugin;
|
||||
private final AntiBotService antiBotService;
|
||||
|
||||
@@ -80,6 +87,15 @@ public class ConnectListener {
|
||||
InetSocketAddress address = connection.getRemoteAddress();
|
||||
plugin.getLog().info("Incoming login request for {} from {}", username, address);
|
||||
|
||||
// FloodgateVelocity only sets the correct username in GetProfileRequestEvent, but we need it here too.
|
||||
if (plugin.getFloodgateService() != null) {
|
||||
String floodgateUsername = getFloodgateUsername(connection);
|
||||
if (floodgateUsername != null) {
|
||||
plugin.getLog().info("Found player's Floodgate: {}", floodgateUsername);
|
||||
username = floodgateUsername;
|
||||
}
|
||||
}
|
||||
|
||||
Action action = antiBotService.onIncomingConnection(address, username);
|
||||
switch (action) {
|
||||
case Ignore:
|
||||
@@ -103,9 +119,9 @@ public class ConnectListener {
|
||||
@Subscribe
|
||||
public void onGameProfileRequest(GameProfileRequestEvent event) {
|
||||
if (event.isOnlineMode()) {
|
||||
LoginSession session = plugin.getSession().get(event.getConnection().getRemoteAddress());
|
||||
LoginSession session = plugin.getSession().get(event.getConnection());
|
||||
if (session == null) {
|
||||
plugin.getLog().warn("No active login session found for player {}", event.getUsername());
|
||||
plugin.getLog().error("No active login session found for onlinemode player {}", event.getUsername());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,7 +133,7 @@ public class ConnectListener {
|
||||
StoredProfile playerProfile = session.getProfile();
|
||||
playerProfile.setId(verifiedUUID);
|
||||
if (!plugin.getCore().getConfig().get("premiumUuid", true)) {
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(playerProfile.getName());
|
||||
UUID offlineUUID = UUIDAdapter.generateOfflineId(event.getUsername());
|
||||
event.setGameProfile(event.getGameProfile().withId(offlineUUID));
|
||||
plugin.getLog().info("Overridden UUID from {} to {} (based of {}) on {}",
|
||||
verifiedUUID, offlineUUID, verifiedUsername, event.getConnection());
|
||||
@@ -157,7 +173,7 @@ public class ConnectListener {
|
||||
}
|
||||
}
|
||||
|
||||
VelocityLoginSession session = plugin.getSession().get(player.getRemoteAddress());
|
||||
VelocityLoginSession session = plugin.getSession().get(player);
|
||||
if (session == null) {
|
||||
plugin.getLog().info("No active login session found on server connect for {}", player);
|
||||
return;
|
||||
@@ -176,5 +192,77 @@ public class ConnectListener {
|
||||
public void onDisconnect(DisconnectEvent disconnectEvent) {
|
||||
Player player = disconnectEvent.getPlayer();
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
|
||||
plugin.getSession().remove(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Floodgate username from the Floodgate plugin's playerCache using lots of reflections
|
||||
*
|
||||
* @param connection
|
||||
* @return the Floodgate username or null if not found
|
||||
*/
|
||||
private String getFloodgateUsername(InboundConnection connection) {
|
||||
try {
|
||||
// get floodgate's event handler
|
||||
Object floodgateEventHandler = getFloodgateHandler();
|
||||
if (floodgateEventHandler == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the Floodgate playerCache field
|
||||
Field playerCacheField = floodgateEventHandler.getClass().getDeclaredField("playerCache");
|
||||
playerCacheField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
Cache<InboundConnection, FloodgatePlayer> playerCache =
|
||||
(Cache<InboundConnection, FloodgatePlayer>) playerCacheField.get(floodgateEventHandler);
|
||||
|
||||
// Find the FloodgatePlayer instance in playerCache
|
||||
FloodgatePlayer floodgatePlayer = playerCache.getIfPresent(connection);
|
||||
if (floodgatePlayer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return floodgatePlayer.getCorrectUsername();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLog().error("Failed to fetch current floodgate username", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Object getFloodgateHandler()
|
||||
throws NoSuchFieldException, IllegalAccessException {
|
||||
// Get Velocity's event manager
|
||||
EventManager eventManager = plugin.getServer().getEventManager();
|
||||
Field handlerField = eventManager.getClass().getDeclaredField("handlersByType");
|
||||
handlerField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
ListMultimap<Class<?>, ?> handlersByType = (ListMultimap<Class<?>, ?>) handlerField.get(eventManager);
|
||||
|
||||
// Get all registered PreLoginEvent handlers
|
||||
List<?> loginEventRegistrations = handlersByType.get(PreLoginEvent.class);
|
||||
Field pluginField = loginEventRegistrations.get(0).getClass().getDeclaredField("plugin");
|
||||
pluginField.setAccessible(true);
|
||||
|
||||
// Find the Floodgate plugin's PreLoginEvent handler registration (Velocity implementation)
|
||||
Object floodgateRegistration = null;
|
||||
for (Object handler : loginEventRegistrations) {
|
||||
PluginContainer eventHandlerPlugin = (PluginContainer) pluginField.get(handler);
|
||||
String eventHandlerPluginName = eventHandlerPlugin.getInstance().get().getClass().getName();
|
||||
if (eventHandlerPluginName.equals(FLOODGATE_PLUGIN_NAME)) {
|
||||
floodgateRegistration = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (floodgateRegistration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the EventHandler instance (floodgate impl) from Velocity's internal registration handler storage
|
||||
Field eventHandlerField = floodgateRegistration.getClass().getDeclaredField("instance");
|
||||
eventHandlerField.setAccessible(true);
|
||||
return eventHandlerField.get(floodgateRegistration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ public class PluginMessageListener {
|
||||
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
|
||||
}
|
||||
|
||||
private void readMessage(Player forPlayer, String channel, byte[] data) {
|
||||
private void readMessage(Player sender, String channel, byte[] data) {
|
||||
FastLoginCore<Player, CommandSource, FastLoginVelocity> core = plugin.getCore();
|
||||
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
if (successChannel.equals(channel)) {
|
||||
onSuccessMessage(forPlayer);
|
||||
onSuccessMessage(sender);
|
||||
} else if (changeChannel.equals(channel)) {
|
||||
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
|
||||
changeMessage.readFrom(dataInput);
|
||||
@@ -97,19 +97,19 @@ public class PluginMessageListener {
|
||||
boolean isSourceInvoker = changeMessage.isSourceInvoker();
|
||||
if (changeMessage.shouldEnable()) {
|
||||
Boolean premiumWarning = plugin.getCore().getConfig().get("premium-warning", true);
|
||||
if (playerName.equals(forPlayer.getUsername()) && premiumWarning
|
||||
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
if (playerName.equals(sender.getUsername()) && premiumWarning
|
||||
&& !core.getPendingConfirms().contains(sender.getUniqueId())) {
|
||||
String message = core.getMessage("premium-warning");
|
||||
forPlayer.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
|
||||
core.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
sender.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
|
||||
core.getPendingConfirms().add(sender.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
core.getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
|
||||
core.getPendingConfirms().remove(sender.getUniqueId());
|
||||
Runnable task = new AsyncToggleMessage(core, sender, playerName, true, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
} else {
|
||||
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
|
||||
Runnable task = new AsyncToggleMessage(core, sender, playerName, false, isSourceInvoker);
|
||||
plugin.getScheduler().runAsync(task);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ public class PluginMessageListener {
|
||||
if (shouldPersist) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
VelocityLoginSession loginSession = plugin.getSession().get(forPlayer.getRemoteAddress());
|
||||
VelocityLoginSession loginSession = plugin.getSession().get(forPlayer);
|
||||
StoredProfile playerProfile = loginSession.getProfile();
|
||||
loginSession.setRegistered(true);
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AsyncPremiumCheck extends JoinManagement<Player, CommandSource, Vel
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getSession().remove(connection.getRemoteAddress());
|
||||
plugin.getSession().remove(connection);
|
||||
super.onLogin(username, new VelocityLoginSource(connection, preLoginEvent));
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public class AsyncPremiumCheck extends JoinManagement<Player, CommandSource, Vel
|
||||
String username, boolean registered) {
|
||||
source.enableOnlinemode();
|
||||
VelocityLoginSession session = new VelocityLoginSession(username, registered, profile);
|
||||
plugin.getSession().put(source.getAddress(), session);
|
||||
plugin.getSession().put(source.getConnection(), session);
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().addLoginAttempt(ip, username);
|
||||
@@ -91,6 +91,6 @@ public class AsyncPremiumCheck extends JoinManagement<Player, CommandSource, Vel
|
||||
@Override
|
||||
public void startCrackedSession(VelocityLoginSource source, StoredProfile profile, String username) {
|
||||
VelocityLoginSession session = new VelocityLoginSession(username, false, profile);
|
||||
plugin.getSession().put(source.getAddress(), session);
|
||||
plugin.getSession().put(source.getConnection(), session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,29 +33,26 @@ import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPremiumTogg
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.proxy.ConsoleCommandSource;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
private final FastLoginCore<Player, CommandSource, FastLoginVelocity> core;
|
||||
private final CommandSource sender;
|
||||
private final Player sender;
|
||||
private final String senderName;
|
||||
private final String targetPlayer;
|
||||
private final boolean toPremium;
|
||||
private final boolean isPlayerSender;
|
||||
|
||||
public AsyncToggleMessage(FastLoginCore<Player, CommandSource, FastLoginVelocity> core,
|
||||
CommandSource sender, String playerName, boolean toPremium, boolean playerSender) {
|
||||
Player sender, String playerName, boolean toPremium, boolean playerSender) {
|
||||
this.core = core;
|
||||
this.sender = sender;
|
||||
this.targetPlayer = playerName;
|
||||
this.toPremium = toPremium;
|
||||
this.isPlayerSender = playerSender;
|
||||
if (sender instanceof Player playSender) {
|
||||
senderName = playSender.getUsername();
|
||||
} else {
|
||||
senderName = "";
|
||||
}
|
||||
this.senderName = sender.getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,7 +79,14 @@ public class AsyncToggleMessage implements Runnable {
|
||||
? PremiumToggleReason.COMMAND_OTHER : PremiumToggleReason.COMMAND_SELF;
|
||||
core.getPlugin().getProxy().getEventManager().fire(
|
||||
new VelocityFastLoginPremiumToggleEvent(playerProfile, reason));
|
||||
sendMessage("remove-premium");
|
||||
|
||||
if (isPlayerSender && core.getConfig().getBoolean("kick-toggle", true)) {
|
||||
TextComponent msg = LegacyComponentSerializer.legacyAmpersand()
|
||||
.deserialize(core.getMessage("remove-premium"));
|
||||
sender.disconnect(msg);
|
||||
} else {
|
||||
sendMessage("remove-premium");
|
||||
}
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
|
||||
@@ -52,7 +52,7 @@ public class FloodgateAuthTask
|
||||
@Override
|
||||
protected void startLogin() {
|
||||
VelocityLoginSession session = new VelocityLoginSession(player.getUsername(), isRegistered, profile);
|
||||
core.getPlugin().getSession().put(player.getRemoteAddress(), session);
|
||||
core.getPlugin().getSession().put(player, session);
|
||||
|
||||
// enable auto login based on the value of 'autoLoginFloodgate' in config.yml
|
||||
boolean forcedOnlineMode = autoLoginFloodgate.equals("true")
|
||||
|
||||
Reference in New Issue
Block a user