Compare commits

...

26 Commits

Author SHA1 Message Date
TuxCoding
568ad7a621 Add configuration option to kick players when toggling premium state
Fixes #1285
2025-04-23 17:12:32 +02:00
TuxCoding
1464ef2952 Add command invoker for change command events
Related #1285
2025-04-23 16:46:37 +02:00
TuxCoding
46239c9ffc Use InboundConnection as session keys for Velocity
Related #1286
2025-04-23 12:22:10 +02:00
TuxCoding
083068b856 Update Mockito for newer Java versions 2025-04-23 12:21:24 +02:00
TuxCoding
dd2aa922d7 [ci-skip] Add project logo 2025-03-18 17:22:48 +01:00
games647
b77ea285e5 Be more precise about why using legacy scheduler 2025-03-18 17:22:48 +01:00
games647
f8f0e7ac7a Drop lombok tool 2025-03-18 17:22:48 +01:00
games647
927f09af67 Fix ProtocolLib version and paper URL (#1269)
### Summary of your change

- Use new URL for papermc. The old one will breaked at the end of
december
- The used version of ProtocolLib is no longer available. I changed it
to new one.
2024-12-02 11:40:45 +01:00
Elikill58
9dee4e56a1 Fix ProtocolLib version and paper URL 2024-12-01 22:10:06 +01:00
games647
4dd6b9ade4 Fix finding player network channel with Protocollib and Floodgate
This resolves the deleted ProtocolLib method for newer builds, but uses
reflection to find the channel despite the variable name.

Fixes #1216
2024-07-08 12:38:10 +02:00
games647
51efb9d62d Extract username for protocollib packets 2024-07-08 12:30:14 +02:00
games647
1a56741112 Merge pull request #1223 from Smart123s/fix/floodgate/velocity-1
Floodgate Velocity fixes
2024-07-08 11:40:28 +02:00
games647
7f488498cf Minor code styling
* Use logging instead of raw exception printing
* Extract method
* Shorter var names
* More precise generics if possible
* Don't restore accessible state could be in conflict with other plugins
if we don't restore the exact value
2024-07-08 10:53:50 +02:00
Smart123s
9a40cf0afb Mark Floodgate Join events as fired too 2024-07-06 18:10:52 +02:00
Smart123s
2d177e3df5 Retrieve and use Floodgate username in Velocity PreLoginEvent 2024-07-06 18:10:26 +02:00
games647
bdd7af8290 Use case-sensitive name from the event joining player
Related #1219
2024-07-05 14:45:20 +02:00
games647
29100b5376 Merge pull request #1214 from games647/dependabot/maven/development-dependencies-ac1d0d4f7c
Bump the development-dependencies group across 1 directory with 2 updates
2024-07-02 14:18:04 +02:00
games647
bcb1893176 Merge pull request #1215 from games647/dependabot/maven/production-dependencies-8573e958dd
Bump the production-dependencies group across 1 directory with 10 updates
2024-07-02 14:17:43 +02:00
games647
5fb6308130 Fix finding the player injector with newer ProtocolLib versions
Fixes #1210
2024-07-02 14:16:56 +02:00
dependabot[bot]
a33f53e259 Bump the production-dependencies group across 1 directory with 10 updates
Bumps the production-dependencies group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) | `10.16.0` | `10.17.0` |
| [org.projectlombok:lombok](https://github.com/projectlombok/lombok) | `1.18.32` | `1.18.34` |
| [com.mycila:license-maven-plugin](https://github.com/mathieucarbou/license-maven-plugin) | `4.4` | `4.5` |
| [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) | `3.3.1` | `3.4.0` |
| [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.2.5` | `3.3.0` |
| [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) | `3.4.1` | `3.4.2` |
| me.clip:placeholderapi | `2.11.5` | `2.11.6` |
| [fr.xephi:authme](https://github.com/AuthMe/AuthMeReloaded) | `5.6.0-beta2` | `5.6.0` |
| [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) | `3.5.3` | `3.6.0` |
| [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) | `3.3.3` | `3.4.0` |



Updates `com.puppycrawl.tools:checkstyle` from 10.16.0 to 10.17.0
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.16.0...checkstyle-10.17.0)

Updates `org.projectlombok:lombok` from 1.18.32 to 1.18.34
- [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown)
- [Commits](https://github.com/projectlombok/lombok/compare/v1.18.32...v1.18.34)

Updates `com.mycila:license-maven-plugin` from 4.4 to 4.5
- [Commits](https://github.com/mathieucarbou/license-maven-plugin/compare/license-maven-plugin-4.4...license-maven-plugin-4.5)

Updates `org.apache.maven.plugins:maven-checkstyle-plugin` from 3.3.1 to 3.4.0
- [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.3.1...maven-checkstyle-plugin-3.4.0)

Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.2.5 to 3.3.0
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.5...surefire-3.3.0)

Updates `org.apache.maven.plugins:maven-jar-plugin` from 3.4.1 to 3.4.2
- [Release notes](https://github.com/apache/maven-jar-plugin/releases)
- [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.4.1...maven-jar-plugin-3.4.2)

Updates `me.clip:placeholderapi` from 2.11.5 to 2.11.6

Updates `fr.xephi:authme` from 5.6.0-beta2 to 5.6.0
- [Release notes](https://github.com/AuthMe/AuthMeReloaded/releases)
- [Commits](https://github.com/AuthMe/AuthMeReloaded/compare/5.6.0-beta2...5.6.0)

Updates `org.apache.maven.plugins:maven-shade-plugin` from 3.5.3 to 3.6.0
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.5.3...maven-shade-plugin-3.6.0)

Updates `org.mariadb.jdbc:mariadb-java-client` from 3.3.3 to 3.4.0
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.3...3.4.0)

---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: org.projectlombok:lombok
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: com.mycila:license-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: org.apache.maven.plugins:maven-jar-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: me.clip:placeholderapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: fr.xephi:authme
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: production-dependencies
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 07:58:40 +00:00
dependabot[bot]
fb6209d26c Bump the development-dependencies group across 1 directory with 2 updates
Bumps the development-dependencies group with 2 updates in the / directory: [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) and [org.mockito:mockito-core](https://github.com/mockito/mockito).


Updates `org.junit.jupiter:junit-jupiter` from 5.10.2 to 5.10.3
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3)

Updates `org.mockito:mockito-core` from 5.11.0 to 5.12.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.11.0...v5.12.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 07:57:04 +00:00
games647
829c70a51b Disable connection verification by default
This matches the vanilla, Spigot, BungeeCord and Velocity configuration.
Mojang seems to have too many issues like IPv6 errors and it's atm not
very useful for server administrators.

Fixes #1102
2024-05-22 15:32:30 +02:00
games647
2876448e0c Merge pull request #1201 from games647/dependabot/maven/production-dependencies-e9fd1767f2
Bump com.mycila:license-maven-plugin from 4.3 to 4.4 in the production-dependencies group
2024-05-22 15:13:53 +02:00
games647
2b60153f8a Merge pull request #1205 from wtlgo/main
Fix floodgate config parsing
2024-05-21 16:47:03 +02:00
wtlgo
54b49eb6be Fix floodgate config parsing 2024-05-21 17:02:46 +03:00
dependabot[bot]
64d291556a Bump com.mycila:license-maven-plugin
Bumps the production-dependencies group with 1 update: [com.mycila:license-maven-plugin](https://github.com/mathieucarbou/license-maven-plugin).


Updates `com.mycila:license-maven-plugin` from 4.3 to 4.4
- [Commits](https://github.com/mathieucarbou/license-maven-plugin/compare/license-maven-plugin-4.3...license-maven-plugin-4.4)

---
updated-dependencies:
- dependency-name: com.mycila:license-maven-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: production-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 18:34:18 +00:00
31 changed files with 310 additions and 161 deletions

View File

@@ -1,5 +1,7 @@
# FastLogin
![A shield-shaped emblem with a bold lightning bolt on the left, resembling Minecraft blocks. To the right, "FastLogin" is written in teal, with the tagline: "Automatically detect and login premium Minecraft players"](https://github.com/user-attachments/assets/0788ef69-029b-465e-83a2-b8e7bccc6295 "FastLogin project logo.avif")
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.

View File

@@ -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>

View File

@@ -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));
});
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

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

View File

@@ -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())
);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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));
}

View File

@@ -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>

View File

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

View File

@@ -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 -->

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -66,4 +66,8 @@ public class VelocityLoginSource implements LoginSource {
public InetSocketAddress getAddress() {
return connection.getRemoteAddress();
}
public InboundConnection getConnection() {
return connection;
}
}

View File

@@ -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);
}
}

View File

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

View File

@@ -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);
}
}

View File

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

View File

@@ -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")